zenstack-encryption 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -77,15 +77,18 @@ npx zenstack generate
77
77
 
78
78
  ```typescript
79
79
  import { ZenStackClient } from '@zenstackhq/orm';
80
- import { createEncryptionPlugin, ENCRYPTION_KEY_BYTES } from 'zenstack-encryption';
80
+ import { createEncryptionPlugin } from 'zenstack-encryption';
81
81
  import schema from './schema.js';
82
82
 
83
- // Load your 32-byte key from environment, KMS, vault, etc.
84
- const encryptionKey = new Uint8Array(
85
- Buffer.from(process.env.ENCRYPTION_KEY!, 'base64')
86
- );
83
+ // Pass a string secret — it will be derived to a 32-byte key via SHA-256
84
+ const encryptionPlugin = createEncryptionPlugin({
85
+ encryptionKey: process.env.ENCRYPTION_SECRET!,
86
+ });
87
87
 
88
- const encryptionPlugin = createEncryptionPlugin({ encryptionKey });
88
+ // Or pass a raw 32-byte Uint8Array if you already have one
89
+ // const encryptionPlugin = createEncryptionPlugin({
90
+ // encryptionKey: new Uint8Array(Buffer.from(process.env.ENCRYPTION_KEY!, 'base64')),
91
+ // });
89
92
 
90
93
  const client = new ZenStackClient(schema, {
91
94
  plugins: [encryptionPlugin],
@@ -104,12 +107,12 @@ console.log(user.secretToken); // → "super-secret-value" (decrypted)
104
107
 
105
108
  ## Key Rotation
106
109
 
107
- When you need to rotate encryption keys, pass old keys via `decryptionKeys`. The plugin will use the primary `encryptionKey` for new writes, but try all keys (primary + decryption) when decrypting:
110
+ When you need to rotate encryption keys, pass old keys via `decryptionKeys`. The plugin will use the primary `encryptionKey` for new writes, but try all keys (primary + decryption) when decrypting. Both strings and `Uint8Array` keys can be mixed:
108
111
 
109
112
  ```typescript
110
113
  const plugin = createEncryptionPlugin({
111
- encryptionKey: newKey, // used for all new encryptions
112
- decryptionKeys: [oldKey], // tried during decryption alongside newKey
114
+ encryptionKey: 'new-secret', // used for all new encryptions
115
+ decryptionKeys: ['old-secret'], // tried during decryption alongside encryptionKey
113
116
  });
114
117
  ```
115
118
 
@@ -150,12 +153,26 @@ const baseClient = new ZenStackClient(schema);
150
153
  const client = baseClient.$use(createEncryptionPlugin({ encryptionKey }));
151
154
  ```
152
155
 
156
+ ## Security Notes
157
+
158
+ When passing a string as `encryptionKey`, the plugin derives a 32-byte key using SHA-256. This is **not** a password-based key derivation function — it does not use salting or iterations. Your string secret should be **high-entropy** (e.g. a random 32+ character token from a secrets manager, not a human-chosen password).
159
+
160
+ ```bash
161
+ # Good: generate a random secret
162
+ openssl rand -base64 32
163
+
164
+ # Bad: weak password
165
+ ENCRYPTION_SECRET="password123"
166
+ ```
167
+
168
+ If you need to derive keys from low-entropy passwords, use a proper KDF (PBKDF2, Argon2) yourself and pass the resulting `Uint8Array` directly.
169
+
153
170
  ## Limitations
154
171
 
155
172
  - **ORM only** — only applies to ORM CRUD operations, not direct Kysely query builder calls via `client.$qb`
156
- - **String fields only** — `@encrypted` can only be applied to `String` fields
157
- - **No encrypted filtering** — encrypted fields cannot be used in `where` clauses since encryption is non-deterministic (each encryption produces different ciphertext due to random IVs)
158
- - **Storage overhead** — encrypted values are longer than the original plaintext due to base64 encoding + metadata; ensure your database column can accommodate the larger size
173
+ - **String fields only** — `@encrypted` can only be applied to `String` fields. Applying `@encrypted` to non-String fields will log a warning at runtime and be ignored.
174
+ - **No encrypted filtering** — encrypted fields **cannot** be used in `where` clauses, `orderBy`, or unique constraints. Since encryption is non-deterministic (each encryption produces different ciphertext due to random IVs), queries like `where: { secretField: 'value' }` will never match. If you need to search by a field, don't encrypt it — or store a separate non-encrypted hash for lookups.
175
+ - **Storage overhead** — encrypted values are larger than the original plaintext. Expect roughly **80 bytes of overhead** per field (IV + GCM tag + metadata + base64 encoding), plus ~37% expansion of the plaintext itself. A 100-character plaintext becomes ~215 characters. Ensure your database columns use `TEXT` or a sufficiently large `VARCHAR`.
159
176
 
160
177
  ## License
161
178
 
