alterei loadPersistedToken e persistToken para usar sessionStorage em vez de localStorage (#10)

* Alterei loadPersistedToken e persistToken para usar sessionStorage em vez de localStorage

reduzir a superfície de exposição do token entre sessões (mitigação frente a XSS).

* test: align auth store tests with sessionStorage persistence

---------

Co-authored-by: bruno cesar <brunoclz@brunos-MacBook-Pro.local>
This commit is contained in:
Lorenzo Machado
2026-03-01 18:54:42 -03:00
committed by GitHub
parent 4db4307888
commit 4eed42df82
2 changed files with 16 additions and 13 deletions

View File

@@ -14,8 +14,8 @@ vi.mock("@/api/client", async () => {
return { ...actual, apiFetch: mockApiFetch };
});
// Mock localStorage
const localStorageMock = (() => {
// Mock sessionStorage
const sessionStorageMock = (() => {
let store: Record<string, string> = {};
return {
getItem: vi.fn((key: string) => store[key] ?? null),
@@ -31,7 +31,9 @@ const localStorageMock = (() => {
};
})();
Object.defineProperty(globalThis, "localStorage", { value: localStorageMock });
Object.defineProperty(globalThis, "sessionStorage", {
value: sessionStorageMock,
});
import { useAuthStore } from "./auth";
@@ -49,7 +51,7 @@ function resetStore() {
describe("useAuthStore", () => {
beforeEach(() => {
vi.clearAllMocks();
localStorageMock.clear();
sessionStorageMock.clear();
resetStore();
});
@@ -57,7 +59,7 @@ describe("useAuthStore", () => {
vi.restoreAllMocks();
});
it("login success sets token and user, persists to localStorage", async () => {
it("login success sets token and user, persists to sessionStorage", async () => {
const tokenRes = { access_token: "jwt-123", token_type: "bearer" };
const userRes = {
id: "u1",
@@ -76,7 +78,7 @@ describe("useAuthStore", () => {
expect(state.user).toEqual(userRes);
expect(state.loading).toBe(false);
expect(state.error).toBeNull();
expect(localStorageMock.setItem).toHaveBeenCalledWith(
expect(sessionStorageMock.setItem).toHaveBeenCalledWith(
STORAGE_KEY,
"jwt-123",
);
@@ -163,7 +165,7 @@ describe("useAuthStore", () => {
expect(state.error).toBe("auth.registerError");
});
it("logout clears token, user, and localStorage", () => {
it("logout clears token, user, and sessionStorage", () => {
useAuthStore.setState({
token: "jwt-123",
user: {
@@ -179,7 +181,7 @@ describe("useAuthStore", () => {
expect(state.token).toBeNull();
expect(state.user).toBeNull();
expect(state.error).toBeNull();
expect(localStorageMock.removeItem).toHaveBeenCalledWith(STORAGE_KEY);
expect(sessionStorageMock.removeItem).toHaveBeenCalledWith(STORAGE_KEY);
});
it("restore success validates cached token and sets user", async () => {
@@ -211,7 +213,7 @@ describe("useAuthStore", () => {
const state = useAuthStore.getState();
expect(state.token).toBeNull();
expect(state.user).toBeNull();
expect(localStorageMock.removeItem).toHaveBeenCalledWith(STORAGE_KEY);
expect(sessionStorageMock.removeItem).toHaveBeenCalledWith(STORAGE_KEY);
});
it("isAuthenticated returns true when token present, false otherwise", () => {

View File

@@ -30,7 +30,8 @@ interface AuthState {
function loadPersistedToken(): string | null {
try {
return localStorage.getItem(STORAGE_KEY);
// Persist tokens only in sessionStorage to limit exposure to XSS
return sessionStorage.getItem(STORAGE_KEY);
} catch {
return null;
}
@@ -39,12 +40,12 @@ function loadPersistedToken(): string | null {
function persistToken(token: string | null): void {
try {
if (token) {
localStorage.setItem(STORAGE_KEY, token);
sessionStorage.setItem(STORAGE_KEY, token);
} else {
localStorage.removeItem(STORAGE_KEY);
sessionStorage.removeItem(STORAGE_KEY);
}
} catch {
// localStorage unavailable
// sessionStorage unavailable
}
}