@happyvertical/secrets 0.80.0 → 0.80.2

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/dist/index.js CHANGED
@@ -1,321 +1,372 @@
1
1
  import { syncSchema } from "@happyvertical/sql";
2
2
  import { createId } from "@happyvertical/utils";
3
3
  import * as crypto from "node:crypto";
4
- class EnvelopeEncryption {
5
- static ALGORITHM = "aes-256-gcm";
6
- static KEY_LENGTH = 32;
7
- // 256 bits
8
- static IV_LENGTH = 12;
9
- // 96 bits for GCM (recommended)
10
- static AUTH_TAG_LENGTH = 16;
11
- // 128 bits
12
- /**
13
- * Generate a new random data encryption key
14
- *
15
- * @returns A 32-byte (256-bit) random key
16
- */
17
- static generateDataKey() {
18
- return crypto.randomBytes(EnvelopeEncryption.KEY_LENGTH);
19
- }
20
- /**
21
- * Generate a random IV for encryption
22
- *
23
- * @returns A 12-byte (96-bit) random IV
24
- */
25
- static generateIV() {
26
- return crypto.randomBytes(EnvelopeEncryption.IV_LENGTH);
27
- }
28
- /**
29
- * Wrap (encrypt) a data encryption key with a master key
30
- *
31
- * @param dataKey - The data key to wrap (32 bytes)
32
- * @param masterKey - The master key to wrap with (32 bytes)
33
- * @returns Object containing wrapped key, IV, and auth tag (all base64-encoded)
34
- */
35
- static wrapKey(dataKey, masterKey) {
36
- EnvelopeEncryption.validateKeyLength(dataKey);
37
- EnvelopeEncryption.validateKeyLength(masterKey);
38
- const iv = EnvelopeEncryption.generateIV();
39
- const cipher = crypto.createCipheriv(
40
- EnvelopeEncryption.ALGORITHM,
41
- masterKey,
42
- iv
43
- );
44
- const encrypted = Buffer.concat([cipher.update(dataKey), cipher.final()]);
45
- const authTag = cipher.getAuthTag();
46
- return {
47
- wrappedKey: encrypted.toString("base64"),
48
- iv: iv.toString("base64"),
49
- authTag: authTag.toString("base64")
50
- };
51
- }
52
- /**
53
- * Unwrap (decrypt) a data encryption key with a master key
54
- *
55
- * @param wrappedKey - Base64-encoded wrapped key
56
- * @param iv - Base64-encoded IV
57
- * @param authTag - Base64-encoded authentication tag
58
- * @param masterKey - The master key to unwrap with (32 bytes)
59
- * @returns The unwrapped data key (32 bytes)
60
- * @throws Error if decryption fails (invalid key, tampered data, etc.)
61
- */
62
- static unwrapKey(wrappedKey, iv, authTag, masterKey) {
63
- EnvelopeEncryption.validateKeyLength(masterKey);
64
- const decipher = crypto.createDecipheriv(
65
- EnvelopeEncryption.ALGORITHM,
66
- masterKey,
67
- Buffer.from(iv, "base64")
68
- );
69
- decipher.setAuthTag(Buffer.from(authTag, "base64"));
70
- return Buffer.concat([
71
- decipher.update(Buffer.from(wrappedKey, "base64")),
72
- decipher.final()
73
- ]);
74
- }
75
- /**
76
- * Encrypt data with a data encryption key
77
- *
78
- * @param plaintext - The plaintext string to encrypt
79
- * @param dataKey - The data encryption key (32 bytes)
80
- * @returns Object containing ciphertext, IV, and auth tag (all base64-encoded)
81
- */
82
- static encryptData(plaintext, dataKey) {
83
- EnvelopeEncryption.validateKeyLength(dataKey);
84
- const iv = EnvelopeEncryption.generateIV();
85
- const cipher = crypto.createCipheriv(
86
- EnvelopeEncryption.ALGORITHM,
87
- dataKey,
88
- iv
89
- );
90
- const encrypted = Buffer.concat([
91
- cipher.update(plaintext, "utf8"),
92
- cipher.final()
93
- ]);
94
- const authTag = cipher.getAuthTag();
95
- return {
96
- ciphertext: encrypted.toString("base64"),
97
- iv: iv.toString("base64"),
98
- authTag: authTag.toString("base64")
99
- };
100
- }
101
- /**
102
- * Decrypt data with a data encryption key
103
- *
104
- * @param ciphertext - Base64-encoded ciphertext
105
- * @param iv - Base64-encoded IV
106
- * @param authTag - Base64-encoded authentication tag
107
- * @param dataKey - The data encryption key (32 bytes)
108
- * @returns The decrypted plaintext string
109
- * @throws Error if decryption fails (invalid key, tampered data, etc.)
110
- */
111
- static decryptData(ciphertext, iv, authTag, dataKey) {
112
- EnvelopeEncryption.validateKeyLength(dataKey);
113
- const decipher = crypto.createDecipheriv(
114
- EnvelopeEncryption.ALGORITHM,
115
- dataKey,
116
- Buffer.from(iv, "base64")
117
- );
118
- decipher.setAuthTag(Buffer.from(authTag, "base64"));
119
- return Buffer.concat([
120
- decipher.update(Buffer.from(ciphertext, "base64")),
121
- decipher.final()
122
- ]).toString("utf8");
123
- }
124
- /**
125
- * Create a full encrypted envelope
126
- *
127
- * This combines key wrapping and data encryption into a single envelope
128
- * that can be stored and later decrypted.
129
- *
130
- * @param plaintext - The plaintext string to encrypt
131
- * @param dataKey - The data encryption key (32 bytes)
132
- * @param wrappedKeyInfo - The wrapped key info (from wrapKey)
133
- * @param amkKeyId - The ID of the AMK used to wrap the key
134
- * @param metadata - Optional metadata to include in the envelope
135
- * @returns A complete encrypted envelope
136
- */
137
- static createEnvelope(plaintext, dataKey, wrappedKeyInfo, amkKeyId, metadata) {
138
- const encrypted = EnvelopeEncryption.encryptData(plaintext, dataKey);
139
- const combinedWrappedKey = `${wrappedKeyInfo.wrappedKey}:${wrappedKeyInfo.iv}:${wrappedKeyInfo.authTag}`;
140
- return {
141
- version: 1,
142
- algorithm: "aes-256-gcm",
143
- wrappedKey: combinedWrappedKey,
144
- amkKeyId,
145
- iv: encrypted.iv,
146
- authTag: encrypted.authTag,
147
- ciphertext: encrypted.ciphertext,
148
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
149
- metadata
150
- };
151
- }
152
- /**
153
- * Parse a combined wrapped key string
154
- *
155
- * @param combinedWrappedKey - The combined wrapped key string (wrappedKey:iv:authTag)
156
- * @returns Object with wrappedKey, iv, and authTag
157
- */
158
- static parseWrappedKey(combinedWrappedKey) {
159
- const parts = combinedWrappedKey.split(":");
160
- if (parts.length !== 3) {
161
- throw new Error(
162
- "Invalid wrapped key format: expected wrappedKey:iv:authTag"
163
- );
164
- }
165
- const [wrappedKey, iv, authTag] = parts;
166
- if (!wrappedKey || !iv || !authTag) {
167
- throw new Error(
168
- "Invalid wrapped key format: wrappedKey, iv, and authTag must all be non-empty"
169
- );
170
- }
171
- return { wrappedKey, iv, authTag };
172
- }
173
- /**
174
- * Decrypt a full envelope
175
- *
176
- * @param envelope - The encrypted envelope
177
- * @param masterKey - The master key to unwrap the data key
178
- * @returns The decrypted plaintext string
179
- */
180
- static decryptEnvelope(envelope, masterKey) {
181
- const {
182
- wrappedKey,
183
- iv: keyIv,
184
- authTag: keyAuthTag
185
- } = EnvelopeEncryption.parseWrappedKey(envelope.wrappedKey);
186
- const dataKey = EnvelopeEncryption.unwrapKey(
187
- wrappedKey,
188
- keyIv,
189
- keyAuthTag,
190
- masterKey
191
- );
192
- return EnvelopeEncryption.decryptData(
193
- envelope.ciphertext,
194
- envelope.iv,
195
- envelope.authTag,
196
- dataKey
197
- );
198
- }
199
- /**
200
- * Validate that a key is the correct length
201
- *
202
- * @param key - The key to validate
203
- * @param expectedLength - Expected length in bytes (default: 32)
204
- * @throws Error if key length is invalid
205
- */
206
- static validateKeyLength(key, expectedLength = EnvelopeEncryption.KEY_LENGTH) {
207
- if (key.length !== expectedLength) {
208
- throw new Error(
209
- `Invalid key length: expected ${expectedLength} bytes, got ${key.length} bytes`
210
- );
211
- }
212
- }
213
- /**
214
- * Parse a hex-encoded key from string
215
- *
216
- * @param hexKey - Hex-encoded key string (64 chars for 32 bytes)
217
- * @returns The key as a Buffer
218
- * @throws Error if the hex string is invalid
219
- */
220
- static parseHexKey(hexKey) {
221
- if (!/^[0-9a-fA-F]+$/.test(hexKey)) {
222
- throw new Error("Invalid hex key: contains non-hex characters");
223
- }
224
- const key = Buffer.from(hexKey, "hex");
225
- EnvelopeEncryption.validateKeyLength(key);
226
- return key;
227
- }
228
- }
229
- class SecretError extends Error {
230
- code;
231
- adapterType;
232
- cause;
233
- constructor(message, code, options) {
234
- super(message);
235
- this.name = "SecretError";
236
- this.code = code;
237
- this.adapterType = options?.adapterType;
238
- this.cause = options?.cause;
239
- }
240
- }
241
- class KeyNotFoundError extends SecretError {
242
- constructor(keyId, adapterType) {
243
- super(`Key not found: ${keyId}`, "KEY_NOT_FOUND", { adapterType });
244
- this.name = "KeyNotFoundError";
245
- }
246
- }
247
- class DecryptionError extends SecretError {
248
- constructor(message, adapterType, cause) {
249
- super(message, "DECRYPTION_FAILED", { adapterType, cause });
250
- this.name = "DecryptionError";
251
- }
252
- }
253
- class EncryptionError extends SecretError {
254
- constructor(message, adapterType, cause) {
255
- super(message, "ENCRYPTION_FAILED", { adapterType, cause });
256
- this.name = "EncryptionError";
257
- }
258
- }
259
- class KeyRotationError extends SecretError {
260
- constructor(message, adapterType, cause) {
261
- super(message, "KEY_ROTATION_FAILED", { adapterType, cause });
262
- this.name = "KeyRotationError";
263
- }
264
- }
265
- class TenantKeyMissingError extends SecretError {
266
- constructor(tenantId) {
267
- super(
268
- `No active encryption key for tenant: ${tenantId}`,
269
- "TENANT_KEY_MISSING"
270
- );
271
- this.name = "TenantKeyMissingError";
272
- }
273
- }
274
- class AMKUnavailableError extends SecretError {
275
- constructor(message) {
276
- super(message, "AMK_UNAVAILABLE");
277
- this.name = "AMKUnavailableError";
278
- }
279
- }
280
- class StoreNotInitializedError extends SecretError {
281
- constructor(adapterType) {
282
- super("Secret store not initialized", "STORE_NOT_INITIALIZED", {
283
- adapterType
284
- });
285
- this.name = "StoreNotInitializedError";
286
- }
287
- }
288
- class InvalidKeyFormatError extends SecretError {
289
- constructor(message, adapterType) {
290
- super(message, "INVALID_KEY_FORMAT", { adapterType });
291
- this.name = "InvalidKeyFormatError";
292
- }
293
- }
294
- const DEFAULT_ROTATION_PERIOD_MS = 90 * 24 * 60 * 60 * 1e3;
295
- class DatabaseSecretStore {
296
- db;
297
- keysTable;
298
- amkConfig;
299
- cachedAmk = null;
300
- initialized = false;
301
- listeners = [];
302
- constructor(options) {
303
- this.db = options.db;
304
- const tableName = options.keysTable ?? "tenant_encryption_keys";
305
- if (!/^[A-Za-z0-9_]+$/.test(tableName)) {
306
- throw new Error(
307
- `Invalid keysTable name "${tableName}". Table name must contain only alphanumeric characters and underscores.`
308
- );
309
- }
310
- this.keysTable = tableName;
311
- this.amkConfig = options.amk;
312
- }
313
- /**
314
- * Initialize the store (create schema if needed)
315
- */
316
- async initialize() {
317
- if (this.initialized) return;
318
- const schema = `
4
+ //#region \0rolldown/runtime.js
5
+ var __defProp = Object.defineProperty;
6
+ var __exportAll = (all, no_symbols) => {
7
+ let target = {};
8
+ for (var name in all) __defProp(target, name, {
9
+ get: all[name],
10
+ enumerable: true
11
+ });
12
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
13
+ return target;
14
+ };
15
+ //#endregion
16
+ //#region src/shared/envelope.ts
17
+ /**
18
+ * Core envelope encryption primitives using AES-256-GCM
19
+ *
20
+ * This class provides the cryptographic operations for envelope encryption:
21
+ * - Generate data encryption keys (DEKs)
22
+ * - Wrap/unwrap DEKs with a master key
23
+ * - Encrypt/decrypt data with DEKs
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * // Generate a new data key
28
+ * const dataKey = EnvelopeEncryption.generateDataKey();
29
+ *
30
+ * // Wrap the data key with an AMK
31
+ * const wrapped = EnvelopeEncryption.wrapKey(dataKey, amk);
32
+ *
33
+ * // Encrypt data with the data key
34
+ * const encrypted = EnvelopeEncryption.encryptData('secret', dataKey);
35
+ *
36
+ * // Later: unwrap and decrypt
37
+ * const unwrappedKey = EnvelopeEncryption.unwrapKey(wrapped.wrappedKey, wrapped.iv, wrapped.authTag, amk);
38
+ * const plaintext = EnvelopeEncryption.decryptData(encrypted.ciphertext, encrypted.iv, encrypted.authTag, unwrappedKey);
39
+ * ```
40
+ */
41
+ var EnvelopeEncryption = class EnvelopeEncryption {
42
+ static ALGORITHM = "aes-256-gcm";
43
+ static KEY_LENGTH = 32;
44
+ static IV_LENGTH = 12;
45
+ static AUTH_TAG_LENGTH = 16;
46
+ /**
47
+ * Generate a new random data encryption key
48
+ *
49
+ * @returns A 32-byte (256-bit) random key
50
+ */
51
+ static generateDataKey() {
52
+ return crypto.randomBytes(EnvelopeEncryption.KEY_LENGTH);
53
+ }
54
+ /**
55
+ * Generate a random IV for encryption
56
+ *
57
+ * @returns A 12-byte (96-bit) random IV
58
+ */
59
+ static generateIV() {
60
+ return crypto.randomBytes(EnvelopeEncryption.IV_LENGTH);
61
+ }
62
+ /**
63
+ * Wrap (encrypt) a data encryption key with a master key
64
+ *
65
+ * @param dataKey - The data key to wrap (32 bytes)
66
+ * @param masterKey - The master key to wrap with (32 bytes)
67
+ * @returns Object containing wrapped key, IV, and auth tag (all base64-encoded)
68
+ */
69
+ static wrapKey(dataKey, masterKey) {
70
+ EnvelopeEncryption.validateKeyLength(dataKey);
71
+ EnvelopeEncryption.validateKeyLength(masterKey);
72
+ const iv = EnvelopeEncryption.generateIV();
73
+ const cipher = crypto.createCipheriv(EnvelopeEncryption.ALGORITHM, masterKey, iv);
74
+ const encrypted = Buffer.concat([cipher.update(dataKey), cipher.final()]);
75
+ const authTag = cipher.getAuthTag();
76
+ return {
77
+ wrappedKey: encrypted.toString("base64"),
78
+ iv: iv.toString("base64"),
79
+ authTag: authTag.toString("base64")
80
+ };
81
+ }
82
+ /**
83
+ * Unwrap (decrypt) a data encryption key with a master key
84
+ *
85
+ * @param wrappedKey - Base64-encoded wrapped key
86
+ * @param iv - Base64-encoded IV
87
+ * @param authTag - Base64-encoded authentication tag
88
+ * @param masterKey - The master key to unwrap with (32 bytes)
89
+ * @returns The unwrapped data key (32 bytes)
90
+ * @throws Error if decryption fails (invalid key, tampered data, etc.)
91
+ */
92
+ static unwrapKey(wrappedKey, iv, authTag, masterKey) {
93
+ EnvelopeEncryption.validateKeyLength(masterKey);
94
+ const decipher = crypto.createDecipheriv(EnvelopeEncryption.ALGORITHM, masterKey, Buffer.from(iv, "base64"));
95
+ decipher.setAuthTag(Buffer.from(authTag, "base64"));
96
+ return Buffer.concat([decipher.update(Buffer.from(wrappedKey, "base64")), decipher.final()]);
97
+ }
98
+ /**
99
+ * Encrypt data with a data encryption key
100
+ *
101
+ * @param plaintext - The plaintext string to encrypt
102
+ * @param dataKey - The data encryption key (32 bytes)
103
+ * @returns Object containing ciphertext, IV, and auth tag (all base64-encoded)
104
+ */
105
+ static encryptData(plaintext, dataKey) {
106
+ EnvelopeEncryption.validateKeyLength(dataKey);
107
+ const iv = EnvelopeEncryption.generateIV();
108
+ const cipher = crypto.createCipheriv(EnvelopeEncryption.ALGORITHM, dataKey, iv);
109
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
110
+ const authTag = cipher.getAuthTag();
111
+ return {
112
+ ciphertext: encrypted.toString("base64"),
113
+ iv: iv.toString("base64"),
114
+ authTag: authTag.toString("base64")
115
+ };
116
+ }
117
+ /**
118
+ * Decrypt data with a data encryption key
119
+ *
120
+ * @param ciphertext - Base64-encoded ciphertext
121
+ * @param iv - Base64-encoded IV
122
+ * @param authTag - Base64-encoded authentication tag
123
+ * @param dataKey - The data encryption key (32 bytes)
124
+ * @returns The decrypted plaintext string
125
+ * @throws Error if decryption fails (invalid key, tampered data, etc.)
126
+ */
127
+ static decryptData(ciphertext, iv, authTag, dataKey) {
128
+ EnvelopeEncryption.validateKeyLength(dataKey);
129
+ const decipher = crypto.createDecipheriv(EnvelopeEncryption.ALGORITHM, dataKey, Buffer.from(iv, "base64"));
130
+ decipher.setAuthTag(Buffer.from(authTag, "base64"));
131
+ return Buffer.concat([decipher.update(Buffer.from(ciphertext, "base64")), decipher.final()]).toString("utf8");
132
+ }
133
+ /**
134
+ * Create a full encrypted envelope
135
+ *
136
+ * This combines key wrapping and data encryption into a single envelope
137
+ * that can be stored and later decrypted.
138
+ *
139
+ * @param plaintext - The plaintext string to encrypt
140
+ * @param dataKey - The data encryption key (32 bytes)
141
+ * @param wrappedKeyInfo - The wrapped key info (from wrapKey)
142
+ * @param amkKeyId - The ID of the AMK used to wrap the key
143
+ * @param metadata - Optional metadata to include in the envelope
144
+ * @returns A complete encrypted envelope
145
+ */
146
+ static createEnvelope(plaintext, dataKey, wrappedKeyInfo, amkKeyId, metadata) {
147
+ const encrypted = EnvelopeEncryption.encryptData(plaintext, dataKey);
148
+ return {
149
+ version: 1,
150
+ algorithm: "aes-256-gcm",
151
+ wrappedKey: `${wrappedKeyInfo.wrappedKey}:${wrappedKeyInfo.iv}:${wrappedKeyInfo.authTag}`,
152
+ amkKeyId,
153
+ iv: encrypted.iv,
154
+ authTag: encrypted.authTag,
155
+ ciphertext: encrypted.ciphertext,
156
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
157
+ metadata
158
+ };
159
+ }
160
+ /**
161
+ * Parse a combined wrapped key string
162
+ *
163
+ * @param combinedWrappedKey - The combined wrapped key string (wrappedKey:iv:authTag)
164
+ * @returns Object with wrappedKey, iv, and authTag
165
+ */
166
+ static parseWrappedKey(combinedWrappedKey) {
167
+ const parts = combinedWrappedKey.split(":");
168
+ if (parts.length !== 3) throw new Error("Invalid wrapped key format: expected wrappedKey:iv:authTag");
169
+ const [wrappedKey, iv, authTag] = parts;
170
+ if (!wrappedKey || !iv || !authTag) throw new Error("Invalid wrapped key format: wrappedKey, iv, and authTag must all be non-empty");
171
+ return {
172
+ wrappedKey,
173
+ iv,
174
+ authTag
175
+ };
176
+ }
177
+ /**
178
+ * Decrypt a full envelope
179
+ *
180
+ * @param envelope - The encrypted envelope
181
+ * @param masterKey - The master key to unwrap the data key
182
+ * @returns The decrypted plaintext string
183
+ */
184
+ static decryptEnvelope(envelope, masterKey) {
185
+ const { wrappedKey, iv: keyIv, authTag: keyAuthTag } = EnvelopeEncryption.parseWrappedKey(envelope.wrappedKey);
186
+ const dataKey = EnvelopeEncryption.unwrapKey(wrappedKey, keyIv, keyAuthTag, masterKey);
187
+ return EnvelopeEncryption.decryptData(envelope.ciphertext, envelope.iv, envelope.authTag, dataKey);
188
+ }
189
+ /**
190
+ * Validate that a key is the correct length
191
+ *
192
+ * @param key - The key to validate
193
+ * @param expectedLength - Expected length in bytes (default: 32)
194
+ * @throws Error if key length is invalid
195
+ */
196
+ static validateKeyLength(key, expectedLength = EnvelopeEncryption.KEY_LENGTH) {
197
+ if (key.length !== expectedLength) throw new Error(`Invalid key length: expected ${expectedLength} bytes, got ${key.length} bytes`);
198
+ }
199
+ /**
200
+ * Parse a hex-encoded key from string
201
+ *
202
+ * @param hexKey - Hex-encoded key string (64 chars for 32 bytes)
203
+ * @returns The key as a Buffer
204
+ * @throws Error if the hex string is invalid
205
+ */
206
+ static parseHexKey(hexKey) {
207
+ if (!/^[0-9a-fA-F]+$/.test(hexKey)) throw new Error("Invalid hex key: contains non-hex characters");
208
+ const key = Buffer.from(hexKey, "hex");
209
+ EnvelopeEncryption.validateKeyLength(key);
210
+ return key;
211
+ }
212
+ };
213
+ //#endregion
214
+ //#region src/shared/errors.ts
215
+ /**
216
+ * Base error class for secret operations
217
+ */
218
+ var SecretError = class extends Error {
219
+ code;
220
+ adapterType;
221
+ cause;
222
+ constructor(message, code, options) {
223
+ super(message);
224
+ this.name = "SecretError";
225
+ this.code = code;
226
+ this.adapterType = options?.adapterType;
227
+ this.cause = options?.cause;
228
+ }
229
+ };
230
+ /**
231
+ * Error thrown when a key is not found
232
+ */
233
+ var KeyNotFoundError = class extends SecretError {
234
+ constructor(keyId, adapterType) {
235
+ super(`Key not found: ${keyId}`, "KEY_NOT_FOUND", { adapterType });
236
+ this.name = "KeyNotFoundError";
237
+ }
238
+ };
239
+ /**
240
+ * Error thrown when decryption fails
241
+ */
242
+ var DecryptionError = class extends SecretError {
243
+ constructor(message, adapterType, cause) {
244
+ super(message, "DECRYPTION_FAILED", {
245
+ adapterType,
246
+ cause
247
+ });
248
+ this.name = "DecryptionError";
249
+ }
250
+ };
251
+ /**
252
+ * Error thrown when encryption fails
253
+ */
254
+ var EncryptionError = class extends SecretError {
255
+ constructor(message, adapterType, cause) {
256
+ super(message, "ENCRYPTION_FAILED", {
257
+ adapterType,
258
+ cause
259
+ });
260
+ this.name = "EncryptionError";
261
+ }
262
+ };
263
+ /**
264
+ * Error thrown when key rotation fails
265
+ */
266
+ var KeyRotationError = class extends SecretError {
267
+ constructor(message, adapterType, cause) {
268
+ super(message, "KEY_ROTATION_FAILED", {
269
+ adapterType,
270
+ cause
271
+ });
272
+ this.name = "KeyRotationError";
273
+ }
274
+ };
275
+ /**
276
+ * Error thrown when a tenant's encryption key is missing
277
+ */
278
+ var TenantKeyMissingError = class extends SecretError {
279
+ constructor(tenantId) {
280
+ super(`No active encryption key for tenant: ${tenantId}`, "TENANT_KEY_MISSING");
281
+ this.name = "TenantKeyMissingError";
282
+ }
283
+ };
284
+ /**
285
+ * Error thrown when the Application Master Key is unavailable
286
+ */
287
+ var AMKUnavailableError = class extends SecretError {
288
+ constructor(message) {
289
+ super(message, "AMK_UNAVAILABLE");
290
+ this.name = "AMKUnavailableError";
291
+ }
292
+ };
293
+ /**
294
+ * Error thrown when the store is not initialized
295
+ */
296
+ var StoreNotInitializedError = class extends SecretError {
297
+ constructor(adapterType) {
298
+ super("Secret store not initialized", "STORE_NOT_INITIALIZED", { adapterType });
299
+ this.name = "StoreNotInitializedError";
300
+ }
301
+ };
302
+ /**
303
+ * Error thrown when an invalid key format is encountered
304
+ */
305
+ var InvalidKeyFormatError = class extends SecretError {
306
+ constructor(message, adapterType) {
307
+ super(message, "INVALID_KEY_FORMAT", { adapterType });
308
+ this.name = "InvalidKeyFormatError";
309
+ }
310
+ };
311
+ //#endregion
312
+ //#region src/adapters/database.ts
313
+ var database_exports = /* @__PURE__ */ __exportAll({
314
+ DatabaseSecretStore: () => DatabaseSecretStore,
315
+ default: () => DatabaseSecretStore
316
+ });
317
+ /**
318
+ * Default rotation period in milliseconds (90 days)
319
+ */
320
+ var DEFAULT_ROTATION_PERIOD_MS = 2160 * 60 * 60 * 1e3;
321
+ /**
322
+ * Database-backed secret store
323
+ *
324
+ * Uses envelope encryption with AES-256-GCM:
325
+ * - Application Master Key (AMK) from environment variable
326
+ * - Per-tenant Data Encryption Keys (TDEKs) wrapped by AMK
327
+ * - Secrets encrypted by unwrapped TDEKs
328
+ *
329
+ * @example
330
+ * ```typescript
331
+ * const store = new DatabaseSecretStore({
332
+ * type: 'database',
333
+ * db,
334
+ * amk: {
335
+ * provider: 'env',
336
+ * keyEnvVar: 'SECRET_MASTER_KEY',
337
+ * keyId: 'amk-v1'
338
+ * }
339
+ * });
340
+ *
341
+ * await store.initialize();
342
+ *
343
+ * // Encrypt a secret for a tenant
344
+ * const envelope = await store.encrypt('tenant-123', 'api-key', 'sk_live_xxx');
345
+ *
346
+ * // Decrypt
347
+ * const { value } = await store.decrypt('tenant-123', envelope);
348
+ * ```
349
+ */
350
+ var DatabaseSecretStore = class {
351
+ db;
352
+ keysTable;
353
+ amkConfig;
354
+ cachedAmk = null;
355
+ initialized = false;
356
+ listeners = [];
357
+ constructor(options) {
358
+ this.db = options.db;
359
+ const tableName = options.keysTable ?? "tenant_encryption_keys";
360
+ if (!/^[A-Za-z0-9_]+$/.test(tableName)) throw new Error(`Invalid keysTable name "${tableName}". Table name must contain only alphanumeric characters and underscores.`);
361
+ this.keysTable = tableName;
362
+ this.amkConfig = options.amk;
363
+ }
364
+ /**
365
+ * Initialize the store (create schema if needed)
366
+ */
367
+ async initialize() {
368
+ if (this.initialized) return;
369
+ const schema = `
319
370
  CREATE TABLE IF NOT EXISTS "${this.keysTable}" (
320
371
  id TEXT PRIMARY KEY,
321
372
  tenant_id TEXT NOT NULL,
@@ -336,401 +387,330 @@ class DatabaseSecretStore {
336
387
  ON "${this.keysTable}" (rotate_after)
337
388
  WHERE status = 'active';
338
389
  `;
339
- await syncSchema({ db: this.db, schema });
340
- this.initialized = true;
341
- }
342
- /**
343
- * Get the Application Master Key from environment
344
- */
345
- getAMK() {
346
- if (this.cachedAmk) {
347
- return this.cachedAmk;
348
- }
349
- const keyHex = process.env[this.amkConfig.keyEnvVar];
350
- if (!keyHex) {
351
- throw new AMKUnavailableError(
352
- `Application Master Key not found in environment variable: ${this.amkConfig.keyEnvVar}`
353
- );
354
- }
355
- try {
356
- const key = EnvelopeEncryption.parseHexKey(keyHex);
357
- this.cachedAmk = key;
358
- return key;
359
- } catch (error) {
360
- throw new AMKUnavailableError(
361
- `Invalid AMK in ${this.amkConfig.keyEnvVar}: ${error.message}`
362
- );
363
- }
364
- }
365
- /**
366
- * Ensure the store is initialized
367
- */
368
- ensureInitialized() {
369
- if (!this.initialized) {
370
- throw new StoreNotInitializedError("database");
371
- }
372
- }
373
- /**
374
- * Emit an event to all listeners
375
- */
376
- async emitEvent(event) {
377
- for (const listener of this.listeners) {
378
- await listener(event);
379
- }
380
- }
381
- /**
382
- * Subscribe to store events
383
- */
384
- subscribe(listener) {
385
- this.listeners.push(listener);
386
- return () => {
387
- const index = this.listeners.indexOf(listener);
388
- if (index !== -1) {
389
- this.listeners.splice(index, 1);
390
- }
391
- };
392
- }
393
- async encrypt(tenantId, secretName, plaintext, options) {
394
- this.ensureInitialized();
395
- if (typeof tenantId !== "string" || tenantId.trim().length === 0) {
396
- throw new EncryptionError(
397
- "Invalid tenantId provided for encryption",
398
- "database"
399
- );
400
- }
401
- if (typeof secretName !== "string" || secretName.trim().length === 0) {
402
- throw new EncryptionError(
403
- "Invalid secretName provided for encryption",
404
- "database"
405
- );
406
- }
407
- let dataKey = null;
408
- try {
409
- let tenantKey = await this.getTenantKey(tenantId);
410
- if (!tenantKey) {
411
- tenantKey = await this.createTenantKey(tenantId);
412
- }
413
- const amk = this.getAMK();
414
- const {
415
- wrappedKey,
416
- iv: keyIv,
417
- authTag: keyAuthTag
418
- } = EnvelopeEncryption.parseWrappedKey(tenantKey.wrappedKey);
419
- dataKey = EnvelopeEncryption.unwrapKey(
420
- wrappedKey,
421
- keyIv,
422
- keyAuthTag,
423
- amk
424
- );
425
- const envelope = EnvelopeEncryption.createEnvelope(
426
- plaintext,
427
- dataKey,
428
- { wrappedKey, iv: keyIv, authTag: keyAuthTag },
429
- tenantKey.amkKeyId,
430
- options?.metadata
431
- );
432
- await this.emitEvent({
433
- type: "secret.encrypted",
434
- tenantId,
435
- keyId: tenantKey.keyId,
436
- secretName,
437
- timestamp: /* @__PURE__ */ new Date()
438
- });
439
- return envelope;
440
- } catch (error) {
441
- if (error instanceof AMKUnavailableError || error instanceof TenantKeyMissingError) {
442
- throw error;
443
- }
444
- throw new EncryptionError(
445
- `Failed to encrypt secret '${secretName}' for tenant ${tenantId}: ${error.message}`,
446
- "database",
447
- error instanceof Error ? error : void 0
448
- );
449
- } finally {
450
- if (dataKey && Buffer.isBuffer(dataKey)) {
451
- dataKey.fill(0);
452
- }
453
- }
454
- }
455
- async decrypt(tenantId, envelope) {
456
- this.ensureInitialized();
457
- if (typeof tenantId !== "string" || tenantId.trim().length === 0) {
458
- throw new DecryptionError(
459
- "Invalid tenantId provided for decryption",
460
- "database"
461
- );
462
- }
463
- const tenantKeys = await this.listKeyVersions(tenantId);
464
- if (tenantKeys.length === 0) {
465
- throw new TenantKeyMissingError(tenantId);
466
- }
467
- const envelopeWrappedKeyParts = envelope.wrappedKey.split(":");
468
- const envelopeWrappedKey = envelopeWrappedKeyParts[0];
469
- const matchingKey = tenantKeys.find((key) => {
470
- const keyParts = key.wrappedKey.split(":");
471
- return keyParts[0] === envelopeWrappedKey;
472
- });
473
- if (!matchingKey) {
474
- throw new DecryptionError(
475
- `Envelope was not encrypted with a key belonging to tenant ${tenantId}`,
476
- "database"
477
- );
478
- }
479
- try {
480
- const amk = this.getAMK();
481
- const plaintext = EnvelopeEncryption.decryptEnvelope(envelope, amk);
482
- await this.emitEvent({
483
- type: "secret.decrypted",
484
- tenantId,
485
- keyId: matchingKey.keyId,
486
- timestamp: /* @__PURE__ */ new Date()
487
- });
488
- return {
489
- value: plaintext,
490
- keyId: matchingKey.keyId,
491
- createdAt: new Date(envelope.createdAt),
492
- metadata: envelope.metadata
493
- };
494
- } catch (error) {
495
- if (error instanceof AMKUnavailableError) {
496
- throw error;
497
- }
498
- throw new DecryptionError(
499
- `Failed to decrypt secret for tenant ${tenantId}: ${error.message}`,
500
- "database",
501
- error instanceof Error ? error : void 0
502
- );
503
- }
504
- }
505
- async getTenantKey(tenantId) {
506
- this.ensureInitialized();
507
- const row = await this.db.get(this.keysTable, {
508
- tenant_id: tenantId,
509
- status: "active"
510
- });
511
- if (!row) return null;
512
- return this.parseKeyRow(row);
513
- }
514
- async createTenantKey(tenantId) {
515
- this.ensureInitialized();
516
- const amk = this.getAMK();
517
- const dataKey = EnvelopeEncryption.generateDataKey();
518
- const wrapped = EnvelopeEncryption.wrapKey(dataKey, amk);
519
- const keyId = createId();
520
- const wrappedKeyStr = `${wrapped.wrappedKey}:${wrapped.iv}:${wrapped.authTag}`;
521
- const now = /* @__PURE__ */ new Date();
522
- const rotateAfter = new Date(now.getTime() + DEFAULT_ROTATION_PERIOD_MS);
523
- await this.db.insert(this.keysTable, {
524
- id: keyId,
525
- tenant_id: tenantId,
526
- wrapped_key: wrappedKeyStr,
527
- amk_key_id: this.amkConfig.keyId,
528
- status: "active",
529
- version: 1,
530
- rotate_after: rotateAfter.toISOString(),
531
- created_at: now.toISOString(),
532
- updated_at: now.toISOString()
533
- });
534
- await this.emitEvent({
535
- type: "key.created",
536
- tenantId,
537
- keyId,
538
- timestamp: now
539
- });
540
- return {
541
- keyId,
542
- tenantId,
543
- wrappedKey: wrappedKeyStr,
544
- amkKeyId: this.amkConfig.keyId,
545
- status: "active",
546
- version: 1,
547
- createdAt: now,
548
- rotateAfter
549
- };
550
- }
551
- async rotateTenantKey(tenantId) {
552
- this.ensureInitialized();
553
- const currentKey = await this.getTenantKey(tenantId);
554
- if (!currentKey) {
555
- throw new TenantKeyMissingError(tenantId);
556
- }
557
- const now = /* @__PURE__ */ new Date();
558
- try {
559
- await this.db.update(
560
- this.keysTable,
561
- { id: currentKey.keyId },
562
- { status: "rotating", updated_at: now.toISOString() }
563
- );
564
- const amk = this.getAMK();
565
- const dataKey = EnvelopeEncryption.generateDataKey();
566
- const wrapped = EnvelopeEncryption.wrapKey(dataKey, amk);
567
- const newKeyId = createId();
568
- const wrappedKeyStr = `${wrapped.wrappedKey}:${wrapped.iv}:${wrapped.authTag}`;
569
- const rotateAfter = new Date(now.getTime() + DEFAULT_ROTATION_PERIOD_MS);
570
- await this.db.insert(this.keysTable, {
571
- id: newKeyId,
572
- tenant_id: tenantId,
573
- wrapped_key: wrappedKeyStr,
574
- amk_key_id: this.amkConfig.keyId,
575
- status: "active",
576
- version: currentKey.version + 1,
577
- rotate_after: rotateAfter.toISOString(),
578
- created_at: now.toISOString(),
579
- updated_at: now.toISOString()
580
- });
581
- await this.db.update(
582
- this.keysTable,
583
- { id: currentKey.keyId },
584
- {
585
- status: "retired",
586
- retired_at: now.toISOString(),
587
- updated_at: now.toISOString()
588
- }
589
- );
590
- await this.emitEvent({
591
- type: "key.rotated",
592
- tenantId,
593
- keyId: newKeyId,
594
- timestamp: now
595
- });
596
- return {
597
- keyId: newKeyId,
598
- tenantId,
599
- wrappedKey: wrappedKeyStr,
600
- amkKeyId: this.amkConfig.keyId,
601
- status: "active",
602
- version: currentKey.version + 1,
603
- createdAt: now,
604
- rotateAfter
605
- };
606
- } catch (error) {
607
- throw new KeyRotationError(
608
- `Failed to rotate key for tenant ${tenantId}: ${error.message}`,
609
- "database",
610
- error instanceof Error ? error : void 0
611
- );
612
- }
613
- }
614
- async retireTenantKey(tenantId, keyId) {
615
- this.ensureInitialized();
616
- const now = /* @__PURE__ */ new Date();
617
- await this.db.update(
618
- this.keysTable,
619
- { id: keyId, tenant_id: tenantId },
620
- {
621
- status: "retired",
622
- retired_at: now.toISOString(),
623
- updated_at: now.toISOString()
624
- }
625
- );
626
- await this.emitEvent({
627
- type: "key.retired",
628
- tenantId,
629
- keyId,
630
- timestamp: now
631
- });
632
- }
633
- async getActiveAMK() {
634
- this.getAMK();
635
- return {
636
- keyId: this.amkConfig.keyId,
637
- description: `AMK from env var ${this.amkConfig.keyEnvVar}`,
638
- status: "active",
639
- provider: "env",
640
- keyReference: this.amkConfig.keyEnvVar,
641
- createdAt: /* @__PURE__ */ new Date()
642
- };
643
- }
644
- async rewrapTenantKey(_tenantId, _newAmkKeyId) {
645
- throw new Error(
646
- "AMK rotation not supported for env-based keys. Use AWS KMS, Vault, or Azure Key Vault for AMK rotation support."
647
- );
648
- }
649
- async listKeyVersions(tenantId) {
650
- this.ensureInitialized();
651
- const { rows } = await this.db.query(
652
- `SELECT * FROM "${this.keysTable}" WHERE tenant_id = ? ORDER BY version DESC`,
653
- tenantId
654
- );
655
- return rows.map((row) => this.parseKeyRow(row));
656
- }
657
- /**
658
- * Parse a database row into a TenantDataEncryptionKey
659
- */
660
- parseKeyRow(row) {
661
- return {
662
- keyId: row.id,
663
- tenantId: row.tenant_id,
664
- wrappedKey: row.wrapped_key,
665
- amkKeyId: row.amk_key_id,
666
- status: row.status,
667
- version: row.version,
668
- createdAt: new Date(row.created_at),
669
- rotateAfter: row.rotate_after ? new Date(row.rotate_after) : void 0,
670
- retiredAt: row.retired_at ? new Date(row.retired_at) : void 0
671
- };
672
- }
673
- }
674
- const database = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
675
- __proto__: null,
676
- DatabaseSecretStore
677
- }, Symbol.toStringTag, { value: "Module" }));
390
+ await syncSchema({
391
+ db: this.db,
392
+ schema
393
+ });
394
+ this.initialized = true;
395
+ }
396
+ /**
397
+ * Get the Application Master Key from environment
398
+ */
399
+ getAMK() {
400
+ if (this.cachedAmk) return this.cachedAmk;
401
+ const keyHex = process.env[this.amkConfig.keyEnvVar];
402
+ if (!keyHex) throw new AMKUnavailableError(`Application Master Key not found in environment variable: ${this.amkConfig.keyEnvVar}`);
403
+ try {
404
+ const key = EnvelopeEncryption.parseHexKey(keyHex);
405
+ this.cachedAmk = key;
406
+ return key;
407
+ } catch (error) {
408
+ throw new AMKUnavailableError(`Invalid AMK in ${this.amkConfig.keyEnvVar}: ${error.message}`);
409
+ }
410
+ }
411
+ /**
412
+ * Ensure the store is initialized
413
+ */
414
+ ensureInitialized() {
415
+ if (!this.initialized) throw new StoreNotInitializedError("database");
416
+ }
417
+ /**
418
+ * Emit an event to all listeners
419
+ */
420
+ async emitEvent(event) {
421
+ for (const listener of this.listeners) await listener(event);
422
+ }
423
+ /**
424
+ * Subscribe to store events
425
+ */
426
+ subscribe(listener) {
427
+ this.listeners.push(listener);
428
+ return () => {
429
+ const index = this.listeners.indexOf(listener);
430
+ if (index !== -1) this.listeners.splice(index, 1);
431
+ };
432
+ }
433
+ async encrypt(tenantId, secretName, plaintext, options) {
434
+ this.ensureInitialized();
435
+ if (typeof tenantId !== "string" || tenantId.trim().length === 0) throw new EncryptionError("Invalid tenantId provided for encryption", "database");
436
+ if (typeof secretName !== "string" || secretName.trim().length === 0) throw new EncryptionError("Invalid secretName provided for encryption", "database");
437
+ let dataKey = null;
438
+ try {
439
+ let tenantKey = await this.getTenantKey(tenantId);
440
+ if (!tenantKey) tenantKey = await this.createTenantKey(tenantId);
441
+ const amk = this.getAMK();
442
+ const { wrappedKey, iv: keyIv, authTag: keyAuthTag } = EnvelopeEncryption.parseWrappedKey(tenantKey.wrappedKey);
443
+ dataKey = EnvelopeEncryption.unwrapKey(wrappedKey, keyIv, keyAuthTag, amk);
444
+ const envelope = EnvelopeEncryption.createEnvelope(plaintext, dataKey, {
445
+ wrappedKey,
446
+ iv: keyIv,
447
+ authTag: keyAuthTag
448
+ }, tenantKey.amkKeyId, options?.metadata);
449
+ await this.emitEvent({
450
+ type: "secret.encrypted",
451
+ tenantId,
452
+ keyId: tenantKey.keyId,
453
+ secretName,
454
+ timestamp: /* @__PURE__ */ new Date()
455
+ });
456
+ return envelope;
457
+ } catch (error) {
458
+ if (error instanceof AMKUnavailableError || error instanceof TenantKeyMissingError) throw error;
459
+ throw new EncryptionError(`Failed to encrypt secret '${secretName}' for tenant ${tenantId}: ${error.message}`, "database", error instanceof Error ? error : void 0);
460
+ } finally {
461
+ if (dataKey && Buffer.isBuffer(dataKey)) dataKey.fill(0);
462
+ }
463
+ }
464
+ async decrypt(tenantId, envelope) {
465
+ this.ensureInitialized();
466
+ if (typeof tenantId !== "string" || tenantId.trim().length === 0) throw new DecryptionError("Invalid tenantId provided for decryption", "database");
467
+ const tenantKeys = await this.listKeyVersions(tenantId);
468
+ if (tenantKeys.length === 0) throw new TenantKeyMissingError(tenantId);
469
+ const envelopeWrappedKey = envelope.wrappedKey.split(":")[0];
470
+ const matchingKey = tenantKeys.find((key) => {
471
+ return key.wrappedKey.split(":")[0] === envelopeWrappedKey;
472
+ });
473
+ if (!matchingKey) throw new DecryptionError(`Envelope was not encrypted with a key belonging to tenant ${tenantId}`, "database");
474
+ try {
475
+ const amk = this.getAMK();
476
+ const plaintext = EnvelopeEncryption.decryptEnvelope(envelope, amk);
477
+ await this.emitEvent({
478
+ type: "secret.decrypted",
479
+ tenantId,
480
+ keyId: matchingKey.keyId,
481
+ timestamp: /* @__PURE__ */ new Date()
482
+ });
483
+ return {
484
+ value: plaintext,
485
+ keyId: matchingKey.keyId,
486
+ createdAt: new Date(envelope.createdAt),
487
+ metadata: envelope.metadata
488
+ };
489
+ } catch (error) {
490
+ if (error instanceof AMKUnavailableError) throw error;
491
+ throw new DecryptionError(`Failed to decrypt secret for tenant ${tenantId}: ${error.message}`, "database", error instanceof Error ? error : void 0);
492
+ }
493
+ }
494
+ async getTenantKey(tenantId) {
495
+ this.ensureInitialized();
496
+ const row = await this.db.get(this.keysTable, {
497
+ tenant_id: tenantId,
498
+ status: "active"
499
+ });
500
+ if (!row) return null;
501
+ return this.parseKeyRow(row);
502
+ }
503
+ async createTenantKey(tenantId) {
504
+ this.ensureInitialized();
505
+ const amk = this.getAMK();
506
+ const dataKey = EnvelopeEncryption.generateDataKey();
507
+ const wrapped = EnvelopeEncryption.wrapKey(dataKey, amk);
508
+ const keyId = createId();
509
+ const wrappedKeyStr = `${wrapped.wrappedKey}:${wrapped.iv}:${wrapped.authTag}`;
510
+ const now = /* @__PURE__ */ new Date();
511
+ const rotateAfter = new Date(now.getTime() + DEFAULT_ROTATION_PERIOD_MS);
512
+ await this.db.insert(this.keysTable, {
513
+ id: keyId,
514
+ tenant_id: tenantId,
515
+ wrapped_key: wrappedKeyStr,
516
+ amk_key_id: this.amkConfig.keyId,
517
+ status: "active",
518
+ version: 1,
519
+ rotate_after: rotateAfter.toISOString(),
520
+ created_at: now.toISOString(),
521
+ updated_at: now.toISOString()
522
+ });
523
+ await this.emitEvent({
524
+ type: "key.created",
525
+ tenantId,
526
+ keyId,
527
+ timestamp: now
528
+ });
529
+ return {
530
+ keyId,
531
+ tenantId,
532
+ wrappedKey: wrappedKeyStr,
533
+ amkKeyId: this.amkConfig.keyId,
534
+ status: "active",
535
+ version: 1,
536
+ createdAt: now,
537
+ rotateAfter
538
+ };
539
+ }
540
+ async rotateTenantKey(tenantId) {
541
+ this.ensureInitialized();
542
+ const currentKey = await this.getTenantKey(tenantId);
543
+ if (!currentKey) throw new TenantKeyMissingError(tenantId);
544
+ const now = /* @__PURE__ */ new Date();
545
+ try {
546
+ await this.db.update(this.keysTable, { id: currentKey.keyId }, {
547
+ status: "rotating",
548
+ updated_at: now.toISOString()
549
+ });
550
+ const amk = this.getAMK();
551
+ const dataKey = EnvelopeEncryption.generateDataKey();
552
+ const wrapped = EnvelopeEncryption.wrapKey(dataKey, amk);
553
+ const newKeyId = createId();
554
+ const wrappedKeyStr = `${wrapped.wrappedKey}:${wrapped.iv}:${wrapped.authTag}`;
555
+ const rotateAfter = new Date(now.getTime() + DEFAULT_ROTATION_PERIOD_MS);
556
+ await this.db.insert(this.keysTable, {
557
+ id: newKeyId,
558
+ tenant_id: tenantId,
559
+ wrapped_key: wrappedKeyStr,
560
+ amk_key_id: this.amkConfig.keyId,
561
+ status: "active",
562
+ version: currentKey.version + 1,
563
+ rotate_after: rotateAfter.toISOString(),
564
+ created_at: now.toISOString(),
565
+ updated_at: now.toISOString()
566
+ });
567
+ await this.db.update(this.keysTable, { id: currentKey.keyId }, {
568
+ status: "retired",
569
+ retired_at: now.toISOString(),
570
+ updated_at: now.toISOString()
571
+ });
572
+ await this.emitEvent({
573
+ type: "key.rotated",
574
+ tenantId,
575
+ keyId: newKeyId,
576
+ timestamp: now
577
+ });
578
+ return {
579
+ keyId: newKeyId,
580
+ tenantId,
581
+ wrappedKey: wrappedKeyStr,
582
+ amkKeyId: this.amkConfig.keyId,
583
+ status: "active",
584
+ version: currentKey.version + 1,
585
+ createdAt: now,
586
+ rotateAfter
587
+ };
588
+ } catch (error) {
589
+ throw new KeyRotationError(`Failed to rotate key for tenant ${tenantId}: ${error.message}`, "database", error instanceof Error ? error : void 0);
590
+ }
591
+ }
592
+ async retireTenantKey(tenantId, keyId) {
593
+ this.ensureInitialized();
594
+ const now = /* @__PURE__ */ new Date();
595
+ await this.db.update(this.keysTable, {
596
+ id: keyId,
597
+ tenant_id: tenantId
598
+ }, {
599
+ status: "retired",
600
+ retired_at: now.toISOString(),
601
+ updated_at: now.toISOString()
602
+ });
603
+ await this.emitEvent({
604
+ type: "key.retired",
605
+ tenantId,
606
+ keyId,
607
+ timestamp: now
608
+ });
609
+ }
610
+ async getActiveAMK() {
611
+ this.getAMK();
612
+ return {
613
+ keyId: this.amkConfig.keyId,
614
+ description: `AMK from env var ${this.amkConfig.keyEnvVar}`,
615
+ status: "active",
616
+ provider: "env",
617
+ keyReference: this.amkConfig.keyEnvVar,
618
+ createdAt: /* @__PURE__ */ new Date()
619
+ };
620
+ }
621
+ async rewrapTenantKey(_tenantId, _newAmkKeyId) {
622
+ throw new Error("AMK rotation not supported for env-based keys. Use AWS KMS, Vault, or Azure Key Vault for AMK rotation support.");
623
+ }
624
+ async listKeyVersions(tenantId) {
625
+ this.ensureInitialized();
626
+ const { rows } = await this.db.query(`SELECT * FROM "${this.keysTable}" WHERE tenant_id = ? ORDER BY version DESC`, tenantId);
627
+ return rows.map((row) => this.parseKeyRow(row));
628
+ }
629
+ /**
630
+ * Parse a database row into a TenantDataEncryptionKey
631
+ */
632
+ parseKeyRow(row) {
633
+ return {
634
+ keyId: row.id,
635
+ tenantId: row.tenant_id,
636
+ wrappedKey: row.wrapped_key,
637
+ amkKeyId: row.amk_key_id,
638
+ status: row.status,
639
+ version: row.version,
640
+ createdAt: new Date(row.created_at),
641
+ rotateAfter: row.rotate_after ? new Date(row.rotate_after) : void 0,
642
+ retiredAt: row.retired_at ? new Date(row.retired_at) : void 0
643
+ };
644
+ }
645
+ };
646
+ //#endregion
647
+ //#region src/shared/factory.ts
648
+ /**
649
+ * Type guard for database options
650
+ */
678
651
  function isDatabaseOptions(opts) {
679
- return opts.type === "database";
652
+ return opts.type === "database";
680
653
  }
