Skip to content

Commit c3aca31

Browse files
authored
fix(auth): preserve padded cookie values across auth routes (#225)
1 parent bfd8053 commit c3aca31

6 files changed

Lines changed: 70 additions & 3 deletions

File tree

src/app/api/auth/backup-pin/route.js

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

5050
const cookies = Object.fromEntries(
5151
cookieHeader.split(/;\s*/).map(cookie => {
52-
const [name, value] = cookie.split('=');
52+
const separator = cookie.indexOf('=');
53+
const name = separator === -1 ? cookie : cookie.slice(0, separator);
54+
const value = separator === -1 ? '' : cookie.slice(separator + 1);
5355
return [name, decodeURIComponent(value)];
5456
})
5557
);

src/app/api/auth/backup-pin/route.test.js

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

26+
function paddedCookieValue() {
27+
return 'base64-eyJhY2Nlc3NfdG9rZW4iOiJhYmMiLCJwYWRkaW5nIjoieCJ9==';
28+
}
29+
2630
function createUsersQuery() {
2731
const query = {
2832
select: vi.fn(() => query),
@@ -70,6 +74,20 @@ describe('backup PIN cookie authentication', () => {
7074
expect(mocks.authGetUser).toHaveBeenCalledWith('access-token');
7175
});
7276

77+
it('preserves equals padding in base64 auth cookies', async () => {
78+
const { GET } = await import('./route.js');
79+
const response = await GET(
80+
new Request('https://qrypt.chat/api/auth/backup-pin', {
81+
headers: {
82+
cookie: `sb-xydzwxwsbgmznthiiscl-auth-token=${paddedCookieValue()}`
83+
}
84+
})
85+
);
86+
87+
expect(response.status).toBe(200);
88+
expect(mocks.authGetUser).toHaveBeenCalledWith('abc');
89+
});
90+
7391
it('normalizes bearer scheme casing and extra spaces', async () => {
7492
const { GET } = await import('./route.js');
7593
const response = await GET(

src/app/api/auth/key-backup/route.js

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

5252
const cookies = Object.fromEntries(
5353
cookieHeader.split(/;\s*/).map(cookie => {
54-
const [name, value] = cookie.split('=');
54+
const separator = cookie.indexOf('=');
55+
const name = separator === -1 ? cookie : cookie.slice(0, separator);
56+
const value = separator === -1 ? '' : cookie.slice(separator + 1);
5557
return [name, decodeURIComponent(value)];
5658
})
5759
);

src/app/api/auth/key-backup/route.test.js

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

26+
function paddedCookieValue() {
27+
return 'base64-eyJhY2Nlc3NfdG9rZW4iOiJhYmMiLCJwYWRkaW5nIjoieCJ9==';
28+
}
29+
2630
function createBackupQuery() {
2731
const query = {
2832
select: vi.fn(() => query),
@@ -98,6 +102,20 @@ describe('key backup cookie authentication', () => {
98102
expect(mocks.authGetUser).toHaveBeenCalledWith('access-token');
99103
});
100104

105+
it('preserves equals padding in base64 auth cookies', async () => {
106+
const { GET } = await import('./route.js');
107+
const response = await GET(
108+
new Request('https://qrypt.chat/api/auth/key-backup', {
109+
headers: {
110+
cookie: `sb-xydzwxwsbgmznthiiscl-auth-token=${paddedCookieValue()}`
111+
}
112+
})
113+
);
114+
115+
expect(response.status).toBe(200);
116+
expect(mocks.authGetUser).toHaveBeenCalledWith('abc');
117+
});
118+
101119
it('normalizes bearer scheme casing and extra spaces', async () => {
102120
const { GET } = await import('./route.js');
103121
const response = await GET(

src/app/api/chat/conversations/[id]/participants/route.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ async function authenticateUser(request) {
3737
// Parse cookies to find auth token
3838
const cookies = Object.fromEntries(
3939
cookieHeader.split(/;\s*/).map(cookie => {
40-
const [name, value] = cookie.split('=');
40+
const separator = cookie.indexOf('=');
41+
const name = separator === -1 ? cookie : cookie.slice(0, separator);
42+
const value = separator === -1 ? '' : cookie.slice(separator + 1);
4143
return [name, decodeURIComponent(value)];
4244
})
4345
);

src/app/api/chat/conversations/[id]/participants/route.test.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ vi.mock('@/lib/supabase/service-role.js', () => ({
2121
}))
2222
}));
2323

24+
function paddedCookieValue() {
25+
return 'base64-eyJhY2Nlc3NfdG9rZW4iOiJhYmMiLCJwYWRkaW5nIjoieCJ9==';
26+
}
27+
2428
function createUsersQuery() {
2529
const query = {
2630
select: vi.fn(() => query),
@@ -110,6 +114,27 @@ describe('GET /api/chat/conversations/[id]/participants', () => {
110114
expect(mocks.participantEq).toHaveBeenCalledWith('conversation_id', 'conversation-1');
111115
});
112116

117+
it('preserves equals padding in base64 auth cookies', async () => {
118+
mocks.serviceFrom.mockImplementation((table) => {
119+
if (table === 'users') return createUsersQuery();
120+
if (table === 'conversation_participants') {
121+
return createParticipantCheckQuery();
122+
}
123+
throw new Error(`Unexpected table: ${table}`);
124+
});
125+
126+
const { GET } = await import('./route.js');
127+
const response = await GET(
128+
new Request('https://qrypt.chat/api/chat/conversations/conversation-1/participants', {
129+
headers: { cookie: `sb-xydzwxwsbgmznthiiscl-auth-token=${paddedCookieValue()}` }
130+
}),
131+
{ params: Promise.resolve({ id: 'conversation-1' }) }
132+
);
133+
134+
expect(response.status).toBe(200);
135+
expect(mocks.authGetUser).toHaveBeenCalledWith('abc');
136+
});
137+
113138
it('rejects missing async route params after authentication', async () => {
114139
mocks.serviceFrom.mockImplementation((table) => {
115140
if (table === 'users') return createUsersQuery();

0 commit comments

Comments
 (0)