@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.
Files changed (38) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/__test__/client-with-cursor-encryption.test.d.ts +1 -0
  3. package/dist/cjs/__test__/client-with-cursor-encryption.test.js +1911 -0
  4. package/dist/cjs/__test__/client-with-cursor-encryption.test.js.map +1 -0
  5. package/dist/cjs/__test__/client.test.js +77 -56
  6. package/dist/cjs/__test__/client.test.js.map +1 -1
  7. package/dist/cjs/client.d.ts +14 -2
  8. package/dist/cjs/client.js +28 -9
  9. package/dist/cjs/client.js.map +1 -1
  10. package/dist/cjs/dynamodb-model.d.ts +4 -0
  11. package/dist/cjs/pagination.d.ts +48 -3
  12. package/dist/cjs/pagination.js +65 -23
  13. package/dist/cjs/pagination.js.map +1 -1
  14. package/dist/cjs/provider.d.ts +21 -20
  15. package/dist/cjs/provider.js +5 -1
  16. package/dist/cjs/provider.js.map +1 -1
  17. package/dist/esm/__test__/client-with-cursor-encryption.test.d.ts +1 -0
  18. package/dist/esm/__test__/client-with-cursor-encryption.test.js +1909 -0
  19. package/dist/esm/__test__/client-with-cursor-encryption.test.js.map +1 -0
  20. package/dist/esm/__test__/client.test.js +78 -57
  21. package/dist/esm/__test__/client.test.js.map +1 -1
  22. package/dist/esm/client.d.ts +14 -2
  23. package/dist/esm/client.js +28 -9
  24. package/dist/esm/client.js.map +1 -1
  25. package/dist/esm/dynamodb-model.d.ts +4 -0
  26. package/dist/esm/pagination.d.ts +48 -3
  27. package/dist/esm/pagination.js +64 -23
  28. package/dist/esm/pagination.js.map +1 -1
  29. package/dist/esm/provider.d.ts +21 -20
  30. package/dist/esm/provider.js +5 -1
  31. package/dist/esm/provider.js.map +1 -1
  32. package/package.json +2 -2
  33. package/src/__test__/client-with-cursor-encryption.test.ts +2445 -0
  34. package/src/__test__/client.test.ts +92 -57
  35. package/src/client.ts +54 -13
  36. package/src/dynamodb-model.ts +4 -0
  37. package/src/pagination.ts +76 -19
  38. 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
- index?: "GSI2" | "GSI3" | "GSI4" | "GSI5"
99
- ) =>
100
- index === "GSI2"
101
- ? Buffer.from(JSON.stringify({ PK, SK, GSI2PK, GSI2SK })).toString("base64")
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
- } = JSON.parse(Buffer.from(encoded, "base64").toString())
157
+ })
158
+ ).toString("base64")
124
159
 
125
- if (typeof PK !== "string" || typeof SK !== "string") throw new Error()
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
- return {
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
  },