654
+ /**
655
+ * Type guard for AWS KMS options
656
+ */
681
657
  function isAWSKMSOptions(opts) {
682
- return opts.type === "aws-kms";
658
+ return opts.type === "aws-kms";
683
659
  }
660
+ /**
661
+ * Type guard for Vault options
662
+ */
684
663
  function isVaultOptions(opts) {
685
- return opts.type === "vault";
664
+ return opts.type === "vault";
686
665
  }
666
+ /**
667
+ * Type guard for Azure Key Vault options
668
+ */
687
669
  function isAzureKeyVaultOptions(opts) {
688
- return opts.type === "azure-keyvault";
670
+ return opts.type === "azure-keyvault";
689
671
  }
672
+ /**
673
+ * Factory function to create secret store instances
674
+ *
675
+ * Uses lazy imports to avoid loading unnecessary adapters and their dependencies.
676
+ *
677
+ * @example
678
+ * ```typescript
679
+ * // Database-backed store with env-based AMK
680
+ * const store = await getSecretStore({
681
+ * type: 'database',
682
+ * db,
683
+ * amk: {
684
+ * provider: 'env',
685
+ * keyEnvVar: 'SECRET_MASTER_KEY',
686
+ * keyId: 'amk-v1'
687
+ * }
688
+ * });
689
+ *
690
+ * // Encrypt a secret
691
+ * const envelope = await store.encrypt('tenant-123', 'api-key', 'sk_live_xxx');
692
+ *
693
+ * // Decrypt
694
+ * const decrypted = await store.decrypt('tenant-123', envelope);
695
+ * ```
696
+ */
690
697
  async function getSecretStore(options) {
691
- if (isDatabaseOptions(options)) {
692
- const { DatabaseSecretStore: DatabaseSecretStore2 } = await Promise.resolve().then(() => database);
693
- const store = new DatabaseSecretStore2(options);
694
- await store.initialize();
695
- return store;
696
- }
697
- if (isAWSKMSOptions(options)) {
698
- throw new Error(
699
- "AWS KMS adapter not yet implemented. Coming in a future release."
700
- );
701
- }
702
- if (isVaultOptions(options)) {
703
- throw new Error(
704
- "HashiCorp Vault adapter not yet implemented. Coming in a future release."
705
- );
706
- }
707
- if (isAzureKeyVaultOptions(options)) {
708
- throw new Error(
709
- "Azure Key Vault adapter not yet implemented. Coming in a future release."
710
- );
711
- }
712
- throw new Error(
713
- `Unsupported secret store type: ${options.type}`
714
- );
698
+ if (isDatabaseOptions(options)) {
699
+ const { DatabaseSecretStore } = await Promise.resolve().then(() => database_exports);
700
+ const store = new DatabaseSecretStore(options);
701
+ await store.initialize();
702
+ return store;
703
+ }
704
+ if (isAWSKMSOptions(options)) throw new Error("AWS KMS adapter not yet implemented. Coming in a future release.");
705
+ if (isVaultOptions(options)) throw new Error("HashiCorp Vault adapter not yet implemented. Coming in a future release.");
706
+ if (isAzureKeyVaultOptions(options)) throw new Error("Azure Key Vault adapter not yet implemented. Coming in a future release.");
707
+ throw new Error(`Unsupported secret store type: ${options.type}`);
715
708
  }
