Skip to content

Commit

Permalink
+fix urlencoded support
Browse files Browse the repository at this point in the history
+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
  • Loading branch information
jerzyadamowski committed Feb 21, 2023
1 parent 9e00bff commit a105e12
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 246 deletions.
66 changes: 44 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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);

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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> | string;
secretKey: (message: AwsIncomingMessage, req: Request, res: Response, next: NextFunction) => Promise<string | undefined> | string | undefined;
headers?: (headers: Dictionary) => Promise<Dictionary> | Dictionary;
enabled?: (req: Request) => Promise<boolean> | boolean;
onMissingHeaders?: (req: Request, res: Response, next: NextFunction) => Promise<void> | void;
Expand All @@ -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()
}
```


2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
3 changes: 1 addition & 2 deletions src/awsSignature.ts
Original file line number Diff line number Diff line change
@@ -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<string, string | string[] | undefined>;
Expand Down Expand Up @@ -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;

Expand Down
23 changes: 4 additions & 19 deletions src/rawBody.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down
Loading

0 comments on commit a105e12

Please sign in to comment.