Skip to content

Commit

Permalink
authenticated encryption of the token
Browse files Browse the repository at this point in the history
  • Loading branch information
monken committed Apr 22, 2022
1 parent 41246ea commit a0e9e66
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 192 deletions.
66 changes: 46 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@

Features:
* Supports Binary and String key types
* Generates AES256 encrypted pagination tokens
* Generates AES256 encrypted and authenticated pagination tokens
* Works with TypeScript type guards natively
* Ensures a minimum number of items when using a `FilterExpression`
* Compatible with AWS SDK v2 and v3

Pagination in NoSQL stores such as DynamoDB can be challenging at times. This
library aims at providing a developer friendly interface around the DynamoDB `Query` API. It also
provides a secure way of sharing a pagination token with an untrustworthy client (like the browser
or a mobile app) without disclosing potentially sensitive data and protecting the integrity of the token.
Pagination in NoSQL stores such as DynamoDB can be challenging. This
library provides a developer friendly interface around the DynamoDB `Query` and `Scan` APIs.
It generates and encrypted and authenticated pagination token that can be shared with an untrustworthy
client (like the browser or a mobile app) without disclosing potentially sensitive data and protecting
the integrity of the token.

**Why is the pagination token encrypted?**

When researching pagination with DynamoDB, you will come across blog posts and libraries that recommend
to JSON-encode the `LastEvaluatedKey` attribute (or even the whole query command). This is dangerous!
to JSON-encode the `LastEvaluatedKey` attribute (or even the whole query command). **This is dangerous!**

The token is sent to a client which can happily decode the token, look at the values for the
partition and sort key and even modify the token, making the application vulnerable to NoSQL injections.
Expand All @@ -30,7 +31,7 @@ partition and sort key and even modify the token, making the application vulnera
## Usage

```typescript
import { QueryPaginator } from '@emdgroup/dynamodb-paginator';
import { Paginator } from '@emdgroup/dynamodb-paginator';
import { DynamoDB } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';

Expand All @@ -40,7 +41,7 @@ const client = DynamoDBDocument.from(new DynamoDB({}));
// persist the key in the SSM parameter store or similar
const key = crypto.randomBytes(32);

const paginateQuery = QueryPaginator.create({
const paginateQuery = Paginator.createQuery({
key,
client,
});
Expand Down Expand Up @@ -100,14 +101,14 @@ for await (const user of paginator.filter(isUser)) {
}
```

## QueryPaginator
## Paginator

The `QueryPaginator` class is a factory for the [`PaginationResponse`](#PaginationResponse) object. This class
The `Paginator` class is a factory for the [`PaginationResponse`](#PaginationResponse) object. This class
is instantiated with the 32-byte encryption key and the DynamoDB document client (versions
2 and 3 of the AWS SDK are supported).

```typescript
const paginateQuery = QueryPaginator.create({
const paginateQuery = Paginator.createQuery({
key: () => Promise.resolve(crypto.randomBytes(32)),
client: documentClient,
});
Expand All @@ -116,7 +117,7 @@ const paginateQuery = QueryPaginator.create({
To create a paginator over a scan operation, use `createScan`.

```typescript
const paginateQuery = QueryPaginator.createScan({
const paginateScan = Paginator.createScan({
key: () => Promise.resolve(crypto.randomBytes(32)),
client: documentClient,
});
Expand All @@ -126,29 +127,29 @@ const paginateQuery = QueryPaginator.createScan({

### constructor

**new QueryPaginator**(`args`)
**new Paginator**(`args`)

Use the static factory function [`create()`](#create) instead of the constructor.

#### Parameters

| Name | Type |
| :------ | :------ |
| `args` | [`QueryPaginatorOptions`](#QueryPaginatorOptions) |
| `args` | [`PaginatorOptions`](#PaginatorOptions) |

## Methods

### create
### createQuery

`Static` **create**(`args`): <T\>(`query`: `QueryCommandInput`, `opts?`: [`PaginateQueryOptions`](#PaginateQueryOptions)<`T`\>) => [`PaginationResponse`](#PaginationResponse)<`T`\>
`Static` **createQuery**(`args`): <T\>(`query`: `QueryCommandInput`, `opts?`: [`PaginateQueryOptions`](#PaginateQueryOptions)<`T`\>) => [`PaginationResponse`](#PaginationResponse)<`T`\>

Returns a function that accepts a DynamoDB Query command and return an instance of `PaginationResponse`.

#### Parameters

| Name | Type |
| :------ | :------ |
| `args` | [`QueryPaginatorOptions`](#QueryPaginatorOptions) |
| `args` | [`PaginatorOptions`](#PaginatorOptions) |

#### Returns

Expand Down Expand Up @@ -187,7 +188,7 @@ Returns a function that accepts a DynamoDB Scan command and return an instance o

| Name | Type |
| :------ | :------ |
| `args` | [`QueryPaginatorOptions`](#QueryPaginatorOptions) |
| `args` | [`PaginatorOptions`](#PaginatorOptions) |

#### Returns

Expand Down Expand Up @@ -215,7 +216,7 @@ Returns a function that accepts a DynamoDB Scan command and return an instance o
[`PaginationResponse`](#PaginationResponse)<`T`\>


## QueryPaginatorOptions
## PaginatorOptions

## Properties

Expand Down Expand Up @@ -324,6 +325,18 @@ Number of items scanned by DynamoDB

## Accessors

### finished

`get` **finished**(): `boolean`

Returns true if all items for this query have been returned from DynamoDB.

#### Returns

`boolean`

___

### nextToken

`get` **nextToken**(): `undefined` \| `string`
Expand All @@ -339,7 +352,7 @@ by DynamoDB. It also prevents a client from modifying the token and therefore ma
execution (NoSQL injection).

The length of the token depends on the length of the values for the partition and sort key of the table
or index that you are querying. The token length is at least 64 characters.
or index that you are querying. The token length is at least 42 characters.

#### Returns

Expand Down Expand Up @@ -488,6 +501,19 @@ for await (const item of items) {

## Properties

### context

`Optional` **context**: `string` \| `Buffer`

The context defines the additional authenticated data (AAD) that is used to generate the signature
for the pagination token. It is optional but recommended because it adds an additional layer of
authentication to the pagination token. Pagination token will be tied to the context and replaying
them in other contexts will fail. Good examples for the context are a user ID or a session ID concatenated
with the purpose of the query, such as `ListPets`. The context cannot be extracted from the pagination
token and can therefore contain sensitive data.

___

### from

`Optional` **from**: `string`
Expand Down
4 changes: 2 additions & 2 deletions bin/merge-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { readFileSync, writeFileSync, readdirSync } from 'fs';
function run() {
const readme: string[] = [];
for (const file of [
'QueryPaginator.md',
'QueryPaginatorOptions.md',
'Paginator.md',
'PaginatorOptions.md',
'PaginationResponse.md',
'PaginateQueryOptions.md',
]) {
Expand Down
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@emdgroup/dynamodb-paginator",
"version": "1.0.0",
"version": "2.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"repository": "https://github.com/emdgroup/dynamodb-paginator.git",
Expand All @@ -23,10 +23,8 @@
},
"devDependencies": {
"@tsconfig/node12": "^1.0.9",
"@types/chai": "^4.2.21",
"@types/mocha": "^9.0.0",
"@types/node": "^16.3.1",
"chai": "^4.3.4",
"mocha": "^9.0.2",
"ts-node": "^10.1.0",
"typedoc": "^0.22.0",
Expand Down
Loading

0 comments on commit a0e9e66

Please sign in to comment.