716
- const PACKAGE_VERSION_INITIALIZED = true;
717
- export {
718
- AMKUnavailableError,
719
- DatabaseSecretStore,
720
- DecryptionError,
721
- EncryptionError,
722
- EnvelopeEncryption,
723
- InvalidKeyFormatError,
724
- KeyNotFoundError,
725
- KeyRotationError,
726
- PACKAGE_VERSION_INITIALIZED,
727
- SecretError,
728
- StoreNotInitializedError,
729
- TenantKeyMissingError,
730
- getSecretStore,
731
- isAWSKMSOptions,
732
- isAzureKeyVaultOptions,
733
- isDatabaseOptions,
734
- isVaultOptions
735
- };
736
- //# sourceMappingURL=index.js.map
709
+ //#endregion
710
+ //#region src/index.ts
711
+ /** @internal */
712
+ var PACKAGE_VERSION_INITIALIZED = true;
713
+ //#endregion
714
+ export { AMKUnavailableError, DatabaseSecretStore, DecryptionError, EncryptionError, EnvelopeEncryption, InvalidKeyFormatError, KeyNotFoundError, KeyRotationError, PACKAGE_VERSION_INITIALIZED, SecretError, StoreNotInitializedError, TenantKeyMissingError, getSecretStore, isAWSKMSOptions, isAzureKeyVaultOptions, isDatabaseOptions, isVaultOptions };
715
+
716
+ //# sourceMappingURL=index.js.map