Add Secure and SameSite=Lax flags to all cookie operations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 07:51:14 +08:00
parent 64832bb5f9
commit 954b41b555
5 changed files with 84 additions and 24 deletions

View File

@@ -56,7 +56,23 @@ describe('loginStore', () => {
}),
);
expect(store.isLoggedIn).toBe(true);
expect(document.cookie).toContain('luciaToken=test-access-token');
// Verify token cookie was set with Secure flag
// (jsdom drops Secure cookies, so spy on setter)
const cookieSetter = vi.spyOn(document, 'cookie', 'set');
vi.clearAllMocks();
axios.post.mockResolvedValue({
data: {
access_token: 'test-access-token',
refresh_token: 'test-refresh-token',
},
});
await store.signIn();
const tokenCall = cookieSetter.mock.calls.find(
(c) => c[0].includes('luciaToken='),
);
expect(tokenCall).toBeDefined();
expect(tokenCall[0]).toContain('Secure');
cookieSetter.mockRestore();
expect(store.$router.push).toHaveBeenCalledWith('/files');
});
@@ -173,6 +189,25 @@ describe('loginStore', () => {
// Should update axios default Authorization header
expect(axios.defaults.headers.common['Authorization'])
.toBe('Bearer new-access-token');
// Verify cookies were set with Secure flag
const cookieSetter = vi.spyOn(document, 'cookie', 'set');
vi.clearAllMocks();
document.cookie = 'luciaRefreshToken=old-refresh-token';
axios.post.mockResolvedValue({
status: 200,
data: {
access_token: 'new-access-token',
refresh_token: 'new-refresh-token',
},
});
await store.refreshToken();
const tokenCall = cookieSetter.mock.calls.find(
(c) => c[0].includes('luciaToken='),
);
expect(tokenCall).toBeDefined();
expect(tokenCall[0]).toContain('Secure');
cookieSetter.mockRestore();
});
it('redirects to login and re-throws on failure', async () => {

View File

@@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import {
getCookie,
setCookie,
@@ -7,6 +7,8 @@ import {
} from '@/utils/cookieUtil.js';
describe('cookieUtil', () => {
let cookieSetter;
beforeEach(() => {
// Clear all cookies before each test
document.cookie.split(';').forEach((c) => {
@@ -16,6 +18,9 @@ describe('cookieUtil', () => {
name + '=; Max-Age=-99999999; path=/';
}
});
// Spy on document.cookie setter to capture the raw string
// (jsdom silently drops Secure cookies on http://)
cookieSetter = vi.spyOn(document, 'cookie', 'set');
});
describe('getCookie', () => {
@@ -43,30 +48,49 @@ describe('cookieUtil', () => {
});
describe('setCookie', () => {
it('sets a cookie with default 1-day expiration', () => {
it('sets cookie with Secure and SameSite=Lax flags', () => {
setCookie('myKey', 'myValue');
expect(getCookie('myKey')).toBe('myValue');
});
it('sets a cookie with empty value', () => {
setCookie('emptyKey', '');
expect(getCookie('emptyKey')).toBe('');
const written = cookieSetter.mock.calls.find(
(c) => c[0].startsWith('myKey='),
);
expect(written).toBeDefined();
const str = written[0];
expect(str).toContain('myKey=myValue');
expect(str).toContain('expires=');
expect(str).toContain('path=/');
expect(str).toContain('Secure');
expect(str).toContain('SameSite=Lax');
});
});
describe('setCookieWithoutExpiration', () => {
it('sets a session cookie', () => {
it('sets session cookie with Secure and SameSite=Lax flags', () => {
setCookieWithoutExpiration('sessionKey', 'sessionVal');
expect(getCookie('sessionKey')).toBe('sessionVal');
const written = cookieSetter.mock.calls.find(
(c) => c[0].startsWith('sessionKey='),
);
expect(written).toBeDefined();
const str = written[0];
expect(str).toContain('sessionKey=sessionVal');
expect(str).toContain('Secure');
expect(str).toContain('SameSite=Lax');
});
});
describe('deleteCookie', () => {
it('removes an existing cookie', () => {
setCookie('toDelete', 'value');
expect(getCookie('toDelete')).toBe('value');
it('sets Max-Age=-99999999 with Secure and SameSite=Lax flags', () => {
deleteCookie('toDelete');
expect(getCookie('toDelete')).toBeNull();
const written = cookieSetter.mock.calls.find(
(c) => c[0].startsWith('toDelete='),
);
expect(written).toBeDefined();
const str = written[0];
expect(str).toContain('Max-Age=-99999999');
expect(str).toContain('Secure');
expect(str).toContain('SameSite=Lax');
});
});
});