Skip to content

Commit

Permalink
fix(adapter-nextjs): duplicate response Set-Cookie headers in pages r…
Browse files Browse the repository at this point in the history
…outer (#13765)

* fix(adapter-nextjs): duplicate response Set-Cookie headers in pages router

* chore: add unit tests
  • Loading branch information
HuiSF authored Sep 3, 2024
1 parent c1ba1a1 commit 3fedf63
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,41 @@ describe('createCookieStorageAdapterFromNextServerContext', () => {
'key3=;Expires=Thu, 01 Jan 1970 00:00:00 GMT',
]);
});

it('does not add duplicate cookies when the cookies are defined in the response Set-Cookie headers', () => {
const mockExistingSetCookieValues = [
'CognitoIdentityServiceProvider.1234.accessToken=1234;Path=/',
'CognitoIdentityServiceProvider.1234.refreshToken=1234;Path=/',
'CognitoIdentityServiceProvider.1234.identityId=;Expires=Thu, 01 Jan 1970 00:00:00 GMT',
];

const request = new IncomingMessage(new Socket());
const response = new ServerResponse(request);
const appendHeaderSpy = jest.spyOn(response, 'appendHeader');
const getHeaderSpy = jest.spyOn(response, 'getHeader');

Object.defineProperty(request, 'cookies', {
get() {
return {};
},
});

getHeaderSpy.mockReturnValue(mockExistingSetCookieValues);

const result = createCookieStorageAdapterFromNextServerContext({
request: request as any,
response,
});

result.set('CognitoIdentityServiceProvider.1234.accessToken', '5678');
expect(appendHeaderSpy).not.toHaveBeenCalled();

result.set('CognitoIdentityServiceProvider.1234.refreshToken', '5678');
expect(appendHeaderSpy).not.toHaveBeenCalled();

result.delete('CognitoIdentityServiceProvider.1234.identityId');
expect(appendHeaderSpy).not.toHaveBeenCalled();
});
});

it('should throw error when no cookie storage adapter is created from the context', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,20 +171,44 @@ const createCookieStorageAdapterFromGetServerSidePropsContext = (
return allCookies;
},
set(name, value, options) {
const encodedName = ensureEncodedForJSCookie(name);

const existingValues = getExistingSetCookieValues(
response.getHeader('Set-Cookie'),
);

// if the cookies have already been set, we don't need to set them again.
if (
existingValues.findIndex(
cookieValue =>
cookieValue.startsWith(`${encodedName}=`) &&
!cookieValue.startsWith(`${encodedName}=;`),
) > -1
) {
return;
}

response.appendHeader(
'Set-Cookie',
`${ensureEncodedForJSCookie(name)}=${value};${
`${encodedName}=${value};${
options ? serializeSetCookieOptions(options) : ''
}`,
);
},
delete(name) {
response.appendHeader(
'Set-Cookie',
`${ensureEncodedForJSCookie(
name,
)}=;Expires=${DATE_IN_THE_PAST.toUTCString()}`,
const encodedName = ensureEncodedForJSCookie(name);
const setCookieValue = `${encodedName}=;Expires=${DATE_IN_THE_PAST.toUTCString()}`;
const existingValues = getExistingSetCookieValues(
response.getHeader('Set-Cookie'),
);

// if the value for cookie deletion is already in the Set-Cookie header, we
// don't need to add the deletion value again.
if (existingValues.includes(setCookieValue)) {
return;
}

response.appendHeader('Set-Cookie', setCookieValue);
},
};
};
Expand Down Expand Up @@ -250,3 +274,8 @@ const serializeSetCookieOptions = (
// we are not using those chars in the auth keys.
const ensureEncodedForJSCookie = (name: string): string =>
encodeURIComponent(name).replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent);

const getExistingSetCookieValues = (
values: number | string | string[] | undefined,
): string[] =>
values === undefined ? [] : Array.isArray(values) ? values : [String(values)];

0 comments on commit 3fedf63

Please sign in to comment.