From a105e122ed637c3d9773d24062fa3cc7b854e846 Mon Sep 17 00:00:00 2001 From: Jerzy Adamowski Date: Tue, 21 Feb 2023 08:26:43 +0100 Subject: [PATCH] +fix urlencoded support +test with more parsers +workaround: types on rawBody makes a lot of different errors - now replaced with any. +fix: rawBody received - conditional setup left for user +fix: documentation --- README.md | 66 ++++--- package-lock.json | 2 +- package.json | 2 +- src/awsSignature.ts | 3 +- src/rawBody.ts | 23 +-- src/test/awsVerify.spec.ts | 236 ++++--------------------- src/test/helpers/exampleData.ts | 2 +- src/test/helpers/multiParserRequest.ts | 28 +++ src/test/helpers/sendSignedRequest.ts | 19 +- 9 files changed, 135 insertions(+), 246 deletions(-) create mode 100644 src/test/helpers/multiParserRequest.ts diff --git a/README.md b/README.md index e685c4b..10409a1 100644 --- a/README.md +++ b/README.md @@ -20,18 +20,13 @@ npm install aws4-express There are prepared helpers to handle the original body with any changes, because if you change a single character, your request won't be valid anymore. -If you use express parsers like `express.raw()` or `express.json()` you can attach with handler `rawBodyFromVerify`. You can also write own stream parser or use our `rawBodyFromStream`. - -**Don't mix express.json(), express.raw() or custom parser together in single configuration** - example below show different way to achieve to pull out raw body. - +If you use express parsers like `express.raw()` or `express.json()` or `express.urlencoded` you can attach with handler `rawBodyFromVerify`. You can also write own stream parser or use our `rawBodyFromStream`. ```typescript import express from 'express'; import { awsVerify, rawBodyFromVerify, rawBodyFromStream } from 'aws4-express'; const app = express(); - app.use(express.urlencoded({ extended: true })); - // whenever you may need to get original body string and you case // when json parser u may use like this app.use( @@ -48,6 +43,13 @@ If you use express parsers like `express.raw()` or `express.json()` you can atta }) ); + // or when url encoded body u may use like this + app.use( + express.urlencoded({ + verify: rawBodyFromVerify, + }), + ); + // or events on when json parser u may use like this app.use(rawBodyFromStream); @@ -82,25 +84,25 @@ If you use express parsers like `express.raw()` or `express.json()` you can atta ## Supported headers: -- `authorization` - must have in proper format: **Authorization: AWS4-HMAC-SHA256 -Credential=< ACCESS_KEY>/< DATE>/< REGION>/< SERVICE>/< TYPE_REQUEST>, +- `authorization` - [required] must have in proper format: **Authorization: AWS4-HMAC-SHA256 +Credential=`ACCESS_KEY`/`DATE`/`REGION`/`SERVICE`/`TYPE_REQUEST`, SignedHeaders=< SIGNED_HEADERS>, -Signature=< SIGNATURE>** : - * **ACCESS_KEY** - any text without whitespaces and slashes (/) - Only have to do is handle distribution of access_key, secret_key and these keys have to be accessible on the server side. - * **DATE** - is part of X-AMZ-DATE: in format YYYYMMDD. - * **REGION** - any thing you need in this place or use something from [amz regions](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html). - * **SERVICE** - any thing you need in this place or 'execute-api' for sake of simplicity. - * **TYPE_REQUEST** - you can use your variations instead of standard 'aws4_request'. - * **SIGNED_HEADERS** - all signed headers - more headers mean harder to temper request. Required headers at this moment: *host:x-amz-date* - * **SIGNATURE** - calculated signature based on [Authenticating Requests (AWS Signature Version 4)](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html) -- `x-amz-date` - must have in request to valid request signature -- `x-amz-content-sha256` - [optional] you can attach this kind of header and remove all `bodyRaw` readers. - * You can also send **UNSIGNED-PAYLOAD** instead of sha-256 signature - this cloud speed up your bigger request, but signature will be same as long as headers remain same. +Signature=`SIGNATURE`** : + * `ACCESS_KEY` - any text without whitespaces and slashes (/) - Only have to do is handle distribution of access_key, secret_key and these keys have to be accessible on the server side. + * `DATE` - is part of X-AMZ-DATE: in format YYYYMMDD. + * `REGION` - any thing you need in this place or use something from [amz regions](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html). + * `SERVICE` - any thing you need in this place or 'execute-api' for sake of simplicity. + * `TYPE_REQUEST` - you can use your variations instead of standard 'aws4_request'. + * `SIGNED_HEADERS` - all signed headers - more headers mean harder to temper request. Required headers at this moment: *host:x-amz-date* + * `SIGNATURE` - calculated signature based on [Authenticating Requests (AWS Signature Version 4)](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html) +- `x-amz-date` - [required] must have in request to valid request signature +- `x-amz-content-sha256` - [optional] you can attach precalculated hash. When X-Amz-Content-Sha256 is sent we skip calculating hash from body. This way is less secure and recommended use at least with `X-Amz-Expires`. + * There can provide your validation whenever you want handle this header `onBeforeParse` or `onAfterParse`. + * You can also send `UNSIGNED-PAYLOAD` instead of sha-256 signature - this cloud speed up your bigger request, but signature will be same as long as headers remain same. * You can put your signature (most client don't include these headers) - should be calculated in this way: ``` crypto.createHash('sha256').update(data, 'utf8').digest('hex') ``` - * When you set this header in your request, `body parser` won't read `raw body` because this is redundant information. Literally, this means skip body calculation hash because I got body hash for you in this header. - `x-amz-expires` - [optional] - format: `YYYY-mm-ddTHH:MM:SS`. If you want valid your request for a period of time and don't want to reuse signature when time is up. Pull Requests are welcome. @@ -279,10 +281,10 @@ export interface AwsIncomingMessage { ### awsVerify: +#### Complete options configuration for `awsVerify`: ```typescript express.use(awsVerify({ - - secretKey: (message: AwsIncomingMessage, req: Request, res: Response, next: NextFunction) => Promise | string; + secretKey: (message: AwsIncomingMessage, req: Request, res: Response, next: NextFunction) => Promise | string | undefined; headers?: (headers: Dictionary) => Promise | Dictionary; enabled?: (req: Request) => Promise | boolean; onMissingHeaders?: (req: Request, res: Response, next: NextFunction) => Promise | void; @@ -304,4 +306,24 @@ express.use(awsVerify({ })) ``` +#### Default values for all optional configuration for `awsVerify`: +```typescript + { + enabled: () => true, + headers: (req) => req.headers, + onExpried: (res) => { + res.status(401).send('Request is expired'); + }, + onMissingHeaders: (res) => { + res.status(400).send('Required headers are missing'); + }, + onSignatureMismatch: (res) => { + res.status(401).send('The signature does not match'); + }, + onBeforeParse: () => true, + onAfterParse: () => true, + onSuccess: () => next() + } +``` + diff --git a/package-lock.json b/package-lock.json index ed18421..ff081cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "aws4-express", - "version": "0.5.2", + "version": "0.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5ec23b6..36cb5f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aws4-express", - "version": "0.5.2", + "version": "0.6.0", "description": "Express middleware handlers for validation AWS Signature V4", "main": "dist/index.js", "scripts": { diff --git a/src/awsSignature.ts b/src/awsSignature.ts index 413c05f..eacf3a8 100644 --- a/src/awsSignature.ts +++ b/src/awsSignature.ts @@ -1,7 +1,6 @@ import crypto, { BinaryLike, KeyObject } from 'crypto'; import querystring from 'querystring'; import { NextFunction, Request, Response } from 'express'; -import { RequestRB } from './rawBody'; import { Headers } from './headers'; export type Dictionary = Record; @@ -231,7 +230,7 @@ export class AwsSignature { const xAmzDate = req.header(Headers.XAmzDate); const xAmzExpires = Number(req.header(Headers.XAmzExpires)); const contentSha256 = req.header(Headers.XAmzContentSha256); - const bodyHash = contentSha256 || this.hash((req as RequestRB).rawBody ?? ''); + const bodyHash = contentSha256 || this.hash((req as any).rawBody ?? ''); const { path, query } = this.parsePath(req.url); const method = req.method; diff --git a/src/rawBody.ts b/src/rawBody.ts index 33aa4c2..80a0f27 100644 --- a/src/rawBody.ts +++ b/src/rawBody.ts @@ -1,31 +1,16 @@ -import { IncomingMessage, ServerResponse } from 'http'; import { NextFunction, Request, Response } from 'express'; -import { Headers } from './headers'; -export interface RequestRB extends Request { - rawBody: string; -} - -export interface IncomingMessageRB extends IncomingMessage { - rawBody: string; -} - -export const rawBodyFromVerify = (req: IncomingMessageRB, _res: ServerResponse, buf: Buffer, encoding: string) => { - if (buf && buf.length && !req.headers[Headers.XAmzContentSha256]) { - req.rawBody = buf.toString((encoding as BufferEncoding) || 'utf8') ?? ''; - } +export const rawBodyFromVerify = (req: any, _res: any, buf: Buffer, encoding: string) => { + req.rawBody = buf.toString((encoding as BufferEncoding) || 'utf8') ?? ''; }; export const rawBodyFromStream = (req: Request, _res: Response, next: NextFunction) => { - (req as RequestRB).rawBody = ''; + (req as any).rawBody = ''; - if (req.headers[Headers.XAmzContentSha256]) { - next(); - } req.setEncoding('utf8'); req.on('data', (chunk) => { - (req as RequestRB).rawBody += chunk; + (req as any).rawBody += chunk; }); req.on('end', () => { diff --git a/src/test/awsVerify.spec.ts b/src/test/awsVerify.spec.ts index 877406b..cb70f34 100644 --- a/src/test/awsVerify.spec.ts +++ b/src/test/awsVerify.spec.ts @@ -1,7 +1,13 @@ import sinon from 'sinon'; import { Headers } from '../headers'; -import { getExample, postExample, getAwsVerifyOptionsExample, getCredentialsExample } from './helpers/exampleData'; -import { sendSignedRequest } from './helpers/sendSignedRequest'; +import { + getExample, + postExample, + getAwsVerifyOptionsExample, + getCredentialsExample, + credentialsPairsExample, +} from './helpers/exampleData'; +import { multiParserRequest } from './helpers/multiParserRequest'; describe('awsVerify', () => { let sandbox: sinon.SinonSandbox; @@ -24,9 +30,7 @@ describe('awsVerify', () => { const optionsAwsSigned = getExample(); const credentials = getCredentialsExample(); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'raw' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'json' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'custom' }, credentials, 200); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 200); }); it('should validate POST request with aws4 signature', async () => { @@ -34,9 +38,7 @@ describe('awsVerify', () => { const optionsAwsSigned = postExample(); const credentials = getCredentialsExample(); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'raw' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'json' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'custom' }, credentials, 200); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 200); }); it('should validate PUT request with aws4 signature', async () => { @@ -44,9 +46,7 @@ describe('awsVerify', () => { const optionsAwsSigned = postExample(); const credentials = getCredentialsExample(); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'raw' }, { ...credentials }, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'json' }, { ...credentials }, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'custom' }, { ...credentials }, 200); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, { ...credentials }, 200); }); it('should validate DELETE request with aws4 signature', async () => { @@ -54,35 +54,20 @@ describe('awsVerify', () => { const optionsAwsSigned = postExample(); const credentials = getCredentialsExample(); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'raw' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'json' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'custom' }, credentials, 200); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 200); }); it('should validate GET request with aws4 signature with credentials available on server side', async () => { const optionsAwsVerify = getAwsVerifyOptionsExample(); const options = getExample(); - - await sendSignedRequest( - optionsAwsVerify, - options, - { parser: 'raw' }, - { accessKeyId: 'test1', secretAccessKey: 'test1' }, - 200, - ); - await sendSignedRequest( - optionsAwsVerify, - options, - { parser: 'json' }, - { accessKeyId: 'test2', secretAccessKey: 'test2' }, - 200, - ); - await sendSignedRequest( - optionsAwsVerify, - options, - { parser: 'custom' }, - { accessKeyId: 'test3', secretAccessKey: 'test3' }, - 200, + const keys = Object.keys(credentialsPairsExample).map((k) => ({ + accessKey: k, + secretKey: credentialsPairsExample[k], + })); + await Promise.all( + keys.map((k) => + multiParserRequest(optionsAwsVerify, options, { accessKeyId: k.accessKey, secretAccessKey: k.secretKey }, 200), + ), ); }); @@ -93,43 +78,17 @@ describe('awsVerify', () => { }); const credentials = getCredentialsExample({}); - await sendSignedRequest(optionsAwsVerify, options, { parser: 'raw' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, options, { parser: 'json' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, options, { parser: 'custom' }, credentials, 200); + await multiParserRequest(optionsAwsVerify, options, credentials, 200); }); it('should not validate request with aws4 signature incorrect credentials', async () => { const optionsAwsVerify = getAwsVerifyOptionsExample(); const options = getExample(); - await sendSignedRequest( - optionsAwsVerify, - options, - { parser: 'raw' }, - { accessKeyId: 'xyz', secretAccessKey: 'test' }, - 401, - ); - await sendSignedRequest( - optionsAwsVerify, - options, - { parser: 'json' }, - { accessKeyId: 'test', secretAccessKey: 'test1' }, - 401, - ); - await sendSignedRequest( - optionsAwsVerify, - options, - { parser: 'custom' }, - { accessKeyId: '1', secretAccessKey: '2' }, - 401, - ); - await sendSignedRequest( - optionsAwsVerify, - options, - { parser: 'custom' }, - { accessKeyId: '1', secretAccessKey: undefined }, - 401, - ); + await multiParserRequest(optionsAwsVerify, options, { accessKeyId: 'xyz', secretAccessKey: 'test' }, 401); + await multiParserRequest(optionsAwsVerify, options, { accessKeyId: 'test', secretAccessKey: 'test1' }, 401); + await multiParserRequest(optionsAwsVerify, options, { accessKeyId: '1', secretAccessKey: '2' }, 401); + await multiParserRequest(optionsAwsVerify, options, { accessKeyId: '1', secretAccessKey: undefined }, 401); }); it('should validate request with aws4 body unsigned with UNSIGNED-PAYLOAD', async () => { @@ -139,9 +98,7 @@ describe('awsVerify', () => { }); const credentials = getCredentialsExample(); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'raw' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'json' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'custom' }, credentials, 200); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 200); }); it('should validate request with aws4 with time greater than now', async () => { @@ -151,9 +108,7 @@ describe('awsVerify', () => { }); const credentials = getCredentialsExample(); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'raw' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'json' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'custom' }, credentials, 200); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 200); }); it('should validate request with aws4 with time equal now', async () => { @@ -163,9 +118,7 @@ describe('awsVerify', () => { }); const credentials = getCredentialsExample(); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'raw' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'json' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'custom' }, credentials, 200); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 200); }); it('should not validate request with aws4 with time lesser than now', async () => { @@ -175,9 +128,7 @@ describe('awsVerify', () => { }); const credentials = getCredentialsExample(); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'raw' }, credentials, 401); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'json' }, credentials, 401); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'custom' }, credentials, 401); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 401); }); it('should validate request with aws4 on ignore validation', async () => { @@ -187,9 +138,7 @@ describe('awsVerify', () => { const optionsAwsSigned = postExample(); const credentials = getCredentialsExample(); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'raw' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'json' }, credentials, 200); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'custom' }, credentials, 200); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 200); }); it('should validate request with aws4 when original host was replaced by routers inside your network', async () => { @@ -212,30 +161,7 @@ describe('awsVerify', () => { }, }; - await sendSignedRequest( - optionsAwsVerify, - optionsAwsSigned, - { parser: 'raw' }, - credentials, - 200, - afterSignedRequest, - ); - await sendSignedRequest( - optionsAwsVerify, - optionsAwsSigned, - { parser: 'json' }, - credentials, - 200, - afterSignedRequest, - ); - await sendSignedRequest( - optionsAwsVerify, - optionsAwsSigned, - { parser: 'custom' }, - credentials, - 200, - afterSignedRequest, - ); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 200, afterSignedRequest); }); it('should not validate request with aws4 with replaced host', async () => { @@ -252,30 +178,7 @@ describe('awsVerify', () => { }, }; - await sendSignedRequest( - optionsAwsVerify, - optionsAwsSigned, - { parser: 'raw' }, - credentials, - 401, - afterSignedRequest, - ); - await sendSignedRequest( - optionsAwsVerify, - optionsAwsSigned, - { parser: 'json' }, - credentials, - 401, - afterSignedRequest, - ); - await sendSignedRequest( - optionsAwsVerify, - optionsAwsSigned, - { parser: 'custom' }, - credentials, - 401, - afterSignedRequest, - ); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 401, afterSignedRequest); }); it('should not validate request with incorrect authorization header', async () => { @@ -298,10 +201,9 @@ describe('awsVerify', () => { await Promise.all( strangeAuthorizationHeaders.map( async (modifiedAuthorization) => - await sendSignedRequest( + await multiParserRequest( optionsAwsVerify, optionsAwsSigned, - { parser: 'raw' }, credentials, 401, undefined, @@ -321,9 +223,7 @@ describe('awsVerify', () => { const optionsAwsSigned = postExample(); const credentials = getCredentialsExample(); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'raw' }, credentials, 429); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'json' }, credentials, 429); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'custom' }, credentials, 429); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 429); }); it('should not validate request with aws4 onAfterParse when limit api', async () => { @@ -336,9 +236,7 @@ describe('awsVerify', () => { const optionsAwsSigned = postExample(); const credentials = getCredentialsExample(); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'raw' }, credentials, 429); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'json' }, credentials, 429); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'custom' }, credentials, 429); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 429); }); it('should not validate request with aws4 onSuccess something goes wrong and need to notify user', async () => { const optionsAwsVerify = getAwsVerifyOptionsExample({ @@ -350,9 +248,7 @@ describe('awsVerify', () => { const optionsAwsSigned = postExample(); const credentials = getCredentialsExample(); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'raw' }, credentials, 500); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'json' }, credentials, 500); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'custom' }, credentials, 500); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 500); }); it('should not validate request with aws4 onExpired header', async () => { const optionsAwsVerify = getAwsVerifyOptionsExample({ @@ -365,9 +261,7 @@ describe('awsVerify', () => { }); const credentials = getCredentialsExample(); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'raw' }, credentials, 408); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'json' }, credentials, 408); - await sendSignedRequest(optionsAwsVerify, optionsAwsSigned, { parser: 'custom' }, credentials, 408); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 408); }); it('should not validate request with aws4 missing authorization and xAmzDate', async () => { const optionsAwsVerify = getAwsVerifyOptionsExample({ @@ -385,33 +279,7 @@ describe('awsVerify', () => { }, }; - await sendSignedRequest( - optionsAwsVerify, - optionsAwsSigned, - { parser: 'raw' }, - credentials, - 417, - afterSignedRequest, - '', - ); - await sendSignedRequest( - optionsAwsVerify, - optionsAwsSigned, - { parser: 'json' }, - credentials, - 417, - afterSignedRequest, - '', - ); - await sendSignedRequest( - optionsAwsVerify, - optionsAwsSigned, - { parser: 'custom' }, - credentials, - 417, - afterSignedRequest, - '', - ); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 417, afterSignedRequest, ''); }); it('should not validate request with aws4 mismatch signature', async () => { const optionsAwsVerify = getAwsVerifyOptionsExample({ @@ -425,32 +293,6 @@ describe('awsVerify', () => { const authorization = 'AWS4-HMAC-SHA256 Credential=test/2011/us-east-1/execute-api/aws4_request, SignedHeaders=accept-encoding;cache-control;content-length;content-type;host;user-agent;x-amz-date, Signature=0230022437e5f1b668997ede2e55d4b00c7a3af802d000a2788d7b3c057503a4'; - await sendSignedRequest( - optionsAwsVerify, - optionsAwsSigned, - { parser: 'raw' }, - credentials, - 400, - undefined, - authorization, - ); - await sendSignedRequest( - optionsAwsVerify, - optionsAwsSigned, - { parser: 'json' }, - credentials, - 400, - undefined, - authorization, - ); - await sendSignedRequest( - optionsAwsVerify, - optionsAwsSigned, - { parser: 'custom' }, - credentials, - 400, - undefined, - authorization, - ); + await multiParserRequest(optionsAwsVerify, optionsAwsSigned, credentials, 400, undefined, authorization); }); }); diff --git a/src/test/helpers/exampleData.ts b/src/test/helpers/exampleData.ts index bdc6d69..7c8e9cb 100644 --- a/src/test/helpers/exampleData.ts +++ b/src/test/helpers/exampleData.ts @@ -7,7 +7,7 @@ const awsOptions = { region: 'eu-central-1', }; -const credentialsPairsExample: Record = { +export const credentialsPairsExample: Record = { xyz: 'xyz', test: 'test', test1: 'test1', diff --git a/src/test/helpers/multiParserRequest.ts b/src/test/helpers/multiParserRequest.ts new file mode 100644 index 0000000..77ed0bc --- /dev/null +++ b/src/test/helpers/multiParserRequest.ts @@ -0,0 +1,28 @@ +import { Request as Aws4Request, Credentials as Aws4Credentials } from 'aws4'; +import { AwsVerifyOptions } from '../..'; +import { parsers, sendSignedRequest } from './sendSignedRequest'; + +export const multiParserRequest = async ( + optionsAwsVerify: AwsVerifyOptions, + optionsAwsSigned: Aws4Request, + aws4Credentials: Aws4Credentials, + expectedHttpCode: number, + afterSignedRequest?: Aws4Request, + afterAuthorizationSignature?: string, +) => { + await Promise.all( + parsers + .filter((p) => p !== 'none') + .map(async (p) => { + await sendSignedRequest( + optionsAwsVerify, + optionsAwsSigned, + { parser: p }, + aws4Credentials, + expectedHttpCode, + afterSignedRequest, + afterAuthorizationSignature, + ); + }), + ); +}; diff --git a/src/test/helpers/sendSignedRequest.ts b/src/test/helpers/sendSignedRequest.ts index 60bf2c8..82c1606 100644 --- a/src/test/helpers/sendSignedRequest.ts +++ b/src/test/helpers/sendSignedRequest.ts @@ -3,10 +3,13 @@ import request from 'supertest'; import { sign, Request as Aws4Request, Credentials as Aws4Credentials } from 'aws4'; import { awsVerify, AwsVerifyOptions, rawBodyFromStream, rawBodyFromVerify } from '../..'; -export type MethodTypes = 'get' | 'post' | 'put' | 'delete'; +export const methods = ['get', 'post', 'put', 'delete'] as const; +export type MethodTypes = (typeof methods)[number]; +export const parsers = ['json', 'urlencoded', 'raw', 'custom', 'none'] as const; +export type ParserTypes = (typeof parsers)[number]; export interface ExpressAppOptions { - parser: 'json' | 'raw' | 'custom' | 'none'; + parser: ParserTypes; path: string; testRouter: (req: Request, res: Response, next: NextFunction) => void; } @@ -14,11 +17,21 @@ export interface ExpressAppOptions { const expressApp = (optionsAwsVerify: AwsVerifyOptions, optionsExpress: ExpressAppOptions) => { const routePath = optionsExpress.path.substring(0, optionsExpress.path.indexOf('?')) ?? '/'; const app = express(); - app.use(express.urlencoded({ extended: true })); switch (optionsExpress.parser) { case 'json': { app.use( express.json({ + type: '*/*', + verify: rawBodyFromVerify, + }), + ); + break; + } + case 'urlencoded': { + app.use( + express.urlencoded({ + extended: true, + type: '*/*', verify: rawBodyFromVerify, }), );