Skip to content

Commit bfd8053

Browse files
authored
fix(auth): preserve padded auth cookie values (#224)
1 parent e37b0b4 commit bfd8053

4 files changed

Lines changed: 68 additions & 2 deletions

File tree

‎src/app/api/crypto/public-keys/all/route.js‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ async function authenticateUser(request) {
2323

2424
const cookies = Object.fromEntries(
2525
cookieHeader.split(/;\s*/).map((cookie) => {
26-
const [name, value] = cookie.split('=');
26+
const separator = cookie.indexOf('=');
27+
const name = separator === -1 ? cookie : cookie.slice(0, separator);
28+
const value = separator === -1 ? '' : cookie.slice(separator + 1);
2729
return [name, decodeURIComponent(value)];
2830
})
2931
);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
const mocks = vi.hoisted(() => ({
4+
authGetUser: vi.fn(),
5+
from: vi.fn(),
6+
}));
7+
8+
vi.mock('@supabase/supabase-js', () => ({
9+
createClient: vi.fn(() => ({
10+
auth: { getUser: mocks.authGetUser },
11+
from: mocks.from,
12+
})),
13+
}));
14+
15+
describe('all public keys cookie authentication', () => {
16+
beforeEach(() => {
17+
vi.resetModules();
18+
vi.clearAllMocks();
19+
process.env.NEXT_PUBLIC_SUPABASE_URL = 'https://example.supabase.co';
20+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = 'anon-key';
21+
process.env.SUPABASE_SERVICE_ROLE_KEY = 'service-key';
22+
mocks.authGetUser.mockResolvedValue({ data: { user: { id: 'auth-user-id' } }, error: null });
23+
mocks.from.mockReturnValue({
24+
select: vi.fn(() => ({
25+
not: vi.fn(() => Promise.resolve({ data: [{ user_id: 'user-1', public_key: 'key-1' }], error: null })),
26+
})),
27+
});
28+
});
29+
30+
it('preserves equals padding in base64 auth cookies', async () => {
31+
const { GET } = await import('./route.js');
32+
const response = await GET(
33+
new Request('https://qrypt.chat/api/crypto/public-keys/all', {
34+
headers: {
35+
cookie: 'sb-xydzwxwsbgmznthiiscl-auth-token=base64-eyJhY2Nlc3NfdG9rZW4iOiJhYmMiLCJwYWRkaW5nIjoieCJ9==',
36+
},
37+
}),
38+
);
39+
40+
expect(response.status).toBe(200);
41+
expect(await response.json()).toEqual([{ user_id: 'user-1', public_key: 'key-1' }]);
42+
expect(mocks.authGetUser).toHaveBeenCalledWith('abc');
43+
});
44+
});

‎src/app/api/crypto/public-keys/route.js‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ async function authenticateUser(request) {
3333
// Parse cookies to find auth token
3434
const cookies = Object.fromEntries(
3535
cookieHeader.split(/;\s*/).map(cookie => {
36-
const [name, value] = cookie.split('=');
36+
const separator = cookie.indexOf('=');
37+
const name = separator === -1 ? cookie : cookie.slice(0, separator);
38+
const value = separator === -1 ? '' : cookie.slice(separator + 1);
3739
return [name, decodeURIComponent(value)];
3840
})
3941
);

‎src/app/api/crypto/public-keys/route.test.js‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ function cookieValue(token) {
2525
return `base64-${Buffer.from(JSON.stringify({ access_token: token })).toString('base64')}`;
2626
}
2727

28+
function paddedCookieValue() {
29+
return 'base64-eyJhY2Nlc3NfdG9rZW4iOiJhYmMiLCJwYWRkaW5nIjoieCJ9==';
30+
}
31+
2832
function createUsersQuery() {
2933
let selected = '';
3034
const query = {
@@ -83,6 +87,20 @@ describe('public key cookie authentication', () => {
8387
expect(mocks.authGetUser).toHaveBeenCalledWith('access-token');
8488
});
8589

90+
it('preserves equals padding in base64 auth cookies', async () => {
91+
const { GET } = await import('./route.js');
92+
const response = await GET(
93+
new Request('https://qrypt.chat/api/crypto/public-keys?user_id=target-user-id', {
94+
headers: {
95+
cookie: `sb-xydzwxwsbgmznthiiscl-auth-token=${paddedCookieValue()}`
96+
}
97+
})
98+
);
99+
100+
expect(response.status).toBe(200);
101+
expect(mocks.authGetUser).toHaveBeenCalledWith('abc');
102+
});
103+
86104
it('rejects non-string public keys before upsert RPC work', async () => {
87105
const { PUT } = await import('./route.js');
88106
const response = await PUT(

0 commit comments

Comments
 (0)