From ed5537a3ac0f71afe9c78e3895e266444fdab607 Mon Sep 17 00:00:00 2001 From: Nicolas Froidure Date: Mon, 5 Oct 2020 11:46:03 +0200 Subject: [PATCH] fix(@whook/aws-lambda): fix lambdas build for multiple headers --- .../src/wrappers/awsHTTPLambda.ts | 30 +++++++++++++++-- packages/whook-cors/src/index.ts | 20 ++++++++--- packages/whook-http-router/src/index.ts | 10 +++--- packages/whook-http-transaction/src/index.ts | 2 +- .../src/services/obfuscator.test.ts | 13 ++++++++ .../src/services/obfuscator.ts | 33 ++++++++++++++++--- 6 files changed, 89 insertions(+), 19 deletions(-) diff --git a/packages/whook-aws-lambda/src/wrappers/awsHTTPLambda.ts b/packages/whook-aws-lambda/src/wrappers/awsHTTPLambda.ts index 2d60e0d2..a899e2c9 100644 --- a/packages/whook-aws-lambda/src/wrappers/awsHTTPLambda.ts +++ b/packages/whook-aws-lambda/src/wrappers/awsHTTPLambda.ts @@ -484,6 +484,7 @@ async function awsRequestEventToRequest(event: any): Promise { type AWSResponseEvent = { statusCode: number; headers: { [name: string]: string }; + multiValueHeaders: { [name: string]: string[] }; body?: string; isBase64Encoded?: boolean; }; @@ -493,7 +494,28 @@ async function responseToAWSResponseEvent( ): Promise { const amazonResponse: AWSResponseEvent = { statusCode: response.status, - headers: response.headers, + headers: Object.keys(response.headers || {}).reduce( + (stringHeaders, name) => ({ + ...stringHeaders, + ...(typeof response.headers[name] === 'string' + ? { + [name]: response.headers[name], + } + : {}), + }), + {}, + ), + multiValueHeaders: Object.keys(response.headers || {}).reduce( + (stringHeaders, name) => ({ + ...stringHeaders, + ...((response.headers[name] as any) instanceof Array + ? { + [name]: response.headers[name], + } + : {}), + }), + {}, + ), }; if (response.body) { @@ -511,8 +533,10 @@ async function responseToAWSResponseEvent( }); }); if ( - response.headers['content-type'].startsWith('image/') || - response.headers['content-type'].startsWith('application/pdf') + (response.headers['content-type'] as 'string').startsWith('image/') || + (response.headers['content-type'] as 'string').startsWith( + 'application/pdf', + ) ) { amazonResponse.body = buf.toString('base64'); amazonResponse.isBase64Encoded = true; diff --git a/packages/whook-cors/src/index.ts b/packages/whook-cors/src/index.ts index dfc4b340..ca75abb3 100644 --- a/packages/whook-cors/src/index.ts +++ b/packages/whook-cors/src/index.ts @@ -226,11 +226,21 @@ export async function augmentAPIWithCORS( export { initOptionsWithCORS }; -function mergeVaryHeaders(baseHeader: string, addedValue: string): string { - const baseHeaderValues = baseHeader - .split(',') - .filter(identity) - .map((v) => v.trim().toLowerCase()); +function mergeVaryHeaders( + baseHeader: string | string[], + addedValue: string, +): string { + const baseHeaderValues = (baseHeader instanceof Array + ? baseHeader + : [baseHeader] + ) + .map((value) => + value + .split(',') + .filter(identity) + .map((v) => v.trim().toLowerCase()), + ) + .reduce((allValues, values) => [...allValues, ...values], []); if (baseHeaderValues.includes('*')) { return '*'; diff --git a/packages/whook-http-router/src/index.ts b/packages/whook-http-router/src/index.ts index 7fd59664..9c9d749c 100644 --- a/packages/whook-http-router/src/index.ts +++ b/packages/whook-http-router/src/index.ts @@ -408,21 +408,21 @@ async function initHTTPRouter({ operation.responses[response.status] && operation.responses[response.status].content && operation.responses[response.status].content[ - response.headers['content-type'] + response.headers['content-type'] as string ] && operation.responses[response.status].content[ - response.headers['content-type'] + response.headers['content-type'] as string ].schema && (operation.responses[response.status].content[ - response.headers['content-type'] + response.headers['content-type'] as string ].schema.type !== 'string' || operation.responses[response.status].content[ - response.headers['content-type'] + response.headers['content-type'] as string ].schema.format !== 'binary'); if ( responseHasSchema && - !STRINGIFYERS[response.headers['content-type']] + !STRINGIFYERS[response.headers['content-type'] as string] ) { throw new HTTPError( 500, diff --git a/packages/whook-http-transaction/src/index.ts b/packages/whook-http-transaction/src/index.ts index e640c82a..1ee536a8 100644 --- a/packages/whook-http-transaction/src/index.ts +++ b/packages/whook-http-transaction/src/index.ts @@ -40,7 +40,7 @@ export type WhookRequest = { export type WhookResponse< S = number, H = { - [name: string]: string; + [name: string]: string | string[]; }, B = any > = { diff --git a/packages/whook-http-transaction/src/services/obfuscator.test.ts b/packages/whook-http-transaction/src/services/obfuscator.test.ts index 21e69f8d..d3ddc4b5 100644 --- a/packages/whook-http-transaction/src/services/obfuscator.test.ts +++ b/packages/whook-http-transaction/src/services/obfuscator.test.ts @@ -86,6 +86,19 @@ describe('Obfuscator Service', () => { } `); }); + + it('should work with set-cookie sensible headers', () => { + expect( + obfuscator.obfuscateSensibleHeaders({ + 'Set-Cookie': + 'refresh_token=XdJ2%2BXmU0GLqB9bOfFOKWzg4ixn%2BfmJlWEHb%2FPXFrN4MHmteVfAC1WNMVCbSwT9M%2FbkJmIgdFXvDvEMDvZw0elIRvClwjaLVXgQqGSbtbkMTSv%2BtvvdnqIrfYRSuwhX2v5YqvUJsKk1RmxRekOyq5gthUutdT8iHc0PMdKiojm0UTdEq3Jzefc70BhdXnjDH9uQJMb5JfuzZKbx994yk9NdQMnjYUC4Fo6i0SFqPtLGi4mxnOp8Rug5Zm11tdej9sA4UXPsITx%2BhKkdVEm0J%2BqXVeCD7kD7Ybuw%2Bno%2FcxNwSCYy7WFPG6VNTAWPP9jctqWYCICiDjYNgFM0IoikCNQ%3D%3D; Domain=app.diagrams-technologies.com; Path=/v0/auth; HttpOnly; Secure; SameSite=Strict', + }), + ).toMatchInlineSnapshot(` + Object { + "Set-Cookie": "refresh_token=XdJ...%3D; Domain=app.diagrams-technologies.com; Path=/v0/auth; HttpOnly; Secure; SameSite=Strict", + } + `); + }); }); describe('obfuscateSensibleProps()', () => { diff --git a/packages/whook-http-transaction/src/services/obfuscator.ts b/packages/whook-http-transaction/src/services/obfuscator.ts index fbdc6430..0e4d08f4 100644 --- a/packages/whook-http-transaction/src/services/obfuscator.ts +++ b/packages/whook-http-transaction/src/services/obfuscator.ts @@ -38,6 +38,11 @@ const DEFAULT_SENSIBLE_HEADERS: SensibleValueDescriptor[] = [ clearIndices: [0], }, { name: 'cookie', pattern: /^(.*)$/i, clearIndices: [] }, + { + name: 'set-cookie', + pattern: /^([^=]+=)([^;]*)(.*)$/i, + clearIndices: [0, 2], + }, ]; const DEFAULT_SENSIBLE_PROPS: SensibleValueDescriptor[] = [ { @@ -121,7 +126,23 @@ async function initObfuscator({ ); } - function selectivelyObfuscate(pattern, clearIndices, value) { + function selectivelyObfuscateAll( + pattern: RegExp, + clearIndices: number[], + values: string | string[], + ) { + return values instanceof Array + ? values.map((value) => + selectivelyObfuscate(pattern, clearIndices, value), + ) + : selectivelyObfuscate(pattern, clearIndices, values); + } + + function selectivelyObfuscate( + pattern: RegExp, + clearIndices: number[], + value: string, + ) { // SECURITY: Here, we first test the pattern to ensure // the selective obfuscation will work, if not, we obfuscate // the whole value to default to security @@ -139,7 +160,9 @@ async function initObfuscator({ : obfuscate(value); } - function obfuscateSensibleHeaders(headers: { [name: string]: string }) { + function obfuscateSensibleHeaders(headers: { + [name: string]: string | string[]; + }) { return Object.keys(headers).reduce((finalHeaders, headerName) => { const sensibleHeader = SENSIBLE_HEADERS.find( (sensibleHeader) => @@ -148,7 +171,7 @@ async function initObfuscator({ return { ...finalHeaders, [headerName]: sensibleHeader - ? selectivelyObfuscate( + ? selectivelyObfuscateAll( sensibleHeader.pattern, sensibleHeader.clearIndices, headers[headerName], @@ -158,7 +181,7 @@ async function initObfuscator({ }, {}); } - function obfuscateSensibleProps(propValue, propName = '_') { + function obfuscateSensibleProps(propValue: any, propName = '_') { if (propValue instanceof Array) { return propValue.map((value) => obfuscateSensibleProps(value, propName)); } else if (typeof propValue === 'object' && propValue !== null) { @@ -183,7 +206,7 @@ async function initObfuscator({ ? selectivelyObfuscate( sensibleProp.pattern, sensibleProp.clearIndices, - propValue, + propValue.toString(), ) : propValue; } else if (null === propValue) {