Fix support for cookies bigger than 4KB in size (i.e. refresh tokens).
Signed-off-by: Dominik Bargowski <dominik.bargowski@allegro.com>
This commit is contained in:
@@ -14,12 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Request, Response } from 'express';
|
||||
import { CookieOptions, Request, Response } from 'express';
|
||||
import { CookieConfigurer } from '../types';
|
||||
|
||||
const THOUSAND_DAYS_MS = 1000 * 24 * 60 * 60 * 1000;
|
||||
const TEN_MINUTES_MS = 600 * 1000;
|
||||
|
||||
const MAX_COOKIE_SIZE_CHARACTERS = 4000;
|
||||
|
||||
const defaultCookieConfigurer: CookieConfigurer = ({
|
||||
callbackUrl,
|
||||
providerId,
|
||||
@@ -85,50 +87,126 @@ export class OAuthCookieManager {
|
||||
};
|
||||
}
|
||||
|
||||
setNonce(res: Response, nonce: string, origin?: string) {
|
||||
res.cookie(this.nonceCookie, nonce, {
|
||||
setNonce(res: Response, nonce: string, origin?: string): void {
|
||||
this.setCookie(res, this.nonceCookie, nonce, {
|
||||
maxAge: TEN_MINUTES_MS,
|
||||
...this.getConfig(origin, '/handler'),
|
||||
});
|
||||
}
|
||||
|
||||
setRefreshToken(res: Response, refreshToken: string, origin?: string) {
|
||||
res.cookie(this.refreshTokenCookie, refreshToken, {
|
||||
setRefreshToken(res: Response, refreshToken: string, origin?: string): void {
|
||||
this.setCookie(res, this.refreshTokenCookie, refreshToken, {
|
||||
maxAge: THOUSAND_DAYS_MS,
|
||||
...this.getConfig(origin),
|
||||
});
|
||||
}
|
||||
|
||||
removeRefreshToken(res: Response, origin?: string) {
|
||||
res.cookie(this.refreshTokenCookie, '', {
|
||||
maxAge: 0,
|
||||
...this.getConfig(origin),
|
||||
});
|
||||
removeRefreshToken(res: Response, origin?: string): void {
|
||||
this.removeCookie(res, this.refreshTokenCookie, origin);
|
||||
}
|
||||
|
||||
removeGrantedScopes(res: Response, origin?: string) {
|
||||
res.cookie(this.grantedScopeCookie, '', {
|
||||
maxAge: 0,
|
||||
...this.getConfig(origin),
|
||||
});
|
||||
removeGrantedScopes(res: Response, origin?: string): void {
|
||||
this.removeCookie(res, this.grantedScopeCookie, origin);
|
||||
}
|
||||
|
||||
setGrantedScopes(res: Response, scope: string, origin?: string) {
|
||||
res.cookie(this.grantedScopeCookie, scope, {
|
||||
setGrantedScopes(res: Response, scope: string, origin?: string): void {
|
||||
this.setCookie(res, this.grantedScopeCookie, scope, {
|
||||
maxAge: THOUSAND_DAYS_MS,
|
||||
...this.getConfig(origin),
|
||||
});
|
||||
}
|
||||
|
||||
getNonce(req: Request): string | undefined {
|
||||
return req.cookies[this.nonceCookie];
|
||||
return this.getCookie(req, this.nonceCookie);
|
||||
}
|
||||
|
||||
getRefreshToken(req: Request): string | undefined {
|
||||
return req.cookies[this.refreshTokenCookie];
|
||||
return this.getCookie(req, this.refreshTokenCookie);
|
||||
}
|
||||
|
||||
getGrantedScopes(req: Request): string | undefined {
|
||||
return req.cookies[this.grantedScopeCookie];
|
||||
return this.getCookie(req, this.grantedScopeCookie);
|
||||
}
|
||||
|
||||
private setCookie(
|
||||
res: Response,
|
||||
name: string,
|
||||
val: string,
|
||||
options: CookieOptions,
|
||||
): Response {
|
||||
if (val.length > MAX_COOKIE_SIZE_CHARACTERS) {
|
||||
const chunked = this.splitCookieToChunks(val, MAX_COOKIE_SIZE_CHARACTERS);
|
||||
let output = res;
|
||||
chunked.forEach((value, chunkNumber) => {
|
||||
output = output.cookie(
|
||||
OAuthCookieManager.getCookieChunkName(name, chunkNumber),
|
||||
value,
|
||||
options,
|
||||
);
|
||||
});
|
||||
return output;
|
||||
}
|
||||
return res.cookie(name, val, options);
|
||||
}
|
||||
|
||||
private getCookie(req: Request, name: string): string | undefined {
|
||||
const isChunked =
|
||||
!!req.cookies[OAuthCookieManager.getCookieChunkName(name, 0)];
|
||||
if (isChunked) {
|
||||
const chunks: string[] = [];
|
||||
let chunkNumber = 0;
|
||||
let chunk =
|
||||
req.cookies[OAuthCookieManager.getCookieChunkName(name, chunkNumber)];
|
||||
while (chunk) {
|
||||
chunks.push(chunk);
|
||||
chunkNumber++;
|
||||
chunk =
|
||||
req.cookies[OAuthCookieManager.getCookieChunkName(name, chunkNumber)];
|
||||
}
|
||||
return chunks.join('');
|
||||
}
|
||||
return req.cookies[name];
|
||||
}
|
||||
|
||||
private removeCookie(res: Response, name: string, origin?: string): Response {
|
||||
const req = res.req;
|
||||
const options = {
|
||||
maxAge: 0,
|
||||
...this.getConfig(origin),
|
||||
};
|
||||
const isChunked =
|
||||
!!req.cookies[OAuthCookieManager.getCookieChunkName(name, 0)];
|
||||
if (isChunked) {
|
||||
const oldFormatExists = !!req.cookies[name];
|
||||
let output: Response = oldFormatExists
|
||||
? res.cookie(name, '', options)
|
||||
: res;
|
||||
for (let chunkNumber = 0; ; chunkNumber++) {
|
||||
const key = OAuthCookieManager.getCookieChunkName(name, chunkNumber);
|
||||
const exists = !!req.cookies[key];
|
||||
if (!exists) {
|
||||
break;
|
||||
}
|
||||
output = output.cookie(key, '', options);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
return res.cookie(name, '', options);
|
||||
}
|
||||
|
||||
private splitCookieToChunks(val: string, chunkSize: number): string[] {
|
||||
const numChunks = Math.ceil(val.length / chunkSize);
|
||||
const chunks: string[] = Array<string>(numChunks);
|
||||
|
||||
let offset: number = 0;
|
||||
for (let i = 0; i < numChunks; i++) {
|
||||
chunks[i] = val.substring(offset, offset + chunkSize);
|
||||
offset += chunkSize;
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
private static getCookieChunkName(name: string, chunkIndex: number): string {
|
||||
return `${name}-${chunkIndex}`;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user