Skip to content

Commit 79daee3

Browse files
committed
Handle cookie and OAuth error text without unsafe decoding
1 parent 6138066 commit 79daee3

4 files changed

Lines changed: 21 additions & 6 deletions

File tree

frontend/src/App.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,7 @@ const AppContent: React.FC = () => {
137137
const params = new URLSearchParams(location.search);
138138
const oauthError = params.get('oauth_error');
139139
if (oauthError) {
140-
const decodedError = decodeURIComponent(oauthError).replace(/\+/g, ' ');
141-
setGlobalError(decodedError);
140+
setGlobalError(oauthError);
142141
clearPendingSignInMethod();
143142
params.delete('oauth_error');
144143
const remainingSearch = params.toString();

frontend/src/modules/user/views/ManageProfile.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function ManageProfile({ user, onUpdate }: ManageProfileProps) {
3333
if (oauthError) {
3434
setStatusModal({
3535
title: 'Account Connection Failed',
36-
msg: decodeURIComponent(oauthError).replace(/\+/g, ' ')
36+
msg: oauthError
3737
});
3838
setSearchParams({});
3939
}

frontend/src/utils/api.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,15 @@ export const getCookie = (name: string): string | null => {
3737

3838
const cookies = document.cookie.split(';');
3939
for (const c of cookies) {
40-
const [key, val] = c.trim().split('=');
41-
if (key === name) {
42-
return decodeURIComponent(val);
40+
const cookie = c.trim();
41+
const separator = cookie.indexOf('=');
42+
if (separator >= 0 && cookie.slice(0, separator) === name) {
43+
try {
44+
return decodeURIComponent(cookie.slice(separator + 1));
45+
} catch {
46+
// A malformed cookie should trigger token refresh, not abort the request.
47+
return null;
48+
}
4349
}
4450
}
4551
return null;

frontend/tests/utils/api.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ describe('api utils', () => {
2525
expect(getCookie('missing')).toBeNull();
2626
});
2727

28+
it('preserves equals signs inside cookie values', () => {
29+
document.cookie = 'session=abc==; path=/';
30+
expect(getCookie('session')).toBe('abc==');
31+
});
32+
33+
it('treats malformed cookie encoding as a missing token', () => {
34+
document.cookie = 'XSRF-TOKEN=%E0%A4%A; path=/';
35+
expect(getCookie('XSRF-TOKEN')).toBeNull();
36+
});
37+
2838
it('adds the xsrf token header for mutating requests', async () => {
2939
document.cookie = 'XSRF-TOKEN=csrf-token; path=/';
3040
const handler = (api.interceptors.request as any).handlers[0].fulfilled;

0 commit comments

Comments
 (0)