From 4e54b40cdc2f4302b1f25d0d8044faa0b3a7e2d0 Mon Sep 17 00:00:00 2001 From: ericctsf Date: Tue, 1 Feb 2022 15:36:41 +0000 Subject: [PATCH 1/2] Authorizor over ride header Added a http header SLS_OFFLINE_AUTHORISER_OVERRIDE which behaves like the AUTHORIZER environment variable but allows for testing many handler behaviours in a single serverless offline run by changing the authorizer value on each request --- README.md | 17 ++- package.json | 3 +- .../LambdaProxyIntegrationEvent.js | 16 +++ .../LambdaProxyIntegrationEventV2.js | 16 +++ .../override-authorizer/handler.js | 8 ++ .../override-authorizer.test.js | 104 ++++++++++++++++++ .../override-authorizer/serverless.yml | 33 ++++++ 7 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 tests/integration/override-authorizer/handler.js create mode 100644 tests/integration/override-authorizer/override-authorizer.test.js create mode 100644 tests/integration/override-authorizer/serverless.yml diff --git a/README.md b/README.md index 0aba3857b..aafd99acf 100644 --- a/README.md +++ b/README.md @@ -364,10 +364,11 @@ only enabled with the `--ignoreJWTSignature` flag. Make sure to only set this fl You are able to use some custom headers in your request to gain more control over the requestContext object. -| Header | Event key | -| ------------------------------- | ----------------------------------------------------------- | -| cognito-identity-id | event.requestContext.identity.cognitoIdentityId | -| cognito-authentication-provider | event.requestContext.identity.cognitoAuthenticationProvider | +| Header | Event key | Example | +| ------------------------------- | ----------------------------------------------------------- | ------- | +| cognito-identity-id | event.requestContext.identity.cognitoIdentityId | | +| cognito-authentication-provider | event.requestContext.identity.cognitoAuthenticationProvider | | +| SLS_OFFLINE_AUTHORIZER_OVERRIDE | event.requestContext.authorizer | { "iam": {"cognitoUser": { "amr": ["unauthenticated"], "identityId": "abc123" }}} | By doing this you are now able to change those values using a custom header. This can help you with easier authentication or retrieving the userId from a `cognitoAuthenticationProvider` value. @@ -744,6 +745,10 @@ We try to follow [Airbnb's JavaScript Style Guide](https://github.com/airbnb/jav | :------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------: | | [lteacher](https://github.com/lteacher) | [martinmicunda](https://github.com/martinmicunda) | [nori3tsu](https://github.com/nori3tsu) | [ppasmanik](https://github.com/ppasmanik) | [ryanzyy](https://github.com/ryanzyy) | -| [m0ppers](https://github.com/m0ppers) | [footballencarta](https://github.com/footballencarta) | [bryanvaz](https://github.com/bryanvaz) | [njyjn](https://github.com/njyjn) | +| [m0ppers](https://github.com/m0ppers) | [footballencarta](https://github.com/footballencarta) | [bryanvaz](https://github.com/bryanvaz) | [njyjn](https://github.com/njyjn) | | | :---------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------: | ------------------------------------- | -| [m0ppers](https://github.com/m0ppers) | [footballencarta](https://github.com/footballencarta) | [bryanvaz](https://github.com/bryanvaz) | [njyjn](https://github.com/njyjn) | [kdybicz](https://github.com/kdybicz) | +| [m0ppers](https://github.com/m0ppers) | [footballencarta](https://github.com/footballencarta) | [bryanvaz](https://github.com/bryanvaz) | [njyjn](https://github.com/njyjn) | [kdybicz](https://github.com/kdybicz) | + +| [ericctsf](https://github.com/ericctsf) | | | | | +| :------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------: | +| [ericctsf](https://github.com/erictsf) | | | | | diff --git a/package.json b/package.json index 5d9bda79d..fa8cc25c4 100644 --- a/package.json +++ b/package.json @@ -156,7 +156,8 @@ "Dima Krutolianov (https://github.com/dimadk24)", "Bryan Vaz (https://github.com/bryanvaz)", "Justin Ng (https://github.com/njyjn)", - "Fernando Alvarez (https://github.com/jefer590)" + "Fernando Alvarez (https://github.com/jefer590)", + "Eric Carter (https://github.com/ericctsf)" ], "husky": { "hooks": { diff --git a/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js b/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js index b798b9e39..30e4f5f59 100644 --- a/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js +++ b/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js @@ -75,6 +75,22 @@ export default class LambdaProxyIntegrationEvent { // NOTE FIXME request.raw.req.rawHeaders can only be null for testing (hapi shot inject()) const headers = parseHeaders(rawHeaders || []) || {} + if (headers.SLS_OFFLINE_AUTHORIZER_OVERRIDE) { + try { + authAuthorizer = parse(headers.SLS_OFFLINE_AUTHORIZER_OVERRIDE) + } catch (error) { + if (this.log) { + this.log.error( + 'Could not parse header SLS_OFFLINE_AUTHORIZER_OVERRIDE, make sure it is correct JSON', + ) + } else { + console.error( + 'Serverless-offline: Could not parse header SLS_OFFLINE_AUTHORIZER_OVERRIDE make sure it is correct JSON.', + ) + } + } + } + if (body) { if (typeof body !== 'string') { // this.#request.payload is NOT the same as the rawPayload diff --git a/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js b/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js index 2801866a6..803f8c20f 100644 --- a/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +++ b/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js @@ -63,6 +63,22 @@ export default class LambdaProxyIntegrationEventV2 { // NOTE FIXME request.raw.req.rawHeaders can only be null for testing (hapi shot inject()) const headers = parseHeaders(rawHeaders || []) || {} + if (headers.SLS_OFFLINE_AUTHORIZER_OVERRIDE) { + try { + authAuthorizer = parse(headers.SLS_OFFLINE_AUTHORIZER_OVERRIDE) + } catch (error) { + if (this.log) { + this.log.error( + 'Could not parse header SLS_OFFLINE_AUTHORIZER_OVERRIDE, make sure it is correct JSON', + ) + } else { + console.error( + 'Serverless-offline: Could not parse header SLS_OFFLINE_AUTHORIZER_OVERRIDE make sure it is correct JSON.', + ) + } + } + } + if (body) { if (typeof body !== 'string') { // this.#request.payload is NOT the same as the rawPayload diff --git a/tests/integration/override-authorizer/handler.js b/tests/integration/override-authorizer/handler.js new file mode 100644 index 000000000..223b597f5 --- /dev/null +++ b/tests/integration/override-authorizer/handler.js @@ -0,0 +1,8 @@ +'use strict' + +exports.echo_authorizer = async function get(context) { + return { + body: JSON.stringify(context.requestContext.authorizer), + statusCode: 200, + } +} diff --git a/tests/integration/override-authorizer/override-authorizer.test.js b/tests/integration/override-authorizer/override-authorizer.test.js new file mode 100644 index 000000000..396cc4751 --- /dev/null +++ b/tests/integration/override-authorizer/override-authorizer.test.js @@ -0,0 +1,104 @@ +import { resolve } from 'path' +import fetch from 'node-fetch' +import { joinUrl, setup, teardown } from '../_testHelpers/index.js' + +jest.setTimeout(30000) + +const envAuthorizer = { + iam: { + cognitoUser: { + amr: ['unauthenticated'], + identityId: 'env_identity_id', + }, + }, +} + +const headerAuthorizer = { + iam: { + cognitoUser: { + amr: ['unauthenticated'], + identityId: 'header_identity_id', + }, + }, +} + +describe('override authorizer tests', () => { + // init + beforeAll(async () => { + process.env.AUTHORIZER = JSON.stringify(envAuthorizer) + await setup({ + servicePath: resolve(__dirname), + }) + }) + + // cleanup + afterAll(async () => { + process.env.AUTHORIZER = undefined + await teardown() + }) + + // + ;[ + { + description: 'HTTP API Falls back on env variable', + req: { + path: '/gateway_v2_http_api', + headers: {}, + }, + res: { + status: 200, + body: envAuthorizer, + }, + }, + { + description: 'REST API Falls back on env variable', + req: { + path: '/dev/gateway_v1_rest_api', + headers: {}, + }, + res: { + status: 200, + body: envAuthorizer, + }, + }, + { + description: 'HTTP API uses override header', + req: { + path: '/gateway_v2_http_api', + headers: { + SLS_OFFLINE_AUTHORIZER_OVERRIDE: JSON.stringify(headerAuthorizer), + }, + }, + res: { + status: 200, + body: headerAuthorizer, + }, + }, + { + description: 'HTTP API uses override header', + req: { + path: '/dev/gateway_v1_rest_api', + headers: { + SLS_OFFLINE_AUTHORIZER_OVERRIDE: JSON.stringify(headerAuthorizer), + }, + }, + res: { + status: 200, + body: headerAuthorizer, + }, + }, + ].forEach(({ description, req, res }) => { + test(description, async () => { + const url = joinUrl(TEST_BASE_URL, req.path) + const options = { + headers: req.headers, + } + + const response = await fetch(url, options) + expect(response.status).toEqual(res.status) + + const json = await response.json() + expect(json).toEqual(res.body) + }) + }) +}) diff --git a/tests/integration/override-authorizer/serverless.yml b/tests/integration/override-authorizer/serverless.yml new file mode 100644 index 000000000..3f4495ad8 --- /dev/null +++ b/tests/integration/override-authorizer/serverless.yml @@ -0,0 +1,33 @@ +service: jwt-authorizer + +plugins: + - ../../../ + +custom: + serverless-offline: + noAuth: true + +provider: + memorySize: 128 + name: aws + region: us-east-1 # default + runtime: nodejs12.x + stage: dev + versionFunctions: false + httpApi: + payload: '2.0' + +functions: + user: + events: + - http: + authorizer: + type: 'AWS_IAM' + method: get + path: gateway_v1_rest_api + - httpApi: + authorizer: + type: AWS_IAM + method: get + path: gateway_v2_http_api + handler: handler.echo_authorizer From ce0830cdb2fc541c4b2bc22e81ce433df479200c Mon Sep 17 00:00:00 2001 From: ericctsf Date: Thu, 3 Feb 2022 09:00:17 +0000 Subject: [PATCH 2/2] Change header style to kebab-case to match other headers SLS_OFFLINE_AUTHORIZER_OVERRIDE ==> sls-offline-authorizer-override and run prettier on readme --- README.md | 18 +++++++++--------- .../LambdaProxyIntegrationEvent.js | 8 ++++---- .../LambdaProxyIntegrationEventV2.js | 8 ++++---- .../override-authorizer.test.js | 4 ++-- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index aafd99acf..6b08a77c4 100644 --- a/README.md +++ b/README.md @@ -364,11 +364,11 @@ only enabled with the `--ignoreJWTSignature` flag. Make sure to only set this fl You are able to use some custom headers in your request to gain more control over the requestContext object. -| Header | Event key | Example | -| ------------------------------- | ----------------------------------------------------------- | ------- | -| cognito-identity-id | event.requestContext.identity.cognitoIdentityId | | -| cognito-authentication-provider | event.requestContext.identity.cognitoAuthenticationProvider | | -| SLS_OFFLINE_AUTHORIZER_OVERRIDE | event.requestContext.authorizer | { "iam": {"cognitoUser": { "amr": ["unauthenticated"], "identityId": "abc123" }}} | +| Header | Event key | Example | +| ------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------- | +| cognito-identity-id | event.requestContext.identity.cognitoIdentityId | | +| cognito-authentication-provider | event.requestContext.identity.cognitoAuthenticationProvider | | +| sls-offline-authorizer-override | event.requestContext.authorizer | { "iam": {"cognitoUser": { "amr": ["unauthenticated"], "identityId": "abc123" }}} | By doing this you are now able to change those values using a custom header. This can help you with easier authentication or retrieving the userId from a `cognitoAuthenticationProvider` value. @@ -745,10 +745,10 @@ We try to follow [Airbnb's JavaScript Style Guide](https://github.com/airbnb/jav | :------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------: | | [lteacher](https://github.com/lteacher) | [martinmicunda](https://github.com/martinmicunda) | [nori3tsu](https://github.com/nori3tsu) | [ppasmanik](https://github.com/ppasmanik) | [ryanzyy](https://github.com/ryanzyy) | -| [m0ppers](https://github.com/m0ppers) | [footballencarta](https://github.com/footballencarta) | [bryanvaz](https://github.com/bryanvaz) | [njyjn](https://github.com/njyjn) | | +| [m0ppers](https://github.com/m0ppers) | [footballencarta](https://github.com/footballencarta) | [bryanvaz](https://github.com/bryanvaz) | [njyjn](https://github.com/njyjn) | | | :---------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------: | ------------------------------------- | -| [m0ppers](https://github.com/m0ppers) | [footballencarta](https://github.com/footballencarta) | [bryanvaz](https://github.com/bryanvaz) | [njyjn](https://github.com/njyjn) | [kdybicz](https://github.com/kdybicz) | +| [m0ppers](https://github.com/m0ppers) | [footballencarta](https://github.com/footballencarta) | [bryanvaz](https://github.com/bryanvaz) | [njyjn](https://github.com/njyjn) | [kdybicz](https://github.com/kdybicz) | | [ericctsf](https://github.com/ericctsf) | | | | | -| :------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------: | -| [ericctsf](https://github.com/erictsf) | | | | | +| :------------------------------------------------------------------------------------------------------------------------------: | :-: | :-: | :-: | :-: | +| [ericctsf](https://github.com/erictsf) | | | | | diff --git a/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js b/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js index 30e4f5f59..30bb7c904 100644 --- a/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js +++ b/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js @@ -75,17 +75,17 @@ export default class LambdaProxyIntegrationEvent { // NOTE FIXME request.raw.req.rawHeaders can only be null for testing (hapi shot inject()) const headers = parseHeaders(rawHeaders || []) || {} - if (headers.SLS_OFFLINE_AUTHORIZER_OVERRIDE) { + if (headers['sls-offline-authorizer-override']) { try { - authAuthorizer = parse(headers.SLS_OFFLINE_AUTHORIZER_OVERRIDE) + authAuthorizer = parse(headers['sls-offline-authorizer-override']) } catch (error) { if (this.log) { this.log.error( - 'Could not parse header SLS_OFFLINE_AUTHORIZER_OVERRIDE, make sure it is correct JSON', + 'Could not parse header sls-offline-authorizer-override, make sure it is correct JSON', ) } else { console.error( - 'Serverless-offline: Could not parse header SLS_OFFLINE_AUTHORIZER_OVERRIDE make sure it is correct JSON.', + 'Serverless-offline: Could not parse header sls-offline-authorizer-override make sure it is correct JSON.', ) } } diff --git a/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js b/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js index 803f8c20f..fba0821fe 100644 --- a/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +++ b/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js @@ -63,17 +63,17 @@ export default class LambdaProxyIntegrationEventV2 { // NOTE FIXME request.raw.req.rawHeaders can only be null for testing (hapi shot inject()) const headers = parseHeaders(rawHeaders || []) || {} - if (headers.SLS_OFFLINE_AUTHORIZER_OVERRIDE) { + if (headers['sls-offline-authorizer-override']) { try { - authAuthorizer = parse(headers.SLS_OFFLINE_AUTHORIZER_OVERRIDE) + authAuthorizer = parse(headers['sls-offline-authorizer-override']) } catch (error) { if (this.log) { this.log.error( - 'Could not parse header SLS_OFFLINE_AUTHORIZER_OVERRIDE, make sure it is correct JSON', + 'Could not parse header sls-offline-authorizer-override, make sure it is correct JSON', ) } else { console.error( - 'Serverless-offline: Could not parse header SLS_OFFLINE_AUTHORIZER_OVERRIDE make sure it is correct JSON.', + 'Serverless-offline: Could not parse header sls-offline-authorizer-override make sure it is correct JSON.', ) } } diff --git a/tests/integration/override-authorizer/override-authorizer.test.js b/tests/integration/override-authorizer/override-authorizer.test.js index 396cc4751..ec749ab63 100644 --- a/tests/integration/override-authorizer/override-authorizer.test.js +++ b/tests/integration/override-authorizer/override-authorizer.test.js @@ -66,7 +66,7 @@ describe('override authorizer tests', () => { req: { path: '/gateway_v2_http_api', headers: { - SLS_OFFLINE_AUTHORIZER_OVERRIDE: JSON.stringify(headerAuthorizer), + 'sls-offline-authorizer-override': JSON.stringify(headerAuthorizer), }, }, res: { @@ -79,7 +79,7 @@ describe('override authorizer tests', () => { req: { path: '/dev/gateway_v1_rest_api', headers: { - SLS_OFFLINE_AUTHORIZER_OVERRIDE: JSON.stringify(headerAuthorizer), + 'sls-offline-authorizer-override': JSON.stringify(headerAuthorizer), }, }, res: {