package/dist/index.d.mts CHANGED
@@ -8,7 +8,9 @@ import { FieldDef, SchemaDef } from "@zenstackhq/orm/schema";
8
8
  declare class Decrypter {
9
9
  private readonly decryptionKeys;
10
10
  private keys;
11
+ private initPromise;
11
12
  constructor(decryptionKeys: Uint8Array[]);
13
+ private ensureKeys;
12
14
  /**
13
15
  * Decrypts the given data
14
16
  */
@@ -36,14 +38,16 @@ declare class Encrypter {
36
38
  */
37
39
  type SimpleEncryption = {
38
40
  /**
39
- * The encryption key (must be 32 bytes / 256 bits)
41
+ * The encryption key. Pass a Uint8Array of exactly 32 bytes, or a string
42
+ * which will be derived to a 32-byte key via SHA-256.
40
43
  */
41
- encryptionKey: Uint8Array;
44
+ encryptionKey: string | Uint8Array;
42
45
  /**
43
46
  * Additional decryption keys for key rotation support.
44
47
  * When decrypting, all keys (encryptionKey + decryptionKeys) are tried.
48
+ * Each key can be a Uint8Array (32 bytes) or a string (derived via SHA-256).
45
49
  */
46
- decryptionKeys?: Uint8Array[];
50
+ decryptionKeys?: (string | Uint8Array)[];
47
51
  };
48
52
  /**
49
53
  * Custom encryption configuration for user-provided encryption handlers
@@ -82,10 +86,16 @@ declare function isCustomEncryption(config: EncryptionConfig): config is CustomE
82
86
  * @param config Encryption configuration (simple or custom)
83
87
  * @returns A runtime plugin that handles field encryption/decryption
84
88
  */
85
- declare function createEncryptionPlugin<Schema extends SchemaDef>(config: EncryptionConfig): _zenstackhq_orm0.RuntimePlugin<any, {}, {}>;
89
+ declare function createEncryptionPlugin<Schema extends SchemaDef>(config: EncryptionConfig): _zenstackhq_orm0.RuntimePlugin<any, Record<string, never>, Record<string, never>>;
86
90
  //#endregion
87
91
  //#region src/utils.d.ts
88
92
  declare const ENCRYPTION_KEY_BYTES = 32;
93
+ /**
94
+ * Resolve a key input to a Uint8Array. If the input is a string, it is
95
+ * derived to 32 bytes via SHA-256. If it is already a Uint8Array, its
96
+ * length is validated.
97
+ */
98
+ declare function deriveKey(input: string | Uint8Array): Promise<Uint8Array>;
89
99
  //#endregion
90
- export { type CustomEncryption, Decrypter, ENCRYPTION_KEY_BYTES, Encrypter, type EncryptionConfig, type SimpleEncryption, createEncryptionPlugin, isCustomEncryption };
100
+ export { type CustomEncryption, Decrypter, ENCRYPTION_KEY_BYTES, Encrypter, type EncryptionConfig, type SimpleEncryption, createEncryptionPlugin, deriveKey, isCustomEncryption };
91
101
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/decrypter.ts","../src/encrypter.ts","../src/types.ts","../src/plugin.ts","../src/utils.ts"],"mappings":";;;;;;;cAKa,SAAA;EAAA,iBAGoB,cAAA;EAAA,QAFrB,IAAA;cAEqB,cAAA,EAAgB,UAAA;EAeT;;;EAA9B,OAAA,CAAQ,IAAA,WAAe,OAAA;AAAA;;;;;;cClBpB,SAAA;EAAA,iBAIoB,aAAA;EAAA,QAHrB,GAAA;EAAA,QACA,SAAA;cAEqB,aAAA,EAAe,UAAA;EDDf;;;ECUvB,OAAA,CAAQ,IAAA,WAAe,OAAA;AAAA;;;;;;KCbrB,gBAAA;EFAU;;;EEIlB,aAAA,EAAe,UAAA;EFHP;;;;EESR,cAAA,GAAiB,UAAA;AAAA;;;;KAMT,gBAAA;;;ADhBZ;;;;;ECwBI,OAAA,GAAU,KAAA,UAAe,KAAA,EAAO,QAAA,EAAU,KAAA,aAAkB,OAAA;EDtBpD;;;;;;;EC+BR,OAAA,GAAU,KAAA,UAAe,KAAA,EAAO,QAAA,EAAU,MAAA,aAAmB,OAAA;AAAA;;;;KAMrD,gBAAA,GAAmB,gBAAA,GAAmB,gBAAA;;;;iBAKlC,kBAAA,CAAmB,MAAA,EAAQ,gBAAA,GAAmB,MAAA,IAAU,gBAAA;;;;;AF5CxE;;;;iBGwBgB,sBAAA,gBAAsC,SAAA,CAAA,CAAW,MAAA,EAAQ,gBAAA,GAAgB,gBAAA,CAAA,aAAA;;;cC1B5E,oBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/decrypter.ts","../src/encrypter.ts","../src/types.ts","../src/plugin.ts","../src/utils.ts"],"mappings":";;;;;;;cAKa,SAAA;EAAA,iBAIoB,cAAA;EAAA,QAHrB,IAAA;EAAA,QACA,WAAA;cAEqB,cAAA,EAAgB,UAAA;EAAA,QAY/B,UAAA;EAfN;;;EAkCF,OAAA,CAAQ,IAAA,WAAe,OAAA;AAAA;;;;;;cCnCpB,SAAA;EAAA,iBAIoB,aAAA;EAAA,QAHrB,GAAA;EAAA,QACA,SAAA;cAEqB,aAAA,EAAe,UAAA;EDAf;;;ECSvB,OAAA,CAAQ,IAAA,WAAe,OAAA;AAAA;;;;;;KCbrB,gBAAA;EFAU;;;;EEKlB,aAAA,WAAwB,UAAA;EFHhB;;;;;EEUR,cAAA,aAA2B,UAAA;AAAA;;;;KAMnB,gBAAA;;;ADlBZ;;;;;EC0BI,OAAA,GAAU,KAAA,UAAe,KAAA,EAAO,QAAA,EAAU,KAAA,aAAkB,OAAA;EDxBpD;;;;;;;ECiCR,OAAA,GAAU,KAAA,UAAe,KAAA,EAAO,QAAA,EAAU,MAAA,aAAmB,OAAA;AAAA;;;;KAMrD,gBAAA,GAAmB,gBAAA,GAAmB,gBAAA;;;;iBAKlC,kBAAA,CAAmB,MAAA,EAAQ,gBAAA,GAAmB,MAAA,IAAU,gBAAA;;;;;AF9CxE;;;;iBGyCgB,sBAAA,gBAAsC,SAAA,CAAA,CAAW,MAAA,EAAQ,gBAAA,GAAgB,gBAAA,CAAA,aAAA,MAAA,MAAA,iBAAA,MAAA;;;cC3C5E,oBAAA;;;;;;iBAsBS,SAAA,CAAU,KAAA,WAAgB,UAAA,GAAa,OAAA,CAAQ,UAAA"}
package/dist/index.mjs CHANGED
@@ -15,6 +15,19 @@ const encryptionMetaSchema = z.object({
15
15
  k: z.string()
16
16
  });
17
17
  /**
18
+ * Resolve a key input to a Uint8Array. If the input is a string, it is
19
+ * derived to 32 bytes via SHA-256. If it is already a Uint8Array, its
20
+ * length is validated.
21
+ */
22
+ async function deriveKey(input) {
23
+ if (typeof input === "string") {
24
+ const encoded = new TextEncoder().encode(input);
25
+ return new Uint8Array(await crypto.subtle.digest("SHA-256", encoded));
26
+ }
27
+ if (input.length !== ENCRYPTION_KEY_BYTES) throw new Error(`Encryption key must be ${ENCRYPTION_KEY_BYTES} bytes`);
28
+ return input;
29
+ }
30
+ /**
18
31
  * Load a raw encryption key into a CryptoKey object
19
32
  */
20
33
  async function loadKey(key, keyUsages) {
@@ -78,7 +91,7 @@ async function _decrypt(data, findKey) {
78
91
  }
79
92
  return decoder.decode(decrypted);
80
93
  }
81
- throw lastError;
94
+ throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("Decryption failed with all available keys");
82
95
  }
83
96
 
84
97
  //#endregion
@@ -93,14 +106,22 @@ var Decrypter = class {
93
106
  if (decryptionKeys.length === 0) throw new Error("At least one decryption key must be provided");
94
107
  for (const key of decryptionKeys) if (key.length !== ENCRYPTION_KEY_BYTES) throw new Error(`Decryption key must be ${ENCRYPTION_KEY_BYTES} bytes`);
95
108
  }
109
+ async ensureKeys() {
110
+ if (this.keys.length > 0) return;
111
+ if (this.initPromise) return this.initPromise;
112
+ this.initPromise = (async () => {
113
+ this.keys = await Promise.all(this.decryptionKeys.map(async (key) => ({
114
+ key: await loadKey(key, ["decrypt"]),
115
+ digest: await getKeyDigest(key)
116
+ })));
117
+ })();
118
+ return this.initPromise;
119
+ }
96
120
  /**
97
121
  * Decrypts the given data
98
122
  */
99
123
  async decrypt(data) {
100
- if (this.keys.length === 0) this.keys = await Promise.all(this.decryptionKeys.map(async (key) => ({
101
- key: await loadKey(key, ["decrypt"]),
102
- digest: await getKeyDigest(key)
103
- })));
124
+ await this.ensureKeys();
104
125
  return _decrypt(data, async (digest) => this.keys.filter((entry) => entry.digest === digest).map((entry) => entry.key));
105
126
  }
106
127
  };
