@model-ts/dynamodb 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/cjs/__test__/client-with-cursor-encryption.test.d.ts +1 -0
- package/dist/cjs/__test__/client-with-cursor-encryption.test.js +1911 -0
- package/dist/cjs/__test__/client-with-cursor-encryption.test.js.map +1 -0
- package/dist/cjs/__test__/client.test.js +77 -56
- package/dist/cjs/__test__/client.test.js.map +1 -1
- package/dist/cjs/client.d.ts +14 -2
- package/dist/cjs/client.js +28 -9
- package/dist/cjs/client.js.map +1 -1
- package/dist/cjs/dynamodb-model.d.ts +4 -0
- package/dist/cjs/pagination.d.ts +48 -3
- package/dist/cjs/pagination.js +65 -23
- package/dist/cjs/pagination.js.map +1 -1
- package/dist/cjs/provider.d.ts +21 -20
- package/dist/cjs/provider.js +5 -1
- package/dist/cjs/provider.js.map +1 -1
- package/dist/esm/__test__/client-with-cursor-encryption.test.d.ts +1 -0
- package/dist/esm/__test__/client-with-cursor-encryption.test.js +1909 -0
- package/dist/esm/__test__/client-with-cursor-encryption.test.js.map +1 -0
- package/dist/esm/__test__/client.test.js +78 -57
- package/dist/esm/__test__/client.test.js.map +1 -1
- package/dist/esm/client.d.ts +14 -2
- package/dist/esm/client.js +28 -9
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/dynamodb-model.d.ts +4 -0
- package/dist/esm/pagination.d.ts +48 -3
- package/dist/esm/pagination.js +64 -23
- package/dist/esm/pagination.js.map +1 -1
- package/dist/esm/provider.d.ts +21 -20
- package/dist/esm/provider.js +5 -1
- package/dist/esm/provider.js.map +1 -1
- package/package.json +2 -2
- package/src/__test__/client-with-cursor-encryption.test.ts +2445 -0
- package/src/__test__/client.test.ts +92 -57
- package/src/client.ts +54 -13
- package/src/dynamodb-model.ts +4 -0
- package/src/pagination.ts +76 -19
- package/src/provider.ts +5 -2
package/src/pagination.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import crypto from "crypto"
|
|
1
2
|
import { PaginationError } from "./errors"
|
|
2
3
|
|
|
4
|
+
const SIV = "Q05yyCR+0tyWl6glrZhlNw=="
|
|
5
|
+
const ENCRYPTION_ALG = "aes-256-ctr"
|
|
6
|
+
|
|
3
7
|
export interface PageInfo {
|
|
4
8
|
hasPreviousPage: boolean
|
|
5
9
|
hasNextPage: boolean
|
|
@@ -71,6 +75,47 @@ export function decodePagination(pagination: PaginationInput): {
|
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Utility function to encrypt a cursor with AES-256-CTR, but uses a
|
|
80
|
+
* synthetic initialization vector (SIV) to ensure that the same cursor
|
|
81
|
+
* produces the same encrypted value.
|
|
82
|
+
*/
|
|
83
|
+
const encryptCursor = (key: Buffer, cursor: string) => {
|
|
84
|
+
const cipher = crypto.createCipheriv(
|
|
85
|
+
ENCRYPTION_ALG,
|
|
86
|
+
key,
|
|
87
|
+
Buffer.from(SIV, "base64")
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
const encrypted = Buffer.concat([cipher.update(cursor), cipher.final()])
|
|
91
|
+
|
|
92
|
+
return encrypted.toString("base64")
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Utility function to decrypt a cursor with AES-256-CTR, but uses a
|
|
97
|
+
* synthetic initialization vector (SIV) to ensure that the same cursor
|
|
98
|
+
* produces the same encrypted value.
|
|
99
|
+
*/
|
|
100
|
+
const decryptCursor = (key: Buffer, encryptedCursor: string) => {
|
|
101
|
+
try {
|
|
102
|
+
const decipher = crypto.createDecipheriv(
|
|
103
|
+
ENCRYPTION_ALG,
|
|
104
|
+
key,
|
|
105
|
+
Buffer.from(SIV, "base64")
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const decrypted = Buffer.concat([
|
|
109
|
+
decipher.update(Buffer.from(encryptedCursor, "base64")),
|
|
110
|
+
decipher.final(),
|
|
111
|
+
]).toString()
|
|
112
|
+
|
|
113
|
+
return decrypted
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
74
119
|
export const encodeDDBCursor = (
|
|
75
120
|
{
|
|
76
121
|
PK,
|
|
@@ -95,21 +140,10 @@ export const encodeDDBCursor = (
|
|
|
95
140
|
GSI5PK?: string
|
|
96
141
|
GSI5SK?: string
|
|
97
142
|
},
|
|
98
|
-
|
|
99
|
-
) =>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
: index === "GSI3"
|
|
103
|
-
? Buffer.from(JSON.stringify({ PK, SK, GSI3PK, GSI3SK })).toString("base64")
|
|
104
|
-
: index === "GSI4"
|
|
105
|
-
? Buffer.from(JSON.stringify({ PK, SK, GSI4PK, GSI4SK })).toString("base64")
|
|
106
|
-
: index === "GSI5"
|
|
107
|
-
? Buffer.from(JSON.stringify({ PK, SK, GSI5PK, GSI5SK })).toString("base64")
|
|
108
|
-
: Buffer.from(JSON.stringify({ PK, SK })).toString("base64")
|
|
109
|
-
|
|
110
|
-
export const decodeDDBCursor = (encoded: string) => {
|
|
111
|
-
try {
|
|
112
|
-
const {
|
|
143
|
+
encryptionKey?: Buffer
|
|
144
|
+
) => {
|
|
145
|
+
const cursor = Buffer.from(
|
|
146
|
+
JSON.stringify({
|
|
113
147
|
PK,
|
|
114
148
|
SK,
|
|
115
149
|
GSI2PK,
|
|
@@ -120,11 +154,26 @@ export const decodeDDBCursor = (encoded: string) => {
|
|
|
120
154
|
GSI4SK,
|
|
121
155
|
GSI5PK,
|
|
122
156
|
GSI5SK,
|
|
123
|
-
}
|
|
157
|
+
})
|
|
158
|
+
).toString("base64")
|
|
124
159
|
|
|
125
|
-
|
|
160
|
+
if (encryptionKey) return encryptCursor(encryptionKey, cursor)
|
|
161
|
+
|
|
162
|
+
return cursor
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const decodeDDBCursor = (
|
|
166
|
+
encoded: string,
|
|
167
|
+
index?: "GSI2" | "GSI3" | "GSI4" | "GSI5",
|
|
168
|
+
encryptionKey?: Buffer
|
|
169
|
+
) => {
|
|
170
|
+
try {
|
|
171
|
+
const json = encryptionKey ? decryptCursor(encryptionKey, encoded) : encoded
|
|
172
|
+
// const json = encoded
|
|
173
|
+
|
|
174
|
+
if (!json) throw new Error("Couldn't decrypt cursor")
|
|
126
175
|
|
|
127
|
-
|
|
176
|
+
const {
|
|
128
177
|
PK,
|
|
129
178
|
SK,
|
|
130
179
|
GSI2PK,
|
|
@@ -135,7 +184,15 @@ export const decodeDDBCursor = (encoded: string) => {
|
|
|
135
184
|
GSI4SK,
|
|
136
185
|
GSI5PK,
|
|
137
186
|
GSI5SK,
|
|
138
|
-
}
|
|
187
|
+
} = JSON.parse(Buffer.from(json, "base64").toString())
|
|
188
|
+
|
|
189
|
+
if (typeof PK !== "string" || typeof SK !== "string") throw new Error()
|
|
190
|
+
|
|
191
|
+
if (!index) return { PK, SK }
|
|
192
|
+
if (index === "GSI2") return { PK, SK, GSI2PK, GSI2SK }
|
|
193
|
+
if (index === "GSI3") return { PK, SK, GSI3PK, GSI3SK }
|
|
194
|
+
if (index === "GSI4") return { PK, SK, GSI4PK, GSI4SK }
|
|
195
|
+
if (index === "GSI5") return { PK, SK, GSI5PK, GSI5SK }
|
|
139
196
|
} catch (error) {
|
|
140
197
|
throw new PaginationError("Couldn't decode cursor")
|
|
141
198
|
}
|
package/src/provider.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
import { OutputOf, TypeOf, ModelOf } from "@model-ts/core"
|
|
18
18
|
import { RaceConditionError } from "./errors"
|
|
19
19
|
import { absurd } from "fp-ts/lib/function"
|
|
20
|
-
import { PaginationInput } from "./pagination"
|
|
20
|
+
import { encodeDDBCursor, PaginationInput } from "./pagination"
|
|
21
21
|
|
|
22
22
|
export interface DynamoDBInternals<M extends Decodable> {
|
|
23
23
|
__dynamoDBDecode(
|
|
@@ -524,6 +524,9 @@ export const getProvider = (client: Client) => {
|
|
|
524
524
|
GSI5SK: this.GSI5SK,
|
|
525
525
|
}
|
|
526
526
|
},
|
|
527
|
+
cursor<T extends DynamoDBModelInstance>(this: T) {
|
|
528
|
+
return encodeDDBCursor(this.keys(), client.cursorEncryptionKey)
|
|
529
|
+
},
|
|
527
530
|
put<T extends DynamoDBModelInstance>(
|
|
528
531
|
this: T,
|
|
529
532
|
params?: Omit<
|
|
@@ -621,7 +624,7 @@ export const getProvider = (client: Client) => {
|
|
|
621
624
|
return client.get<M>({
|
|
622
625
|
_model: this,
|
|
623
626
|
_operation: "get",
|
|
624
|
-
key,
|
|
627
|
+
key: { PK: key.PK, SK: key.SK },
|
|
625
628
|
...params,
|
|
626
629
|
})
|
|
627
630
|
},
|