@@ -137,6 +158,7 @@ function isCustomEncryption(config) {
137
158
  //#endregion
138
159
  //#region src/plugin.ts
139
160
  const ENCRYPTED_ATTRIBUTE = "@encrypted";
161
+ const warnedNonStringFields = /* @__PURE__ */ new Set();
140
162
  /**
141
163
  * Check if a field has the @encrypted attribute
142
164
  */
@@ -144,6 +166,18 @@ function isEncryptedField(field) {
144
166
  return field.attributes?.some((attr) => attr.name === ENCRYPTED_ATTRIBUTE) ?? false;
145
167
  }
146
168
  /**
169
+ * Warn once if @encrypted is applied to a non-String field
170
+ */
171
+ function warnIfNonStringEncrypted(modelName, fieldName, field) {
172
+ if (isEncryptedField(field) && field.type !== "String") {
173
+ const key = `${modelName}.${fieldName}`;
174
+ if (!warnedNonStringFields.has(key)) {
175
+ warnedNonStringFields.add(key);
176
+ console.warn(`@encrypted is only supported on String fields. ${key} (type: ${field.type}) will be ignored.`);
177
+ }
178
+ }
179
+ }
180
+ /**
147
181
  * Check if a model has any encrypted fields
148
182
  */
149
183
  function hasEncryptedFields(model) {
@@ -159,11 +193,23 @@ function createEncryptionPlugin(config) {
159
193
  let encrypter;
160
194
  let decrypter;
161
195
  let customEncryption;
162
- if (isCustomEncryption(config)) customEncryption = config;
163
- else {
164
- const simpleConfig = config;
165
- encrypter = new Encrypter(simpleConfig.encryptionKey);
166
- decrypter = new Decrypter([simpleConfig.encryptionKey, ...simpleConfig.decryptionKeys ?? []]);
196
+ let initialized = false;
197
+ let initPromise;
198
+ async function ensureInitialized() {
199
+ if (initialized) return;
200
+ if (initPromise) return initPromise;
201
+ initPromise = (async () => {
202
+ if (isCustomEncryption(config)) customEncryption = config;
203
+ else {
204
+ const simpleConfig = config;
205
+ const encryptionKey = await deriveKey(simpleConfig.encryptionKey);
206
+ const decryptionKeys = await Promise.all((simpleConfig.decryptionKeys ?? []).map(deriveKey));
207
+ encrypter = new Encrypter(encryptionKey);
208
+ decrypter = new Decrypter([encryptionKey, ...decryptionKeys]);
209
+ }
210
+ initialized = true;
211
+ })();
212
+ return initPromise;
167
213
  }
168
214
  async function encryptValue(model, field, value) {
169
215
  if (customEncryption) return customEncryption.encrypt(model, field, value);
@@ -180,9 +226,10 @@ function createEncryptionPlugin(config) {
180
226
  const model = schema.models[modelName];
181
227
  if (!model) return;
182
228
  for (const [fieldName, value] of Object.entries(data)) {
183
- if (value === null || value === void 0 || value === "") continue;
229
+ if (value === null || value === void 0) continue;
184
230
  const field = model.fields[fieldName];
185
231
  if (!field) continue;
232
+ warnIfNonStringEncrypted(modelName, fieldName, field);
186
233
  if (isEncryptedField(field) && typeof value === "string") {
187
234
  data[fieldName] = await encryptValue(modelName, field, value);
188
235
  continue;
@@ -211,10 +258,8 @@ function createEncryptionPlugin(config) {
211
258
  if (itemData) await encryptWriteData(schema, modelName, itemData);
212
259
  }
213
260
  else {
214
- const updateObj = updateData;
215
- const nestedData = updateObj["data"];
261
+ const nestedData = updateData["data"];
216
262
  if (nestedData) await encryptWriteData(schema, modelName, nestedData);
217
- else await encryptWriteData(schema, modelName, updateObj);
218
263
  }
219
264
  const updateManyData = data["updateMany"];
220
265
  if (updateManyData) if (Array.isArray(updateManyData)) for (const item of updateManyData) {
@@ -257,14 +302,14 @@ function createEncryptionPlugin(config) {
257
302
  const model = schema.models[modelName];
258
303
  if (!model) return;
259
304
  for (const [fieldName, value] of Object.entries(data)) {
260
- if (value === null || value === void 0 || value === "") continue;
305
+ if (value === null || value === void 0) continue;
261
306
  const field = model.fields[fieldName];
262
307
  if (!field) continue;
263
308
  if (isEncryptedField(field) && typeof value === "string") {
264
309
  try {
265
310
  data[fieldName] = await decryptValue(modelName, field, value);
266
- } catch (error) {
267
- console.warn(`Failed to decrypt field ${modelName}.${fieldName}:`, error);
311
+ } catch {
312
+ console.warn("Failed to decrypt an encrypted field");
268
313
  }
269
314
  continue;
270
315
  }
@@ -281,12 +326,15 @@ function createEncryptionPlugin(config) {
281
326
  name: "Encryption Plugin",
282
327
  description: "Automatically encrypts and decrypts fields marked with @encrypted",
283
328
  onQuery: async (ctx) => {
329
+ await ensureInitialized();
284
330
  const { model, operation, args, proceed, client } = ctx;
285
331
  const schema = client.schema;
286
332
  const modelDef = schema.models[model];
287
333
  if (!modelDef || !hasEncryptedFields(modelDef)) return proceed(args);
288
- const processedArgs = args ? JSON.parse(JSON.stringify(args)) : void 0;
289
- if (operation === "create" || operation === "update" || operation === "upsert" || operation === "createMany" || operation === "updateMany" || operation === "createManyAndReturn") {
334
+ const isWrite = operation === "create" || operation === "update" || operation === "upsert" || operation === "createMany" || operation === "updateMany" || operation === "createManyAndReturn";
335
+ let processedArgs = args;
336
+ if (isWrite) {
337
+ processedArgs = args ? JSON.parse(JSON.stringify(args)) : void 0;
290
338
  if (processedArgs?.data) if (Array.isArray(processedArgs.data)) for (const item of processedArgs.data) await encryptWriteData(schema, model, item);
291
339
  else await encryptWriteData(schema, model, processedArgs.data);
292
340
  if (operation === "upsert") {
@@ -306,5 +354,5 @@ function createEncryptionPlugin(config) {
306
354
  }
307
355
 
308
356
  //#endregion
309
- export { Decrypter, ENCRYPTION_KEY_BYTES, Encrypter, createEncryptionPlugin, isCustomEncryption };
357
+ export { Decrypter, ENCRYPTION_KEY_BYTES, Encrypter, createEncryptionPlugin, deriveKey, isCustomEncryption };
310
358
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/utils.ts","../src/decrypter.ts","../src/encrypter.ts","../src/types.ts","../src/plugin.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const ENCRYPTER_VERSION = 1;\nexport const ENCRYPTION_KEY_BYTES = 32;\nexport const IV_BYTES = 12;\nexport const ALGORITHM = 'AES-GCM';\nexport const KEY_DIGEST_BYTES = 8;\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nconst encryptionMetaSchema = z.object({\n // version\n v: z.number(),\n // algorithm\n a: z.string(),\n // key digest\n k: z.string(),\n});\n\n/**\n * Load a raw encryption key into a CryptoKey object\n */\nexport async function loadKey(key: Uint8Array, keyUsages: KeyUsage[]): Promise<CryptoKey> {\n // Convert to ArrayBuffer for crypto.subtle compatibility\n const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength) as ArrayBuffer;\n return crypto.subtle.importKey('raw', keyBuffer, ALGORITHM, false, keyUsages);\n}\n\n/**\n * Get a digest of the encryption key for identification\n */\nexport async function getKeyDigest(key: Uint8Array): Promise<string> {\n // Convert to ArrayBuffer for crypto.subtle compatibility\n const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength) as ArrayBuffer;\n const rawDigest = await crypto.subtle.digest('SHA-256', keyBuffer);\n return new Uint8Array(rawDigest.slice(0, KEY_DIGEST_BYTES)).reduce(\n (acc, byte) => acc + byte.toString(16).padStart(2, '0'),\n '',\n );\n}\n\n/**\n * Encrypt data using AES-GCM\n */\nexport async function _encrypt(data: string, key: CryptoKey, keyDigest: string): Promise<string> {\n const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));\n const encrypted = await crypto.subtle.encrypt(\n {\n name: ALGORITHM,\n iv,\n },\n key,\n encoder.encode(data),\n );\n\n // combine IV and encrypted data into a single array of bytes\n const cipherBytes = [...iv, ...new Uint8Array(encrypted)];\n\n // encryption metadata\n const meta = { v: ENCRYPTER_VERSION, a: ALGORITHM, k: keyDigest };\n\n // convert concatenated result to base64 string\n return `${btoa(JSON.stringify(meta))}.${btoa(String.fromCharCode(...cipherBytes))}`;\n}\n\n/**\n * Decrypt data using AES-GCM\n */\nexport async function _decrypt(data: string, findKey: (digest: string) => Promise<CryptoKey[]>): Promise<string> {\n const [metaText, cipherText] = data.split('.');\n if (!metaText || !cipherText) {\n throw new Error('Malformed encrypted data');\n }\n\n let metaObj: unknown;\n try {\n metaObj = JSON.parse(atob(metaText));\n } catch {\n throw new Error('Malformed metadata');\n }\n\n // parse meta\n const { a: algorithm, k: keyDigest } = encryptionMetaSchema.parse(metaObj);\n\n // find a matching decryption key\n const keys = await findKey(keyDigest);\n if (keys.length === 0) {\n throw new Error('No matching decryption key found');\n }\n\n // convert base64 back to bytes\n const bytes = Uint8Array.from(atob(cipherText), (c) => c.charCodeAt(0));\n\n // extract IV from the head\n const iv = bytes.slice(0, IV_BYTES);\n const cipher = bytes.slice(IV_BYTES);\n let lastError: unknown;\n\n for (const key of keys) {\n let decrypted: ArrayBuffer;\n try {\n decrypted = await crypto.subtle.decrypt({ name: algorithm, iv }, key, cipher);\n } catch (err) {\n lastError = err;\n continue;\n }\n return decoder.decode(decrypted);\n }\n\n throw lastError;\n}\n","import { _decrypt, ENCRYPTION_KEY_BYTES, getKeyDigest, loadKey } from './utils.js';\n\n/**\n * Default decrypter with support for key rotation\n */\nexport class Decrypter {\n private keys: Array<{ key: CryptoKey; digest: string }> = [];\n\n constructor(private readonly decryptionKeys: Uint8Array[]) {\n if (decryptionKeys.length === 0) {\n throw new Error('At least one decryption key must be provided');\n }\n\n for (const key of decryptionKeys) {\n if (key.length !== ENCRYPTION_KEY_BYTES) {\n throw new Error(`Decryption key must be ${ENCRYPTION_KEY_BYTES} bytes`);\n }\n }\n }\n\n /**\n * Decrypts the given data\n */\n async decrypt(data: string): Promise<string> {\n if (this.keys.length === 0) {\n this.keys = await Promise.all(\n this.decryptionKeys.map(async (key) => ({\n key: await loadKey(key, ['decrypt']),\n digest: await getKeyDigest(key),\n })),\n );\n }\n\n return _decrypt(data, async (digest) =>\n this.keys.filter((entry) => entry.digest === digest).map((entry) => entry.key),\n );\n }\n}\n","import { _encrypt, ENCRYPTION_KEY_BYTES, getKeyDigest, loadKey } from './utils.js';\n\n/**\n * Default encrypter using AES-256-GCM\n */\nexport class Encrypter {\n private key: CryptoKey | undefined;\n private keyDigest: string | undefined;\n\n constructor(private readonly encryptionKey: Uint8Array) {\n if (encryptionKey.length !== ENCRYPTION_KEY_BYTES) {\n throw new Error(`Encryption key must be ${ENCRYPTION_KEY_BYTES} bytes`);\n }\n }\n\n /**\n * Encrypts the given data\n */\n async encrypt(data: string): Promise<string> {\n if (!this.key) {\n this.key = await loadKey(this.encryptionKey, ['encrypt']);\n }\n\n if (!this.keyDigest) {\n this.keyDigest = await getKeyDigest(this.encryptionKey);\n }\n\n return _encrypt(data, this.key, this.keyDigest);\n }\n}\n","import type { FieldDef } from '@zenstackhq/orm/schema';\n\n/**\n * Simple encryption configuration using built-in AES-256-GCM encryption\n */\nexport type SimpleEncryption = {\n /**\n * The encryption key (must be 32 bytes / 256 bits)\n */\n encryptionKey: Uint8Array;\n\n /**\n * Additional decryption keys for key rotation support.\n * When decrypting, all keys (encryptionKey + decryptionKeys) are tried.\n */\n decryptionKeys?: Uint8Array[];\n};\n\n/**\n * Custom encryption configuration for user-provided encryption handlers\n */\nexport type CustomEncryption = {\n /**\n * Custom encryption function\n * @param model The model name\n * @param field The field definition\n * @param plain The plaintext value to encrypt\n * @returns The encrypted value\n */\n encrypt: (model: string, field: FieldDef, plain: string) => Promise<string>;\n\n /**\n * Custom decryption function\n * @param model The model name\n * @param field The field definition\n * @param cipher The encrypted value to decrypt\n * @returns The decrypted value\n */\n decrypt: (model: string, field: FieldDef, cipher: string) => Promise<string>;\n};\n\n/**\n * Encryption configuration - either simple (built-in) or custom\n */\nexport type EncryptionConfig = SimpleEncryption | CustomEncryption;\n\n/**\n * Type guard to check if encryption config is custom\n */\nexport function isCustomEncryption(config: EncryptionConfig): config is CustomEncryption {\n return 'encrypt' in config && 'decrypt' in config;\n}\n","import { definePlugin } from '@zenstackhq/orm';\nimport type { FieldDef, ModelDef, SchemaDef } from '@zenstackhq/orm/schema';\nimport { Decrypter } from './decrypter.js';\nimport { Encrypter } from './encrypter.js';\nimport type { CustomEncryption, EncryptionConfig, SimpleEncryption } from './types.js';\nimport { isCustomEncryption } from './types.js';\n\nconst ENCRYPTED_ATTRIBUTE = '@encrypted';\n\n/**\n * Check if a field has the @encrypted attribute\n */\nfunction isEncryptedField(field: FieldDef): boolean {\n return field.attributes?.some((attr) => attr.name === ENCRYPTED_ATTRIBUTE) ?? false;\n}\n\n/**\n * Check if a model has any encrypted fields\n */\nfunction hasEncryptedFields(model: ModelDef): boolean {\n return Object.values(model.fields).some(isEncryptedField);\n}\n\n/**\n * Creates an encryption plugin for ZenStack ORM\n *\n * @param config Encryption configuration (simple or custom)\n * @returns A runtime plugin that handles field encryption/decryption\n */\nexport function createEncryptionPlugin<Schema extends SchemaDef>(config: EncryptionConfig) {\n let encrypter: Encrypter | undefined;\n let decrypter: Decrypter | undefined;\n let customEncryption: CustomEncryption | undefined;\n\n if (isCustomEncryption(config)) {\n customEncryption = config;\n } else {\n const simpleConfig = config as SimpleEncryption;\n encrypter = new Encrypter(simpleConfig.encryptionKey);\n const allDecryptionKeys = [simpleConfig.encryptionKey, ...(simpleConfig.decryptionKeys ?? [])];\n decrypter = new Decrypter(allDecryptionKeys);\n }\n\n async function encryptValue(model: string, field: FieldDef, value: string): Promise<string> {\n if (customEncryption) {\n return customEncryption.encrypt(model, field, value);\n }\n return encrypter!.encrypt(value);\n }\n\n async function decryptValue(model: string, field: FieldDef, value: string): Promise<string> {\n if (customEncryption) {\n return customEncryption.decrypt(model, field, value);\n }\n return decrypter!.decrypt(value);\n }\n\n /**\n * Recursively encrypt fields in write data\n */\n async function encryptWriteData(\n schema: SchemaDef,\n modelName: string,\n data: Record<string, unknown>,\n ): Promise<void> {\n const model = schema.models[modelName];\n if (!model) return;\n\n for (const [fieldName, value] of Object.entries(data)) {\n if (value === null || value === undefined || value === '') {\n continue;\n }\n\n const field = model.fields[fieldName];\n if (!field) continue;\n\n // Handle encrypted string fields\n if (isEncryptedField(field) && typeof value === 'string') {\n data[fieldName] = await encryptValue(modelName, field, value);\n continue;\n }\n\n // Handle relation fields (nested writes)\n if (field.relation && typeof value === 'object') {\n const relatedModel = field.type;\n await encryptNestedWrites(schema, relatedModel, value as Record<string, unknown>);\n }\n }\n }\n\n /**\n * Handle nested write operations (create, update, connect, etc.)\n */\n async function encryptNestedWrites(\n schema: SchemaDef,\n modelName: string,\n data: Record<string, unknown>,\n ): Promise<void> {\n // Handle create\n const createData = data['create'];\n if (createData) {\n if (Array.isArray(createData)) {\n for (const item of createData) {\n await encryptWriteData(schema, modelName, item as Record<string, unknown>);\n }\n } else {\n await encryptWriteData(schema, modelName, createData as Record<string, unknown>);\n }\n }\n\n // Handle createMany\n const createManyData = data['createMany'];\n if (createManyData && typeof createManyData === 'object') {\n const createManyItems = (createManyData as Record<string, unknown>)['data'];\n if (Array.isArray(createManyItems)) {\n for (const item of createManyItems) {\n await encryptWriteData(schema, modelName, item as Record<string, unknown>);\n }\n }\n }\n\n // Handle update\n const updateData = data['update'];\n if (updateData) {\n if (Array.isArray(updateData)) {\n for (const item of updateData) {\n const updateItem = item as Record<string, unknown>;\n const itemData = updateItem['data'];\n if (itemData) {\n await encryptWriteData(schema, modelName, itemData as Record<string, unknown>);\n }\n }\n } else {\n const updateObj = updateData as Record<string, unknown>;\n const nestedData = updateObj['data'];\n if (nestedData) {\n await encryptWriteData(schema, modelName, nestedData as Record<string, unknown>);\n } else {\n await encryptWriteData(schema, modelName, updateObj);\n }\n }\n }\n\n // Handle updateMany\n const updateManyData = data['updateMany'];\n if (updateManyData) {\n if (Array.isArray(updateManyData)) {\n for (const item of updateManyData) {\n const updateItem = item as Record<string, unknown>;\n const itemData = updateItem['data'];\n if (itemData) {\n await encryptWriteData(schema, modelName, itemData as Record<string, unknown>);\n }\n }\n } else {\n const updateObj = updateManyData as Record<string, unknown>;\n const nestedData = updateObj['data'];\n if (nestedData) {\n await encryptWriteData(schema, modelName, nestedData as Record<string, unknown>);\n }\n }\n }\n\n // Handle upsert\n const upsertData = data['upsert'];\n if (upsertData) {\n if (Array.isArray(upsertData)) {\n for (const item of upsertData) {\n const upsertItem = item as Record<string, unknown>;\n const createPart = upsertItem['create'];\n const updatePart = upsertItem['update'];\n if (createPart) {\n await encryptWriteData(schema, modelName, createPart as Record<string, unknown>);\n }\n if (updatePart) {\n await encryptWriteData(schema, modelName, updatePart as Record<string, unknown>);\n }\n }\n } else {\n const upsertObj = upsertData as Record<string, unknown>;\n const createPart = upsertObj['create'];\n const updatePart = upsertObj['update'];\n if (createPart) {\n await encryptWriteData(schema, modelName, createPart as Record<string, unknown>);\n }\n if (updatePart) {\n await encryptWriteData(schema, modelName, updatePart as Record<string, unknown>);\n }\n }\n }\n\n // Handle connectOrCreate\n const connectOrCreateData = data['connectOrCreate'];\n if (connectOrCreateData) {\n if (Array.isArray(connectOrCreateData)) {\n for (const item of connectOrCreateData) {\n const cocItem = item as Record<string, unknown>;\n const createPart = cocItem['create'];\n if (createPart) {\n await encryptWriteData(schema, modelName, createPart as Record<string, unknown>);\n }\n }\n } else {\n const cocObj = connectOrCreateData as Record<string, unknown>;\n const createPart = cocObj['create'];\n if (createPart) {\n await encryptWriteData(schema, modelName, createPart as Record<string, unknown>);\n }\n }\n }\n }\n\n /**\n * Recursively decrypt fields in result data\n */\n async function decryptResultData(\n schema: SchemaDef,\n modelName: string,\n data: Record<string, unknown>,\n ): Promise<void> {\n const model = schema.models[modelName];\n if (!model) return;\n\n for (const [fieldName, value] of Object.entries(data)) {\n if (value === null || value === undefined || value === '') {\n continue;\n }\n\n const field = model.fields[fieldName];\n if (!field) continue;\n\n // Handle encrypted string fields\n if (isEncryptedField(field) && typeof value === 'string') {\n try {\n data[fieldName] = await decryptValue(modelName, field, value);\n } catch (error) {\n // If decryption fails, log warning and keep original value\n console.warn(`Failed to decrypt field ${modelName}.${fieldName}:`, error);\n }\n continue;\n }\n\n // Handle relation fields (nested data)\n if (field.relation && value !== null) {\n const relatedModel = field.type;\n if (Array.isArray(value)) {\n for (const item of value) {\n if (typeof item === 'object' && item !== null) {\n await decryptResultData(schema, relatedModel, item as Record<string, unknown>);\n }\n }\n } else if (typeof value === 'object') {\n await decryptResultData(schema, relatedModel, value as Record<string, unknown>);\n }\n }\n }\n }\n\n return definePlugin<Schema, {}, {}>({\n id: 'encryption',\n name: 'Encryption Plugin',\n description: 'Automatically encrypts and decrypts fields marked with @encrypted',\n\n onQuery: async (ctx) => {\n const { model, operation, args, proceed, client } = ctx;\n const schema = (client as any).schema as SchemaDef;\n const modelDef = schema.models[model];\n\n // Check if this model has any encrypted fields\n if (!modelDef || !hasEncryptedFields(modelDef)) {\n return proceed(args);\n }\n\n // Clone args to avoid mutating original\n const processedArgs = args ? JSON.parse(JSON.stringify(args)) : undefined;\n\n // Handle write operations - encrypt data before writing\n if (\n operation === 'create' ||\n operation === 'update' ||\n operation === 'upsert' ||\n operation === 'createMany' ||\n operation === 'updateMany' ||\n operation === 'createManyAndReturn'\n ) {\n if (processedArgs?.data) {\n if (Array.isArray(processedArgs.data)) {\n for (const item of processedArgs.data) {\n await encryptWriteData(schema, model, item);\n }\n } else {\n await encryptWriteData(schema, model, processedArgs.data);\n }\n }\n\n // Handle upsert create/update\n if (operation === 'upsert') {\n if (processedArgs?.create) {\n await encryptWriteData(schema, model, processedArgs.create);\n }\n if (processedArgs?.update) {\n await encryptWriteData(schema, model, processedArgs.update);\n }\n }\n }\n\n // Execute the query\n const result = await proceed(processedArgs);\n\n // Handle read operations - decrypt data after reading\n if (result !== null && result !== undefined) {\n if (Array.isArray(result)) {\n for (const item of result) {\n if (typeof item === 'object' && item !== null) {\n await decryptResultData(schema, model, item as Record<string, unknown>);\n }\n }\n } else if (typeof result === 'object') {\n await decryptResultData(schema, model, result as Record<string, unknown>);\n }\n }\n\n return result;\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/utils.ts","../src/decrypter.ts","../src/encrypter.ts","../src/types.ts","../src/plugin.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const ENCRYPTER_VERSION = 1;\nexport const ENCRYPTION_KEY_BYTES = 32;\nexport const IV_BYTES = 12;\nexport const ALGORITHM = 'AES-GCM';\nexport const KEY_DIGEST_BYTES = 8;\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nconst encryptionMetaSchema = z.object({\n // version\n v: z.number(),\n // algorithm\n a: z.string(),\n // key digest\n k: z.string(),\n});\n\n/**\n * Resolve a key input to a Uint8Array. If the input is a string, it is\n * derived to 32 bytes via SHA-256. If it is already a Uint8Array, its\n * length is validated.\n */\nexport async function deriveKey(input: string | Uint8Array): Promise<Uint8Array> {\n if (typeof input === 'string') {\n const encoded = new TextEncoder().encode(input);\n return new Uint8Array(await crypto.subtle.digest('SHA-256', encoded));\n }\n if (input.length !== ENCRYPTION_KEY_BYTES) {\n throw new Error(`Encryption key must be ${ENCRYPTION_KEY_BYTES} bytes`);\n }\n return input;\n}\n\n/**\n * Load a raw encryption key into a CryptoKey object\n */\nexport async function loadKey(key: Uint8Array, keyUsages: KeyUsage[]): Promise<CryptoKey> {\n // Convert to ArrayBuffer for crypto.subtle compatibility\n const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength) as ArrayBuffer;\n return crypto.subtle.importKey('raw', keyBuffer, ALGORITHM, false, keyUsages);\n}\n\n/**\n * Get a digest of the encryption key for identification\n */\nexport async function getKeyDigest(key: Uint8Array): Promise<string> {\n // Convert to ArrayBuffer for crypto.subtle compatibility\n const keyBuffer = key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength) as ArrayBuffer;\n const rawDigest = await crypto.subtle.digest('SHA-256', keyBuffer);\n return new Uint8Array(rawDigest.slice(0, KEY_DIGEST_BYTES)).reduce(\n (acc, byte) => acc + byte.toString(16).padStart(2, '0'),\n '',\n );\n}\n\n/**\n * Encrypt data using AES-GCM\n */\nexport async function _encrypt(data: string, key: CryptoKey, keyDigest: string): Promise<string> {\n const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));\n const encrypted = await crypto.subtle.encrypt(\n {\n name: ALGORITHM,\n iv,\n },\n key,\n encoder.encode(data),\n );\n\n // combine IV and encrypted data into a single array of bytes\n const cipherBytes = [...iv, ...new Uint8Array(encrypted)];\n\n // encryption metadata\n const meta = { v: ENCRYPTER_VERSION, a: ALGORITHM, k: keyDigest };\n\n // convert concatenated result to base64 string\n return `${btoa(JSON.stringify(meta))}.${btoa(String.fromCharCode(...cipherBytes))}`;\n}\n\n/**\n * Decrypt data using AES-GCM\n */\nexport async function _decrypt(data: string, findKey: (digest: string) => Promise<CryptoKey[]>): Promise<string> {\n const [metaText, cipherText] = data.split('.');\n if (!metaText || !cipherText) {\n throw new Error('Malformed encrypted data');\n }\n\n let metaObj: unknown;\n try {\n metaObj = JSON.parse(atob(metaText));\n } catch {\n throw new Error('Malformed metadata');\n }\n\n // parse meta\n const { a: algorithm, k: keyDigest } = encryptionMetaSchema.parse(metaObj);\n\n // find a matching decryption key\n const keys = await findKey(keyDigest);\n if (keys.length === 0) {\n throw new Error('No matching decryption key found');\n }\n\n // convert base64 back to bytes\n const bytes = Uint8Array.from(atob(cipherText), (c) => c.charCodeAt(0));\n\n // extract IV from the head\n const iv = bytes.slice(0, IV_BYTES);\n const cipher = bytes.slice(IV_BYTES);\n let lastError: unknown;\n\n for (const key of keys) {\n let decrypted: ArrayBuffer;\n try {\n decrypted = await crypto.subtle.decrypt({ name: algorithm, iv }, key, cipher);\n } catch (err) {\n lastError = err;\n continue;\n }\n return decoder.decode(decrypted);\n }\n\n throw lastError instanceof Error\n ? lastError\n : new Error('Decryption failed with all available keys');\n}\n","import { _decrypt, ENCRYPTION_KEY_BYTES, getKeyDigest, loadKey } from './utils.js';\n\n/**\n * Default decrypter with support for key rotation\n */\nexport class Decrypter {\n private keys: Array<{ key: CryptoKey; digest: string }> = [];\n private initPromise: Promise<void> | undefined;\n\n constructor(private readonly decryptionKeys: Uint8Array[]) {\n if (decryptionKeys.length === 0) {\n throw new Error('At least one decryption key must be provided');\n }\n\n for (const key of decryptionKeys) {\n if (key.length !== ENCRYPTION_KEY_BYTES) {\n throw new Error(`Decryption key must be ${ENCRYPTION_KEY_BYTES} bytes`);\n }\n }\n }\n\n private async ensureKeys(): Promise<void> {\n if (this.keys.length > 0) return;\n if (this.initPromise) return this.initPromise;\n\n this.initPromise = (async () => {\n this.keys = await Promise.all(\n this.decryptionKeys.map(async (key) => ({\n key: await loadKey(key, ['decrypt']),\n digest: await getKeyDigest(key),\n })),\n );\n })();\n\n return this.initPromise;\n }\n\n /**\n * Decrypts the given data\n */\n async decrypt(data: string): Promise<string> {\n await this.ensureKeys();\n\n return _decrypt(data, async (digest) =>\n this.keys.filter((entry) => entry.digest === digest).map((entry) => entry.key),\n );\n }\n}\n","import { _encrypt, ENCRYPTION_KEY_BYTES, getKeyDigest, loadKey } from './utils.js';\n\n/**\n * Default encrypter using AES-256-GCM\n */\nexport class Encrypter {\n private key: CryptoKey | undefined;\n private keyDigest: string | undefined;\n\n constructor(private readonly encryptionKey: Uint8Array) {\n if (encryptionKey.length !== ENCRYPTION_KEY_BYTES) {\n throw new Error(`Encryption key must be ${ENCRYPTION_KEY_BYTES} bytes`);\n }\n }\n\n /**\n * Encrypts the given data\n */\n async encrypt(data: string): Promise<string> {\n if (!this.key) {\n this.key = await loadKey(this.encryptionKey, ['encrypt']);\n }\n\n if (!this.keyDigest) {\n this.keyDigest = await getKeyDigest(this.encryptionKey);\n }\n\n return _encrypt(data, this.key, this.keyDigest);\n }\n}\n","import type { FieldDef } from '@zenstackhq/orm/schema';\n\n/**\n * Simple encryption configuration using built-in AES-256-GCM encryption\n */\nexport type SimpleEncryption = {\n /**\n * The encryption key. Pass a Uint8Array of exactly 32 bytes, or a string\n * which will be derived to a 32-byte key via SHA-256.\n */\n encryptionKey: string | Uint8Array;\n\n /**\n * Additional decryption keys for key rotation support.\n * When decrypting, all keys (encryptionKey + decryptionKeys) are tried.\n * Each key can be a Uint8Array (32 bytes) or a string (derived via SHA-256).\n */\n decryptionKeys?: (string | Uint8Array)[];\n};\n\n/**\n * Custom encryption configuration for user-provided encryption handlers\n */\nexport type CustomEncryption = {\n /**\n * Custom encryption function\n * @param model The model name\n * @param field The field definition\n * @param plain The plaintext value to encrypt\n * @returns The encrypted value\n */\n encrypt: (model: string, field: FieldDef, plain: string) => Promise<string>;\n\n /**\n * Custom decryption function\n * @param model The model name\n * @param field The field definition\n * @param cipher The encrypted value to decrypt\n * @returns The decrypted value\n */\n decrypt: (model: string, field: FieldDef, cipher: string) => Promise<string>;\n};\n\n/**\n * Encryption configuration - either simple (built-in) or custom\n */\nexport type EncryptionConfig = SimpleEncryption | CustomEncryption;\n\n/**\n * Type guard to check if encryption config is custom\n */\nexport function isCustomEncryption(config: EncryptionConfig): config is CustomEncryption {\n return 'encrypt' in config && 'decrypt' in config;\n}\n","import { definePlugin } from '@zenstackhq/orm';\nimport type { FieldDef, ModelDef, SchemaDef } from '@zenstackhq/orm/schema';\nimport { Decrypter } from './decrypter.js';\nimport { Encrypter } from './encrypter.js';\nimport type { CustomEncryption, EncryptionConfig, SimpleEncryption } from './types.js';\nimport { isCustomEncryption } from './types.js';\nimport { deriveKey } from './utils.js';\n\nconst ENCRYPTED_ATTRIBUTE = '@encrypted';\nconst warnedNonStringFields = new Set<string>();\n\n/**\n * Check if a field has the @encrypted attribute\n */\nfunction isEncryptedField(field: FieldDef): boolean {\n return field.attributes?.some((attr) => attr.name === ENCRYPTED_ATTRIBUTE) ?? false;\n}\n\n/**\n * Warn once if @encrypted is applied to a non-String field\n */\nfunction warnIfNonStringEncrypted(modelName: string, fieldName: string, field: FieldDef): void {\n if (isEncryptedField(field) && field.type !== 'String') {\n const key = `${modelName}.${fieldName}`;\n if (!warnedNonStringFields.has(key)) {\n warnedNonStringFields.add(key);\n console.warn(\n `@encrypted is only supported on String fields. ${key} (type: ${field.type}) will be ignored.`,\n );\n }\n }\n}\n\n/**\n * Check if a model has any encrypted fields\n */\nfunction hasEncryptedFields(model: ModelDef): boolean {\n return Object.values(model.fields).some(isEncryptedField);\n}\n\n/**\n * Creates an encryption plugin for ZenStack ORM\n *\n * @param config Encryption configuration (simple or custom)\n * @returns A runtime plugin that handles field encryption/decryption\n */\nexport function createEncryptionPlugin<Schema extends SchemaDef>(config: EncryptionConfig) {\n let encrypter: Encrypter | undefined;\n let decrypter: Decrypter | undefined;\n let customEncryption: CustomEncryption | undefined;\n let initialized = false;\n let initPromise: Promise<void> | undefined;\n\n async function ensureInitialized() {\n if (initialized) return;\n if (initPromise) return initPromise;\n\n initPromise = (async () => {\n if (isCustomEncryption(config)) {\n customEncryption = config;\n } else {\n const simpleConfig = config as SimpleEncryption;\n const encryptionKey = await deriveKey(simpleConfig.encryptionKey);\n const decryptionKeys = await Promise.all(\n (simpleConfig.decryptionKeys ?? []).map(deriveKey),\n );\n encrypter = new Encrypter(encryptionKey);\n decrypter = new Decrypter([encryptionKey, ...decryptionKeys]);\n }\n initialized = true;\n })();\n\n return initPromise;\n }\n\n async function encryptValue(model: string, field: FieldDef, value: string): Promise<string> {\n if (customEncryption) {\n return customEncryption.encrypt(model, field, value);\n }\n return encrypter!.encrypt(value);\n }\n\n async function decryptValue(model: string, field: FieldDef, value: string): Promise<string> {\n if (customEncryption) {\n return customEncryption.decrypt(model, field, value);\n }\n return decrypter!.decrypt(value);\n }\n\n /**\n * Recursively encrypt fields in write data\n */\n async function encryptWriteData(\n schema: SchemaDef,\n modelName: string,\n data: Record<string, unknown>,\n ): Promise<void> {\n const model = schema.models[modelName];\n if (!model) return;\n\n for (const [fieldName, value] of Object.entries(data)) {\n if (value === null || value === undefined) {\n continue;\n }\n\n const field = model.fields[fieldName];\n if (!field) continue;\n\n warnIfNonStringEncrypted(modelName, fieldName, field);\n\n // Handle encrypted string fields\n if (isEncryptedField(field) && typeof value === 'string') {\n data[fieldName] = await encryptValue(modelName, field, value);\n continue;\n }\n\n // Handle relation fields (nested writes)\n if (field.relation && typeof value === 'object') {\n const relatedModel = field.type;\n await encryptNestedWrites(schema, relatedModel, value as Record<string, unknown>);\n }\n }\n }\n\n /**\n * Handle nested write operations (create, update, connect, etc.)\n */\n async function encryptNestedWrites(\n schema: SchemaDef,\n modelName: string,\n data: Record<string, unknown>,\n ): Promise<void> {\n // Handle create\n const createData = data['create'];\n if (createData) {\n if (Array.isArray(createData)) {\n for (const item of createData) {\n await encryptWriteData(schema, modelName, item as Record<string, unknown>);\n }\n } else {\n await encryptWriteData(schema, modelName, createData as Record<string, unknown>);\n }\n }\n\n // Handle createMany\n const createManyData = data['createMany'];\n if (createManyData && typeof createManyData === 'object') {\n const createManyItems = (createManyData as Record<string, unknown>)['data'];\n if (Array.isArray(createManyItems)) {\n for (const item of createManyItems) {\n await encryptWriteData(schema, modelName, item as Record<string, unknown>);\n }\n }\n }\n\n // Handle update\n const updateData = data['update'];\n if (updateData) {\n if (Array.isArray(updateData)) {\n for (const item of updateData) {\n const updateItem = item as Record<string, unknown>;\n const itemData = updateItem['data'];\n if (itemData) {\n await encryptWriteData(schema, modelName, itemData as Record<string, unknown>);\n }\n }\n } else {\n const updateObj = updateData as Record<string, unknown>;\n const nestedData = updateObj['data'];\n if (nestedData) {\n await encryptWriteData(schema, modelName, nestedData as Record<string, unknown>);\n }\n }\n }\n\n // Handle updateMany\n const updateManyData = data['updateMany'];\n if (updateManyData) {\n if (Array.isArray(updateManyData)) {\n for (const item of updateManyData) {\n const updateItem = item as Record<string, unknown>;\n const itemData = updateItem['data'];\n if (itemData) {\n await encryptWriteData(schema, modelName, itemData as Record<string, unknown>);\n }\n }\n } else {\n const updateObj = updateManyData as Record<string, unknown>;\n const nestedData = updateObj['data'];\n if (nestedData) {\n await encryptWriteData(schema, modelName, nestedData as Record<string, unknown>);\n }\n }\n }\n\n // Handle upsert\n const upsertData = data['upsert'];\n if (upsertData) {\n if (Array.isArray(upsertData)) {\n for (const item of upsertData) {\n const upsertItem = item as Record<string, unknown>;\n const createPart = upsertItem['create'];\n const updatePart = upsertItem['update'];\n if (createPart) {\n await encryptWriteData(schema, modelName, createPart as Record<string, unknown>);\n }\n if (updatePart) {\n await encryptWriteData(schema, modelName, updatePart as Record<string, unknown>);\n }\n }\n } else {\n const upsertObj = upsertData as Record<string, unknown>;\n const createPart = upsertObj['create'];\n const updatePart = upsertObj['update'];\n if (createPart) {\n await encryptWriteData(schema, modelName, createPart as Record<string, unknown>);\n }\n if (updatePart) {\n await encryptWriteData(schema, modelName, updatePart as Record<string, unknown>);\n }\n }\n }\n\n // Handle connectOrCreate\n const connectOrCreateData = data['connectOrCreate'];\n if (connectOrCreateData) {\n if (Array.isArray(connectOrCreateData)) {\n for (const item of connectOrCreateData) {\n const cocItem = item as Record<string, unknown>;\n const createPart = cocItem['create'];\n if (createPart) {\n await encryptWriteData(schema, modelName, createPart as Record<string, unknown>);\n }\n }\n } else {\n const cocObj = connectOrCreateData as Record<string, unknown>;\n const createPart = cocObj['create'];\n if (createPart) {\n await encryptWriteData(schema, modelName, createPart as Record<string, unknown>);\n }\n }\n }\n }\n\n /**\n * Recursively decrypt fields in result data\n */\n async function decryptResultData(\n schema: SchemaDef,\n modelName: string,\n data: Record<string, unknown>,\n ): Promise<void> {\n const model = schema.models[modelName];\n if (!model) return;\n\n for (const [fieldName, value] of Object.entries(data)) {\n if (value === null || value === undefined) {\n continue;\n }\n\n const field = model.fields[fieldName];\n if (!field) continue;\n\n // Handle encrypted string fields\n if (isEncryptedField(field) && typeof value === 'string') {\n try {\n data[fieldName] = await decryptValue(modelName, field, value);\n } catch {\n // If decryption fails, keep original value\n console.warn('Failed to decrypt an encrypted field');\n }\n continue;\n }\n\n // Handle relation fields (nested data)\n if (field.relation && value !== null) {\n const relatedModel = field.type;\n if (Array.isArray(value)) {\n for (const item of value) {\n if (typeof item === 'object' && item !== null) {\n await decryptResultData(schema, relatedModel, item as Record<string, unknown>);\n }\n }\n } else if (typeof value === 'object') {\n await decryptResultData(schema, relatedModel, value as Record<string, unknown>);\n }\n }\n }\n }\n\n return definePlugin<Schema, Record<string, never>, Record<string, never>>({\n id: 'encryption',\n name: 'Encryption Plugin',\n description: 'Automatically encrypts and decrypts fields marked with @encrypted',\n\n onQuery: async (ctx) => {\n await ensureInitialized();\n const { model, operation, args, proceed, client } = ctx;\n const schema = (client as unknown as { schema: SchemaDef }).schema;\n const modelDef = schema.models[model];\n\n // Check if this model has any encrypted fields\n if (!modelDef || !hasEncryptedFields(modelDef)) {\n return proceed(args);\n }\n\n // Handle write operations - encrypt data before writing\n const isWrite =\n operation === 'create' ||\n operation === 'update' ||\n operation === 'upsert' ||\n operation === 'createMany' ||\n operation === 'updateMany' ||\n operation === 'createManyAndReturn';\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let processedArgs = args as Record<string, any> | undefined;\n\n if (isWrite) {\n // Clone args to avoid mutating original\n processedArgs = args ? JSON.parse(JSON.stringify(args)) : undefined;\n\n if (processedArgs?.data) {\n if (Array.isArray(processedArgs.data)) {\n for (const item of processedArgs.data) {\n await encryptWriteData(schema, model, item);\n }\n } else {\n await encryptWriteData(schema, model, processedArgs.data);\n }\n }\n\n // Handle upsert create/update\n if (operation === 'upsert') {\n if (processedArgs?.create) {\n await encryptWriteData(schema, model, processedArgs.create);\n }\n if (processedArgs?.update) {\n await encryptWriteData(schema, model, processedArgs.update);\n }\n }\n }\n\n // Execute the query\n const result = await proceed(processedArgs);\n\n // Handle read operations - decrypt data after reading\n if (result !== null && result !== undefined) {\n if (Array.isArray(result)) {\n for (const item of result) {\n if (typeof item === 'object' && item !== null) {\n await decryptResultData(schema, model, item as Record<string, unknown>);\n }\n }\n } else if (typeof result === 'object') {\n await decryptResultData(schema, model, result as Record<string, unknown>);\n }\n }\n\n return result;\n },\n });\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,60 +1,63 @@
1
1
  {
2
- "name": "zenstack-encryption",
3
- "version": "0.1.1",
4
- "description": "ZenStack Encryption Plugin - Automatic field encryption/decryption for @encrypted fields",
5
- "keywords": [
6
- "zenstack",
7
- "encryption",
8
- "aes",
9
- "crypto",
10
- "plugin"
11
- ],
12
- "homepage": "https://github.com/genu/zenstack-encryption#readme",
13
- "bugs": {
14
- "url": "https://github.com/genu/zenstack-encryption/issues"
15
- },
16
- "license": "MIT",
17
- "repository": {
18
- "type": "git",
19
- "url": "https://github.com/genu/zenstack-encryption.git"
20
- },
21
- "files": [
22
- "dist",
23
- "plugin.zmodel",
24
- "LICENSE",
25
- "README.md"
26
- ],
27
- "type": "module",
28
- "sideEffects": false,
29
- "exports": {
30
- ".": {
31
- "types": "./dist/index.d.mts",
32
- "default": "./dist/index.mjs"
33
- },
34
- "./plugin.zmodel": "./plugin.zmodel",
35
- "./package.json": "./package.json"
36
- },
37
- "publishConfig": {
38
- "access": "public"
39
- },
40
- "peerDependencies": {
41
- "@zenstackhq/orm": ">=3.3.0"
42
- },
43
- "dependencies": {
44
- "zod": "4.0.0"
45
- },
46
- "devDependencies": {
47
- "@zenstackhq/orm": "3.3.2",
48
- "eslint": "9.0.0",
49
- "tsdown": "0.20.0",
50
- "typescript": "5.9.0",
51
- "typescript-eslint": "8.0.0",
52
- "vitest": "3.0.0"
53
- },
54
- "scripts": {
55
- "build": "tsdown",
56
- "watch": "tsdown --watch",
57
- "lint": "eslint .",
58
- "test": "vitest run"
59
- }
60
- }
2
+ "name": "zenstack-encryption",
3
+ "version": "0.1.3",
4
+ "description": "ZenStack Encryption Plugin - Automatic field encryption/decryption for @encrypted fields",
5
+ "keywords": [
6
+ "zenstack",
7
+ "encryption",
8
+ "aes",
9
+ "crypto",
10
+ "plugin"
11
+ ],
12
+ "homepage": "https://github.com/genu/zenstack-encryption#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/genu/zenstack-encryption/issues"
15
+ },
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/genu/zenstack-encryption.git"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "plugin.zmodel",
24
+ "LICENSE",
25
+ "README.md"
26
+ ],
27
+ "type": "module",
28
+ "sideEffects": false,
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.mts",
32
+ "default": "./dist/index.mjs"
33
+ },
34
+ "./plugin.zmodel": "./plugin.zmodel",
35
+ "./package.json": "./package.json"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "build": "tsdown",
42
+ "watch": "tsdown --watch",
43
+ "lint": "eslint .",
44
+ "test": "vitest run",
45
+ "prepublishOnly": "pnpm run build",
46
+ "prepack": "pnpm run build"
47
+ },
48
+ "peerDependencies": {
49
+ "@zenstackhq/orm": ">=3.3.0"
50
+ },
51
+ "dependencies": {
52
+ "zod": "4.3.6"
53
+ },
54
+ "devDependencies": {
55
+ "@zenstackhq/orm": "3.3.3",
56
+ "eslint": "10.0.0",
57
+ "tsdown": "0.20.3",
58
+ "typescript": "5.9.3",
59
+ "typescript-eslint": "8.54.0",
60
+ "vitest": "4.0.18"
61
+ },
62
+ "packageManager": "pnpm@10.29.1"
63
+ }