@cipherstash/stack 0.14.0 → 0.15.1

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.
@@ -1,3143 +0,0 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/secrets/index.ts
31
- var secrets_exports = {};
32
- __export(secrets_exports, {
33
- Secrets: () => Secrets
34
- });
35
- module.exports = __toCommonJS(secrets_exports);
36
-
37
- // src/encryption/helpers/index.ts
38
- function encryptedToPgComposite(obj) {
39
- return {
40
- data: obj
41
- };
42
- }
43
- function encryptedToCompositeLiteral(obj) {
44
- return `(${JSON.stringify(JSON.stringify(obj))})`;
45
- }
46
- function encryptedToEscapedCompositeLiteral(obj) {
47
- return JSON.stringify(encryptedToCompositeLiteral(obj));
48
- }
49
- function formatEncryptedResult(encrypted, returnType) {
50
- if (returnType === "composite-literal") {
51
- return encryptedToCompositeLiteral(encrypted);
52
- }
53
- if (returnType === "escaped-composite-literal") {
54
- return encryptedToEscapedCompositeLiteral(encrypted);
55
- }
56
- return encrypted;
57
- }
58
- function toFfiKeysetIdentifier(keyset) {
59
- if (!keyset) return void 0;
60
- if ("name" in keyset) {
61
- return { Name: keyset.name };
62
- }
63
- return { Uuid: keyset.id };
64
- }
65
- function isEncryptedPayload(value) {
66
- if (value === null) return false;
67
- if (typeof value !== "object") return false;
68
- const obj = value;
69
- if (!("v" in obj) || typeof obj.v !== "number") return false;
70
- if (!("i" in obj) || typeof obj.i !== "object") return false;
71
- if (!("c" in obj) && !("sv" in obj)) return false;
72
- return true;
73
- }
74
-
75
- // src/schema/index.ts
76
- var import_zod = require("zod");
77
- var eqlCastAsEnum = import_zod.z.enum([
78
- "text",
79
- "int",
80
- "small_int",
81
- "big_int",
82
- "real",
83
- "double",
84
- "boolean",
85
- "date",
86
- "jsonb"
87
- ]).default("text");
88
- var castAsEnum = import_zod.z.enum(["bigint", "boolean", "date", "number", "string", "json", "text"]).default("text");
89
- var tokenFilterSchema = import_zod.z.object({
90
- kind: import_zod.z.literal("downcase")
91
- });
92
- var tokenizerSchema = import_zod.z.union([
93
- import_zod.z.object({
94
- kind: import_zod.z.literal("standard")
95
- }),
96
- import_zod.z.object({
97
- kind: import_zod.z.literal("ngram"),
98
- token_length: import_zod.z.number()
99
- })
100
- ]).default({ kind: "ngram", token_length: 3 }).optional();
101
- var oreIndexOptsSchema = import_zod.z.object({});
102
- var uniqueIndexOptsSchema = import_zod.z.object({
103
- token_filters: import_zod.z.array(tokenFilterSchema).default([]).optional()
104
- });
105
- var matchIndexOptsSchema = import_zod.z.object({
106
- tokenizer: tokenizerSchema,
107
- token_filters: import_zod.z.array(tokenFilterSchema).default([]).optional(),
108
- k: import_zod.z.number().default(6).optional(),
109
- m: import_zod.z.number().default(2048).optional(),
110
- include_original: import_zod.z.boolean().default(false).optional()
111
- });
112
- var arrayIndexModeSchema = import_zod.z.union([
113
- import_zod.z.literal("all"),
114
- import_zod.z.literal("none"),
115
- import_zod.z.object({
116
- item: import_zod.z.boolean().optional(),
117
- wildcard: import_zod.z.boolean().optional(),
118
- position: import_zod.z.boolean().optional()
119
- })
120
- ]);
121
- var steVecIndexOptsSchema = import_zod.z.object({
122
- prefix: import_zod.z.string(),
123
- array_index_mode: arrayIndexModeSchema.optional()
124
- });
125
- var indexesSchema = import_zod.z.object({
126
- ore: oreIndexOptsSchema.optional(),
127
- unique: uniqueIndexOptsSchema.optional(),
128
- match: matchIndexOptsSchema.optional(),
129
- ste_vec: steVecIndexOptsSchema.optional()
130
- }).default({});
131
- var columnSchema = import_zod.z.object({
132
- cast_as: castAsEnum,
133
- indexes: indexesSchema
134
- }).default({});
135
- var tableSchema = import_zod.z.record(columnSchema).default({});
136
- var tablesSchema = import_zod.z.record(tableSchema).default({});
137
- var encryptConfigSchema = import_zod.z.object({
138
- v: import_zod.z.number(),
139
- tables: tablesSchema
140
- });
141
- var EncryptedField = class {
142
- valueName;
143
- castAsValue;
144
- constructor(valueName) {
145
- this.valueName = valueName;
146
- this.castAsValue = "string";
147
- }
148
- /**
149
- * Set or override the plaintext data type for this field.
150
- *
151
- * By default all values are treated as `'string'`. Use this method to specify
152
- * a different type so the encryption layer knows how to encode the plaintext
153
- * before encrypting.
154
- *
155
- * @param castAs - The plaintext data type: `'string'`, `'number'`, `'boolean'`, `'date'`, `'text'`, `'bigint'`, or `'json'`.
156
- * @returns This `EncryptedField` instance for method chaining.
157
- *
158
- * @example
159
- * ```typescript
160
- * import { encryptedField } from "@cipherstash/stack/schema"
161
- *
162
- * const age = encryptedField("age").dataType("number")
163
- * ```
164
- */
165
- dataType(castAs) {
166
- this.castAsValue = castAs;
167
- return this;
168
- }
169
- build() {
170
- return {
171
- cast_as: this.castAsValue,
172
- indexes: {}
173
- };
174
- }
175
- getName() {
176
- return this.valueName;
177
- }
178
- };
179
- var EncryptedColumn = class {
180
- columnName;
181
- castAsValue;
182
- indexesValue = {};
183
- constructor(columnName) {
184
- this.columnName = columnName;
185
- this.castAsValue = "string";
186
- }
187
- /**
188
- * Set or override the plaintext data type for this column.
189
- *
190
- * By default all columns are treated as `'string'`. Use this method to specify
191
- * a different type so the encryption layer knows how to encode the plaintext
192
- * before encrypting.
193
- *
194
- * @param castAs - The plaintext data type: `'string'`, `'number'`, `'boolean'`, `'date'`, `'bigint'`, or `'json'`.
195
- * @returns This `EncryptedColumn` instance for method chaining.
196
- *
197
- * @example
198
- * ```typescript
199
- * import { encryptedColumn } from "@cipherstash/stack/schema"
200
- *
201
- * const dateOfBirth = encryptedColumn("date_of_birth").dataType("date")
202
- * ```
203
- */
204
- dataType(castAs) {
205
- this.castAsValue = castAs;
206
- return this;
207
- }
208
- /**
209
- * Enable Order-Revealing Encryption (ORE) indexing on this column.
210
- *
211
- * ORE allows sorting, comparison, and range queries on encrypted data.
212
- * Use with `encryptQuery` and `queryType: 'orderAndRange'`.
213
- *
214
- * @returns This `EncryptedColumn` instance for method chaining.
215
- *
216
- * @example
217
- * ```typescript
218
- * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
219
- *
220
- * const users = encryptedTable("users", {
221
- * email: encryptedColumn("email").orderAndRange(),
222
- * })
223
- * ```
224
- */
225
- orderAndRange() {
226
- this.indexesValue.ore = {};
227
- return this;
228
- }
229
- /**
230
- * Enable an exact-match (unique) index on this column.
231
- *
232
- * Allows equality queries on encrypted data. Use with `encryptQuery`
233
- * and `queryType: 'equality'`.
234
- *
235
- * @param tokenFilters - Optional array of token filters (e.g. `[{ kind: 'downcase' }]`).
236
- * When omitted, no token filters are applied.
237
- * @returns This `EncryptedColumn` instance for method chaining.
238
- *
239
- * @example
240
- * ```typescript
241
- * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
242
- *
243
- * const users = encryptedTable("users", {
244
- * email: encryptedColumn("email").equality(),
245
- * })
246
- * ```
247
- */
248
- equality(tokenFilters) {
249
- this.indexesValue.unique = {
250
- token_filters: tokenFilters ?? []
251
- };
252
- return this;
253
- }
254
- /**
255
- * Enable a full-text / fuzzy search (match) index on this column.
256
- *
257
- * Uses n-gram tokenization by default for substring and fuzzy matching.
258
- * Use with `encryptQuery` and `queryType: 'freeTextSearch'`.
259
- *
260
- * @param opts - Optional match index configuration. Defaults to 3-character ngram
261
- * tokenization with a downcase filter, `k=6`, `m=2048`, and `include_original=true`.
262
- * @returns This `EncryptedColumn` instance for method chaining.
263
- *
264
- * @example
265
- * ```typescript
266
- * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
267
- *
268
- * const users = encryptedTable("users", {
269
- * email: encryptedColumn("email").freeTextSearch(),
270
- * })
271
- *
272
- * // With custom options
273
- * const posts = encryptedTable("posts", {
274
- * body: encryptedColumn("body").freeTextSearch({
275
- * tokenizer: { kind: "ngram", token_length: 4 },
276
- * k: 8,
277
- * m: 4096,
278
- * }),
279
- * })
280
- * ```
281
- */
282
- freeTextSearch(opts) {
283
- this.indexesValue.match = {
284
- tokenizer: opts?.tokenizer ?? { kind: "ngram", token_length: 3 },
285
- token_filters: opts?.token_filters ?? [
286
- {
287
- kind: "downcase"
288
- }
289
- ],
290
- k: opts?.k ?? 6,
291
- m: opts?.m ?? 2048,
292
- include_original: opts?.include_original ?? true
293
- };
294
- return this;
295
- }
296
- /**
297
- * Configure this column for searchable encrypted JSON (STE-Vec).
298
- *
299
- * Enables encrypted JSONPath selector queries (e.g. `'$.user.email'`) and
300
- * containment queries (e.g. `{ role: 'admin' }`). Automatically sets the
301
- * data type to `'json'`.
302
- *
303
- * When used with `encryptQuery`, the query operation is auto-inferred from
304
- * the plaintext type: strings become selector queries, objects/arrays become
305
- * containment queries.
306
- *
307
- * @returns This `EncryptedColumn` instance for method chaining.
308
- *
309
- * @example
310
- * ```typescript
311
- * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
312
- *
313
- * const documents = encryptedTable("documents", {
314
- * metadata: encryptedColumn("metadata").searchableJson(),
315
- * })
316
- * ```
317
- */
318
- searchableJson() {
319
- this.castAsValue = "json";
320
- this.indexesValue.ste_vec = { prefix: "enabled", array_index_mode: "all" };
321
- return this;
322
- }
323
- build() {
324
- return {
325
- cast_as: this.castAsValue,
326
- indexes: this.indexesValue
327
- };
328
- }
329
- getName() {
330
- return this.columnName;
331
- }
332
- };
333
- var EncryptedTable = class {
334
- constructor(tableName, columnBuilders) {
335
- this.tableName = tableName;
336
- this.columnBuilders = columnBuilders;
337
- }
338
- /**
339
- * Compile this table schema into a `TableDefinition` used internally by the encryption client.
340
- *
341
- * Iterates over all column builders, calls `.build()` on each, and assembles
342
- * the final `{ tableName, columns }` structure. For `searchableJson()` columns,
343
- * the STE-Vec prefix is automatically set to `"<tableName>/<columnName>"`.
344
- *
345
- * @returns A `TableDefinition` containing the table name and built column configs.
346
- *
347
- * @example
348
- * ```typescript
349
- * const users = encryptedTable("users", {
350
- * email: encryptedColumn("email").equality(),
351
- * })
352
- *
353
- * const definition = users.build()
354
- * // { tableName: "users", columns: { email: { cast_as: "string", indexes: { unique: ... } } } }
355
- * ```
356
- */
357
- build() {
358
- const builtColumns = {};
359
- const processColumn = (builder, colName) => {
360
- if (builder instanceof EncryptedColumn) {
361
- const builtColumn = builder.build();
362
- if (builtColumn.cast_as === "json" && builtColumn.indexes.ste_vec?.prefix === "enabled") {
363
- builtColumns[colName] = {
364
- ...builtColumn,
365
- indexes: {
366
- ...builtColumn.indexes,
367
- ste_vec: {
368
- ...builtColumn.indexes.ste_vec,
369
- prefix: `${this.tableName}/${colName}`
370
- }
371
- }
372
- };
373
- } else {
374
- builtColumns[colName] = builtColumn;
375
- }
376
- } else {
377
- for (const [key, value] of Object.entries(builder)) {
378
- if (value instanceof EncryptedField) {
379
- builtColumns[value.getName()] = value.build();
380
- } else {
381
- processColumn(value, key);
382
- }
383
- }
384
- }
385
- };
386
- for (const [colName, builder] of Object.entries(this.columnBuilders)) {
387
- processColumn(builder, colName);
388
- }
389
- return {
390
- tableName: this.tableName,
391
- columns: builtColumns
392
- };
393
- }
394
- };
395
- function encryptedTable(tableName, columns) {
396
- const tableBuilder = new EncryptedTable(
397
- tableName,
398
- columns
399
- );
400
- for (const [colName, colBuilder] of Object.entries(columns)) {
401
- ;
402
- tableBuilder[colName] = colBuilder;
403
- }
404
- return tableBuilder;
405
- }
406
- function encryptedColumn(columnName) {
407
- return new EncryptedColumn(columnName);
408
- }
409
- function buildEncryptConfig(...protectTables) {
410
- const config = {
411
- v: 2,
412
- tables: {}
413
- };
414
- for (const tb of protectTables) {
415
- const tableDef = tb.build();
416
- config.tables[tableDef.tableName] = tableDef.columns;
417
- }
418
- return config;
419
- }
420
-
421
- // src/errors/index.ts
422
- var EncryptionErrorTypes = {
423
- ClientInitError: "ClientInitError",
424
- EncryptionError: "EncryptionError",
425
- DecryptionError: "DecryptionError",
426
- LockContextError: "LockContextError",
427
- CtsTokenError: "CtsTokenError"
428
- };
429
-
430
- // src/utils/logger/index.ts
431
- var import_evlog = require("evlog");
432
- var validLevels = ["debug", "info", "error"];
433
- function levelFromEnv() {
434
- const env = process.env.STASH_STACK_LOG;
435
- if (env && validLevels.includes(env)) return env;
436
- return "error";
437
- }
438
- function samplingRatesForLevel(level) {
439
- switch (level) {
440
- case "debug":
441
- return { debug: 100, info: 100, warn: 100, error: 100 };
442
- case "info":
443
- return { debug: 0, info: 100, warn: 100, error: 100 };
444
- case "error":
445
- default:
446
- return { debug: 0, info: 0, warn: 0, error: 100 };
447
- }
448
- }
449
- var initialized = false;
450
- function initStackLogger() {
451
- if (initialized) return;
452
- initialized = true;
453
- const level = levelFromEnv();
454
- const rates = samplingRatesForLevel(level);
455
- (0, import_evlog.initLogger)({
456
- env: { service: "@cipherstash/stack" },
457
- enabled: true,
458
- sampling: { rates }
459
- });
460
- }
461
- initStackLogger();
462
- function safeMessage(args) {
463
- return typeof args[0] === "string" ? args[0] : "";
464
- }
465
- var logger = {
466
- debug(...args) {
467
- const log = (0, import_evlog.createRequestLogger)();
468
- log.set({
469
- level: "debug",
470
- source: "@cipherstash/stack",
471
- message: safeMessage(args)
472
- });
473
- log.emit();
474
- },
475
- info(...args) {
476
- const log = (0, import_evlog.createRequestLogger)();
477
- log.set({ source: "@cipherstash/stack" });
478
- log.info(safeMessage(args));
479
- log.emit();
480
- },
481
- warn(...args) {
482
- const log = (0, import_evlog.createRequestLogger)();
483
- log.warn(safeMessage(args));
484
- log.emit();
485
- },
486
- error(...args) {
487
- const log = (0, import_evlog.createRequestLogger)();
488
- log.error(safeMessage(args));
489
- log.emit();
490
- }
491
- };
492
-
493
- // src/encryption/index.ts
494
- var import_result11 = require("@byteslice/result");
495
- var import_protect_ffi9 = require("@cipherstash/protect-ffi");
496
- var import_uuid = require("uuid");
497
-
498
- // src/encryption/helpers/type-guards.ts
499
- function isScalarQueryTermArray(value) {
500
- return Array.isArray(value) && value.length > 0 && typeof value[0] === "object" && value[0] !== null && "column" in value[0] && "table" in value[0];
501
- }
502
-
503
- // src/encryption/helpers/error-code.ts
504
- var import_protect_ffi = require("@cipherstash/protect-ffi");
505
- function getErrorCode(error) {
506
- return error instanceof import_protect_ffi.ProtectError ? error.code : void 0;
507
- }
508
-
509
- // src/encryption/operations/batch-encrypt-query.ts
510
- var import_result = require("@byteslice/result");
511
- var import_protect_ffi2 = require("@cipherstash/protect-ffi");
512
-
513
- // src/types.ts
514
- var queryTypeToFfi = {
515
- orderAndRange: "ore",
516
- freeTextSearch: "match",
517
- equality: "unique",
518
- steVecSelector: "ste_vec",
519
- steVecTerm: "ste_vec",
520
- searchableJson: "ste_vec"
521
- };
522
- var queryTypeToQueryOp = {
523
- steVecSelector: "ste_vec_selector",
524
- steVecTerm: "ste_vec_term"
525
- };
526
-
527
- // src/encryption/helpers/infer-index-type.ts
528
- function inferIndexType(column) {
529
- const config = column.build();
530
- const indexes = config.indexes;
531
- if (!indexes || Object.keys(indexes).length === 0) {
532
- throw new Error(`Column "${column.getName()}" has no indexes configured`);
533
- }
534
- if (indexes.unique) return "unique";
535
- if (indexes.match) return "match";
536
- if (indexes.ore) return "ore";
537
- if (indexes.ste_vec) return "ste_vec";
538
- throw new Error(
539
- `Column "${column.getName()}" has no suitable index for queries`
540
- );
541
- }
542
- function inferQueryOpFromPlaintext(plaintext) {
543
- if (typeof plaintext === "string") {
544
- return "ste_vec_selector";
545
- }
546
- if (typeof plaintext === "object" || typeof plaintext === "number" || typeof plaintext === "boolean" || typeof plaintext === "bigint") {
547
- return "ste_vec_term";
548
- }
549
- return "ste_vec_term";
550
- }
551
- function validateIndexType(column, indexType) {
552
- const config = column.build();
553
- const indexes = config.indexes ?? {};
554
- const indexMap = {
555
- unique: !!indexes.unique,
556
- match: !!indexes.match,
557
- ore: !!indexes.ore,
558
- ste_vec: !!indexes.ste_vec
559
- };
560
- if (!indexMap[indexType]) {
561
- throw new Error(
562
- `Index type "${indexType}" is not configured on column "${column.getName()}"`
563
- );
564
- }
565
- }
566
- function resolveIndexType(column, queryType, plaintext) {
567
- const indexType = queryType ? queryTypeToFfi[queryType] : inferIndexType(column);
568
- if (queryType) {
569
- validateIndexType(column, indexType);
570
- if (queryType === "searchableJson") {
571
- if (plaintext === void 0 || plaintext === null) {
572
- return { indexType };
573
- }
574
- return { indexType, queryOp: inferQueryOpFromPlaintext(plaintext) };
575
- }
576
- return { indexType, queryOp: queryTypeToQueryOp[queryType] };
577
- }
578
- if (indexType === "ste_vec") {
579
- if (plaintext === void 0 || plaintext === null) {
580
- return { indexType };
581
- }
582
- return { indexType, queryOp: inferQueryOpFromPlaintext(plaintext) };
583
- }
584
- return { indexType };
585
- }
586
-
587
- // src/encryption/helpers/validation.ts
588
- function validateNumericValue(value) {
589
- if (typeof value === "number" && Number.isNaN(value)) {
590
- return {
591
- failure: {
592
- type: EncryptionErrorTypes.EncryptionError,
593
- message: "[encryption]: Cannot encrypt NaN value"
594
- }
595
- };
596
- }
597
- if (typeof value === "number" && !Number.isFinite(value)) {
598
- return {
599
- failure: {
600
- type: EncryptionErrorTypes.EncryptionError,
601
- message: "[encryption]: Cannot encrypt Infinity value"
602
- }
603
- };
604
- }
605
- return void 0;
606
- }
607
- function assertValidNumericValue(value) {
608
- if (typeof value === "number" && Number.isNaN(value)) {
609
- throw new Error("[encryption]: Cannot encrypt NaN value");
610
- }
611
- if (typeof value === "number" && !Number.isFinite(value)) {
612
- throw new Error("[encryption]: Cannot encrypt Infinity value");
613
- }
614
- }
615
- function assertValueIndexCompatibility(value, indexType, columnName) {
616
- if (typeof value === "number" && indexType === "match") {
617
- throw new Error(
618
- `[encryption]: Cannot use 'match' index with numeric value on column "${columnName}". The 'freeTextSearch' index only supports string values. Configure the column with 'orderAndRange()' or 'equality()' for numeric queries.`
619
- );
620
- }
621
- }
622
-
623
- // src/encryption/operations/base-operation.ts
624
- var EncryptionOperation = class {
625
- auditMetadata;
626
- /**
627
- * Attach audit metadata to this operation. Can be chained.
628
- * @param config Configuration for ZeroKMS audit logging
629
- * @param config.metadata Arbitrary JSON object for appending metadata to the audit log
630
- */
631
- audit(config) {
632
- this.auditMetadata = config.metadata;
633
- return this;
634
- }
635
- /**
636
- * Get the audit data for this operation.
637
- */
638
- getAuditData() {
639
- return {
640
- metadata: this.auditMetadata
641
- };
642
- }
643
- /**
644
- * Make the operation thenable
645
- */
646
- then(onfulfilled, onrejected) {
647
- return this.execute().then(onfulfilled, onrejected);
648
- }
649
- };
650
-
651
- // src/encryption/operations/batch-encrypt-query.ts
652
- function buildQueryPayload(term, lockContext) {
653
- assertValidNumericValue(term.value);
654
- const { indexType, queryOp } = resolveIndexType(
655
- term.column,
656
- term.queryType,
657
- term.value
658
- );
659
- assertValueIndexCompatibility(term.value, indexType, term.column.getName());
660
- const payload = {
661
- plaintext: term.value,
662
- column: term.column.getName(),
663
- table: term.table.tableName,
664
- indexType,
665
- queryOp
666
- };
667
- if (lockContext != null) {
668
- payload.lockContext = lockContext;
669
- }
670
- return payload;
671
- }
672
- function assembleResults(terms, encryptedValues) {
673
- return terms.map(
674
- (term, i) => formatEncryptedResult(encryptedValues[i], term.returnType)
675
- );
676
- }
677
- var BatchEncryptQueryOperation = class extends EncryptionOperation {
678
- constructor(client, terms) {
679
- super();
680
- this.client = client;
681
- this.terms = terms;
682
- }
683
- withLockContext(lockContext) {
684
- return new BatchEncryptQueryOperationWithLockContext(
685
- this.client,
686
- this.terms,
687
- lockContext,
688
- this.auditMetadata
689
- );
690
- }
691
- async execute() {
692
- const log = (0, import_evlog.createRequestLogger)();
693
- log.set({
694
- op: "batchEncryptQuery",
695
- count: this.terms.length,
696
- lockContext: false
697
- });
698
- if (this.terms.length === 0) {
699
- log.emit();
700
- return { data: [] };
701
- }
702
- const result = await (0, import_result.withResult)(
703
- async () => {
704
- if (!this.client) throw noClientError();
705
- const { metadata } = this.getAuditData();
706
- const queries = this.terms.map(
707
- (term) => buildQueryPayload(term)
708
- );
709
- const encrypted = await (0, import_protect_ffi2.encryptQueryBulk)(this.client, {
710
- queries,
711
- unverifiedContext: metadata
712
- });
713
- return assembleResults(this.terms, encrypted);
714
- },
715
- (error) => {
716
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
717
- return {
718
- type: EncryptionErrorTypes.EncryptionError,
719
- message: error.message,
720
- code: getErrorCode(error)
721
- };
722
- }
723
- );
724
- log.emit();
725
- return result;
726
- }
727
- };
728
- var BatchEncryptQueryOperationWithLockContext = class extends EncryptionOperation {
729
- constructor(client, terms, lockContext, auditMetadata) {
730
- super();
731
- this.client = client;
732
- this.terms = terms;
733
- this.lockContext = lockContext;
734
- this.auditMetadata = auditMetadata;
735
- }
736
- async execute() {
737
- const log = (0, import_evlog.createRequestLogger)();
738
- log.set({
739
- op: "batchEncryptQuery",
740
- count: this.terms.length,
741
- lockContext: true
742
- });
743
- if (this.terms.length === 0) {
744
- log.emit();
745
- return { data: [] };
746
- }
747
- const lockContextResult = await this.lockContext.getLockContext();
748
- if (lockContextResult.failure) {
749
- log.emit();
750
- return { failure: lockContextResult.failure };
751
- }
752
- const { ctsToken, context } = lockContextResult.data;
753
- const result = await (0, import_result.withResult)(
754
- async () => {
755
- if (!this.client) throw noClientError();
756
- const { metadata } = this.getAuditData();
757
- const queries = this.terms.map(
758
- (term) => buildQueryPayload(term, context)
759
- );
760
- const encrypted = await (0, import_protect_ffi2.encryptQueryBulk)(this.client, {
761
- queries,
762
- serviceToken: ctsToken,
763
- unverifiedContext: metadata
764
- });
765
- return assembleResults(this.terms, encrypted);
766
- },
767
- (error) => {
768
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
769
- return {
770
- type: EncryptionErrorTypes.EncryptionError,
771
- message: error.message,
772
- code: getErrorCode(error)
773
- };
774
- }
775
- );
776
- log.emit();
777
- return result;
778
- }
779
- };
780
-
781
- // src/encryption/operations/bulk-decrypt.ts
782
- var import_result2 = require("@byteslice/result");
783
- var import_protect_ffi3 = require("@cipherstash/protect-ffi");
784
- var createDecryptPayloads = (encryptedPayloads, lockContext) => {
785
- return encryptedPayloads.map(({ id, data }) => ({
786
- id,
787
- ciphertext: data,
788
- ...lockContext && { lockContext }
789
- }));
790
- };
791
- var mapDecryptedDataToResult = (encryptedPayloads, decryptedData) => {
792
- return decryptedData.map((decryptResult, i) => {
793
- if ("error" in decryptResult) {
794
- return {
795
- id: encryptedPayloads[i].id,
796
- error: decryptResult.error
797
- };
798
- }
799
- return {
800
- id: encryptedPayloads[i].id,
801
- data: decryptResult.data
802
- };
803
- });
804
- };
805
- var BulkDecryptOperation = class extends EncryptionOperation {
806
- client;
807
- encryptedPayloads;
808
- constructor(client, encryptedPayloads) {
809
- super();
810
- this.client = client;
811
- this.encryptedPayloads = encryptedPayloads;
812
- }
813
- withLockContext(lockContext) {
814
- return new BulkDecryptOperationWithLockContext(this, lockContext);
815
- }
816
- async execute() {
817
- const log = (0, import_evlog.createRequestLogger)();
818
- log.set({
819
- op: "bulkDecrypt",
820
- count: this.encryptedPayloads?.length ?? 0,
821
- lockContext: false
822
- });
823
- const result = await (0, import_result2.withResult)(
824
- async () => {
825
- if (!this.client) throw noClientError();
826
- if (!this.encryptedPayloads || this.encryptedPayloads.length === 0)
827
- return [];
828
- const payloads = createDecryptPayloads(this.encryptedPayloads);
829
- const { metadata } = this.getAuditData();
830
- const decryptedData = await (0, import_protect_ffi3.decryptBulkFallible)(this.client, {
831
- ciphertexts: payloads,
832
- unverifiedContext: metadata
833
- });
834
- return mapDecryptedDataToResult(this.encryptedPayloads, decryptedData);
835
- },
836
- (error) => {
837
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
838
- return {
839
- type: EncryptionErrorTypes.DecryptionError,
840
- message: error.message,
841
- code: getErrorCode(error)
842
- };
843
- }
844
- );
845
- log.emit();
846
- return result;
847
- }
848
- getOperation() {
849
- return {
850
- client: this.client,
851
- encryptedPayloads: this.encryptedPayloads
852
- };
853
- }
854
- };
855
- var BulkDecryptOperationWithLockContext = class extends EncryptionOperation {
856
- operation;
857
- lockContext;
858
- constructor(operation, lockContext) {
859
- super();
860
- this.operation = operation;
861
- this.lockContext = lockContext;
862
- const auditData = operation.getAuditData();
863
- if (auditData) {
864
- this.audit(auditData);
865
- }
866
- }
867
- async execute() {
868
- const { client, encryptedPayloads } = this.operation.getOperation();
869
- const log = (0, import_evlog.createRequestLogger)();
870
- log.set({
871
- op: "bulkDecrypt",
872
- count: encryptedPayloads?.length ?? 0,
873
- lockContext: true
874
- });
875
- const result = await (0, import_result2.withResult)(
876
- async () => {
877
- if (!client) throw noClientError();
878
- if (!encryptedPayloads || encryptedPayloads.length === 0) return [];
879
- const context = await this.lockContext.getLockContext();
880
- if (context.failure) {
881
- throw new Error(`[encryption]: ${context.failure.message}`);
882
- }
883
- const payloads = createDecryptPayloads(
884
- encryptedPayloads,
885
- context.data.context
886
- );
887
- const { metadata } = this.getAuditData();
888
- const decryptedData = await (0, import_protect_ffi3.decryptBulkFallible)(client, {
889
- ciphertexts: payloads,
890
- serviceToken: context.data.ctsToken,
891
- unverifiedContext: metadata
892
- });
893
- return mapDecryptedDataToResult(encryptedPayloads, decryptedData);
894
- },
895
- (error) => {
896
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
897
- return {
898
- type: EncryptionErrorTypes.DecryptionError,
899
- message: error.message,
900
- code: getErrorCode(error)
901
- };
902
- }
903
- );
904
- log.emit();
905
- return result;
906
- }
907
- };
908
-
909
- // src/encryption/operations/bulk-decrypt-models.ts
910
- var import_result3 = require("@byteslice/result");
911
-
912
- // src/encryption/helpers/model-helpers.ts
913
- var import_protect_ffi4 = require("@cipherstash/protect-ffi");
914
- function setNestedValue(obj, path2, value) {
915
- const FORBIDDEN_KEYS = ["__proto__", "prototype", "constructor"];
916
- let current = obj;
917
- for (let i = 0; i < path2.length - 1; i++) {
918
- const part = path2[i];
919
- if (FORBIDDEN_KEYS.includes(part)) {
920
- throw new Error(`[encryption]: Forbidden key "${part}" in field path`);
921
- }
922
- if (!(part in current) || typeof current[part] !== "object" || current[part] === null) {
923
- current[part] = {};
924
- }
925
- current = current[part];
926
- }
927
- const lastKey = path2[path2.length - 1];
928
- if (FORBIDDEN_KEYS.includes(lastKey)) {
929
- throw new Error(`[encryption]: Forbidden key "${lastKey}" in field path`);
930
- }
931
- current[lastKey] = value;
932
- }
933
- async function handleSingleModelBulkOperation(items, operation, keyMap) {
934
- if (items.length === 0) {
935
- return {};
936
- }
937
- const results = await operation(items);
938
- const mappedResults = {};
939
- results.forEach((result, index) => {
940
- const originalKey = keyMap[index.toString()];
941
- mappedResults[originalKey] = result;
942
- });
943
- return mappedResults;
944
- }
945
- async function handleMultiModelBulkOperation(items, operation, keyMap) {
946
- if (items.length === 0) {
947
- return {};
948
- }
949
- const results = await operation(items);
950
- const mappedResults = {};
951
- results.forEach((result, index) => {
952
- const key = index.toString();
953
- const { modelIndex, fieldKey } = keyMap[key];
954
- mappedResults[`${modelIndex}-${fieldKey}`] = result;
955
- });
956
- return mappedResults;
957
- }
958
- function prepareFieldsForDecryption(model) {
959
- const otherFields = { ...model };
960
- const operationFields = {};
961
- const nullFields = {};
962
- const keyMap = {};
963
- let index = 0;
964
- const processNestedFields = (obj, prefix = "") => {
965
- for (const [key, value] of Object.entries(obj)) {
966
- const fullKey = prefix ? `${prefix}.${key}` : key;
967
- if (value === null || value === void 0) {
968
- nullFields[fullKey] = value;
969
- continue;
970
- }
971
- if (typeof value === "object" && !isEncryptedPayload(value)) {
972
- processNestedFields(value, fullKey);
973
- } else if (isEncryptedPayload(value)) {
974
- const id = index.toString();
975
- keyMap[id] = fullKey;
976
- operationFields[fullKey] = value;
977
- index++;
978
- const parts = fullKey.split(".");
979
- let current = otherFields;
980
- for (let i = 0; i < parts.length - 1; i++) {
981
- current = current[parts[i]];
982
- }
983
- delete current[parts[parts.length - 1]];
984
- }
985
- }
986
- };
987
- processNestedFields(model);
988
- return { otherFields, operationFields, keyMap, nullFields };
989
- }
990
- function prepareFieldsForEncryption(model, table) {
991
- const otherFields = { ...model };
992
- const operationFields = {};
993
- const nullFields = {};
994
- const keyMap = {};
995
- let index = 0;
996
- const processNestedFields = (obj, prefix = "", columnPaths2 = []) => {
997
- for (const [key, value] of Object.entries(obj)) {
998
- const fullKey = prefix ? `${prefix}.${key}` : key;
999
- if (value === null || value === void 0) {
1000
- nullFields[fullKey] = value;
1001
- continue;
1002
- }
1003
- if (typeof value === "object" && !isEncryptedPayload(value) && !columnPaths2.includes(fullKey)) {
1004
- if (columnPaths2.some((path2) => path2.startsWith(fullKey))) {
1005
- processNestedFields(
1006
- value,
1007
- fullKey,
1008
- columnPaths2
1009
- );
1010
- }
1011
- } else if (columnPaths2.includes(fullKey)) {
1012
- const id = index.toString();
1013
- keyMap[id] = fullKey;
1014
- operationFields[fullKey] = value;
1015
- index++;
1016
- const parts = fullKey.split(".");
1017
- let current = otherFields;
1018
- for (let i = 0; i < parts.length - 1; i++) {
1019
- current = current[parts[i]];
1020
- }
1021
- delete current[parts[parts.length - 1]];
1022
- }
1023
- }
1024
- };
1025
- const columnPaths = Object.keys(table.build().columns);
1026
- processNestedFields(model, "", columnPaths);
1027
- return { otherFields, operationFields, keyMap, nullFields };
1028
- }
1029
- async function decryptModelFields(model, client, auditData) {
1030
- if (!client) {
1031
- throw new Error("Client not initialized");
1032
- }
1033
- const { otherFields, operationFields, keyMap, nullFields } = prepareFieldsForDecryption(model);
1034
- const bulkDecryptPayload = Object.entries(operationFields).map(
1035
- ([key, value]) => ({
1036
- id: key,
1037
- ciphertext: value
1038
- })
1039
- );
1040
- const decryptedFields = await handleSingleModelBulkOperation(
1041
- bulkDecryptPayload,
1042
- (items) => (0, import_protect_ffi4.decryptBulk)(client, {
1043
- ciphertexts: items,
1044
- unverifiedContext: auditData?.metadata
1045
- }),
1046
- keyMap
1047
- );
1048
- const result = { ...otherFields };
1049
- for (const [key, value] of Object.entries(nullFields)) {
1050
- const parts = key.split(".");
1051
- setNestedValue(result, parts, value);
1052
- }
1053
- for (const [key, value] of Object.entries(decryptedFields)) {
1054
- const parts = key.split(".");
1055
- setNestedValue(result, parts, value);
1056
- }
1057
- return result;
1058
- }
1059
- async function encryptModelFields(model, table, client, auditData) {
1060
- if (!client) {
1061
- throw new Error("Client not initialized");
1062
- }
1063
- const { otherFields, operationFields, keyMap, nullFields } = prepareFieldsForEncryption(model, table);
1064
- const bulkEncryptPayload = Object.entries(operationFields).map(
1065
- ([key, value]) => ({
1066
- id: key,
1067
- plaintext: value,
1068
- table: table.tableName,
1069
- column: key
1070
- })
1071
- );
1072
- const encryptedData = await handleSingleModelBulkOperation(
1073
- bulkEncryptPayload,
1074
- (items) => (0, import_protect_ffi4.encryptBulk)(client, {
1075
- plaintexts: items,
1076
- unverifiedContext: auditData?.metadata
1077
- }),
1078
- keyMap
1079
- );
1080
- const result = { ...otherFields };
1081
- for (const [key, value] of Object.entries(nullFields)) {
1082
- const parts = key.split(".");
1083
- setNestedValue(result, parts, value);
1084
- }
1085
- for (const [key, value] of Object.entries(encryptedData)) {
1086
- const parts = key.split(".");
1087
- setNestedValue(result, parts, value);
1088
- }
1089
- return result;
1090
- }
1091
- async function decryptModelFieldsWithLockContext(model, client, lockContext, auditData) {
1092
- if (!client) {
1093
- throw new Error("Client not initialized");
1094
- }
1095
- if (!lockContext) {
1096
- throw new Error("Lock context is not initialized");
1097
- }
1098
- const { otherFields, operationFields, keyMap, nullFields } = prepareFieldsForDecryption(model);
1099
- const bulkDecryptPayload = Object.entries(operationFields).map(
1100
- ([key, value]) => ({
1101
- id: key,
1102
- ciphertext: value,
1103
- lockContext: lockContext.context
1104
- })
1105
- );
1106
- const decryptedFields = await handleSingleModelBulkOperation(
1107
- bulkDecryptPayload,
1108
- (items) => (0, import_protect_ffi4.decryptBulk)(client, {
1109
- ciphertexts: items,
1110
- serviceToken: lockContext.ctsToken,
1111
- unverifiedContext: auditData?.metadata
1112
- }),
1113
- keyMap
1114
- );
1115
- const result = { ...otherFields };
1116
- for (const [key, value] of Object.entries(nullFields)) {
1117
- const parts = key.split(".");
1118
- setNestedValue(result, parts, value);
1119
- }
1120
- for (const [key, value] of Object.entries(decryptedFields)) {
1121
- const parts = key.split(".");
1122
- setNestedValue(result, parts, value);
1123
- }
1124
- return result;
1125
- }
1126
- async function encryptModelFieldsWithLockContext(model, table, client, lockContext, auditData) {
1127
- if (!client) {
1128
- throw new Error("Client not initialized");
1129
- }
1130
- if (!lockContext) {
1131
- throw new Error("Lock context is not initialized");
1132
- }
1133
- const { otherFields, operationFields, keyMap, nullFields } = prepareFieldsForEncryption(model, table);
1134
- const bulkEncryptPayload = Object.entries(operationFields).map(
1135
- ([key, value]) => ({
1136
- id: key,
1137
- plaintext: value,
1138
- table: table.tableName,
1139
- column: key,
1140
- lockContext: lockContext.context
1141
- })
1142
- );
1143
- const encryptedData = await handleSingleModelBulkOperation(
1144
- bulkEncryptPayload,
1145
- (items) => (0, import_protect_ffi4.encryptBulk)(client, {
1146
- plaintexts: items,
1147
- serviceToken: lockContext.ctsToken,
1148
- unverifiedContext: auditData?.metadata
1149
- }),
1150
- keyMap
1151
- );
1152
- const result = { ...otherFields };
1153
- for (const [key, value] of Object.entries(nullFields)) {
1154
- const parts = key.split(".");
1155
- setNestedValue(result, parts, value);
1156
- }
1157
- for (const [key, value] of Object.entries(encryptedData)) {
1158
- const parts = key.split(".");
1159
- setNestedValue(result, parts, value);
1160
- }
1161
- return result;
1162
- }
1163
- function prepareBulkModelsForOperation(models, table) {
1164
- const otherFields = [];
1165
- const operationFields = [];
1166
- const nullFields = [];
1167
- const keyMap = {};
1168
- let index = 0;
1169
- for (let modelIndex = 0; modelIndex < models.length; modelIndex++) {
1170
- const model = models[modelIndex];
1171
- const modelOtherFields = { ...model };
1172
- const modelOperationFields = {};
1173
- const modelNullFields = {};
1174
- const processNestedFields = (obj, prefix = "", columnPaths = []) => {
1175
- for (const [key, value] of Object.entries(obj)) {
1176
- const fullKey = prefix ? `${prefix}.${key}` : key;
1177
- if (value === null || value === void 0) {
1178
- modelNullFields[fullKey] = value;
1179
- continue;
1180
- }
1181
- if (typeof value === "object" && !isEncryptedPayload(value) && !columnPaths.includes(fullKey)) {
1182
- if (columnPaths.some((path2) => path2.startsWith(fullKey))) {
1183
- processNestedFields(
1184
- value,
1185
- fullKey,
1186
- columnPaths
1187
- );
1188
- }
1189
- } else if (columnPaths.includes(fullKey)) {
1190
- const id = index.toString();
1191
- keyMap[id] = { modelIndex, fieldKey: fullKey };
1192
- modelOperationFields[fullKey] = value;
1193
- index++;
1194
- const parts = fullKey.split(".");
1195
- let current = modelOtherFields;
1196
- for (let i = 0; i < parts.length - 1; i++) {
1197
- current = current[parts[i]];
1198
- }
1199
- delete current[parts[parts.length - 1]];
1200
- }
1201
- }
1202
- };
1203
- if (table) {
1204
- const columnPaths = Object.keys(table.build().columns);
1205
- processNestedFields(model, "", columnPaths);
1206
- } else {
1207
- const processEncryptedFields = (obj, prefix = "", columnPaths = []) => {
1208
- for (const [key, value] of Object.entries(obj)) {
1209
- const fullKey = prefix ? `${prefix}.${key}` : key;
1210
- if (value === null || value === void 0) {
1211
- modelNullFields[fullKey] = value;
1212
- continue;
1213
- }
1214
- if (typeof value === "object" && !isEncryptedPayload(value) && !columnPaths.includes(fullKey)) {
1215
- processEncryptedFields(
1216
- value,
1217
- fullKey,
1218
- columnPaths
1219
- );
1220
- } else if (isEncryptedPayload(value)) {
1221
- const id = index.toString();
1222
- keyMap[id] = { modelIndex, fieldKey: fullKey };
1223
- modelOperationFields[fullKey] = value;
1224
- index++;
1225
- const parts = fullKey.split(".");
1226
- let current = modelOtherFields;
1227
- for (let i = 0; i < parts.length - 1; i++) {
1228
- current = current[parts[i]];
1229
- }
1230
- delete current[parts[parts.length - 1]];
1231
- }
1232
- }
1233
- };
1234
- processEncryptedFields(model);
1235
- }
1236
- otherFields.push(modelOtherFields);
1237
- operationFields.push(modelOperationFields);
1238
- nullFields.push(modelNullFields);
1239
- }
1240
- return { otherFields, operationFields, keyMap, nullFields };
1241
- }
1242
- async function bulkEncryptModels(models, table, client, auditData) {
1243
- if (!client) {
1244
- throw new Error("Client not initialized");
1245
- }
1246
- if (!models || models.length === 0) {
1247
- return [];
1248
- }
1249
- const { otherFields, operationFields, keyMap, nullFields } = prepareBulkModelsForOperation(models, table);
1250
- const bulkEncryptPayload = operationFields.flatMap(
1251
- (fields, modelIndex) => Object.entries(fields).map(([key, value]) => ({
1252
- id: `${modelIndex}-${key}`,
1253
- plaintext: value,
1254
- table: table.tableName,
1255
- column: key
1256
- }))
1257
- );
1258
- const encryptedData = await handleMultiModelBulkOperation(
1259
- bulkEncryptPayload,
1260
- (items) => (0, import_protect_ffi4.encryptBulk)(client, {
1261
- plaintexts: items,
1262
- unverifiedContext: auditData?.metadata
1263
- }),
1264
- keyMap
1265
- );
1266
- return models.map((_, modelIndex) => {
1267
- const result = { ...otherFields[modelIndex] };
1268
- for (const [key, value] of Object.entries(nullFields[modelIndex])) {
1269
- const parts = key.split(".");
1270
- setNestedValue(result, parts, value);
1271
- }
1272
- const modelData = Object.fromEntries(
1273
- Object.entries(encryptedData).filter(([key]) => {
1274
- const [idx] = key.split("-");
1275
- return Number.parseInt(idx) === modelIndex;
1276
- }).map(([key, value]) => {
1277
- const [_2, fieldKey] = key.split("-");
1278
- return [fieldKey, value];
1279
- })
1280
- );
1281
- for (const [key, value] of Object.entries(modelData)) {
1282
- const parts = key.split(".");
1283
- setNestedValue(result, parts, value);
1284
- }
1285
- return result;
1286
- });
1287
- }
1288
- async function bulkDecryptModels(models, client, auditData) {
1289
- if (!client) {
1290
- throw new Error("Client not initialized");
1291
- }
1292
- if (!models || models.length === 0) {
1293
- return [];
1294
- }
1295
- const { otherFields, operationFields, keyMap, nullFields } = prepareBulkModelsForOperation(models);
1296
- const bulkDecryptPayload = operationFields.flatMap(
1297
- (fields, modelIndex) => Object.entries(fields).map(([key, value]) => ({
1298
- id: `${modelIndex}-${key}`,
1299
- ciphertext: value
1300
- }))
1301
- );
1302
- const decryptedFields = await handleMultiModelBulkOperation(
1303
- bulkDecryptPayload,
1304
- (items) => (0, import_protect_ffi4.decryptBulk)(client, {
1305
- ciphertexts: items,
1306
- unverifiedContext: auditData?.metadata
1307
- }),
1308
- keyMap
1309
- );
1310
- return models.map((_, modelIndex) => {
1311
- const result = { ...otherFields[modelIndex] };
1312
- for (const [key, value] of Object.entries(nullFields[modelIndex])) {
1313
- const parts = key.split(".");
1314
- setNestedValue(result, parts, value);
1315
- }
1316
- const modelData = Object.fromEntries(
1317
- Object.entries(decryptedFields).filter(([key]) => {
1318
- const [idx] = key.split("-");
1319
- return Number.parseInt(idx) === modelIndex;
1320
- }).map(([key, value]) => {
1321
- const [_2, fieldKey] = key.split("-");
1322
- return [fieldKey, value];
1323
- })
1324
- );
1325
- for (const [key, value] of Object.entries(modelData)) {
1326
- const parts = key.split(".");
1327
- setNestedValue(result, parts, value);
1328
- }
1329
- return result;
1330
- });
1331
- }
1332
- async function bulkDecryptModelsWithLockContext(models, client, lockContext, auditData) {
1333
- if (!client) {
1334
- throw new Error("Client not initialized");
1335
- }
1336
- if (!lockContext) {
1337
- throw new Error("Lock context is not initialized");
1338
- }
1339
- const { otherFields, operationFields, keyMap, nullFields } = prepareBulkModelsForOperation(models);
1340
- const bulkDecryptPayload = operationFields.flatMap(
1341
- (fields, modelIndex) => Object.entries(fields).map(([key, value]) => ({
1342
- id: `${modelIndex}-${key}`,
1343
- ciphertext: value,
1344
- lockContext: lockContext.context
1345
- }))
1346
- );
1347
- const decryptedFields = await handleMultiModelBulkOperation(
1348
- bulkDecryptPayload,
1349
- (items) => (0, import_protect_ffi4.decryptBulk)(client, {
1350
- ciphertexts: items,
1351
- serviceToken: lockContext.ctsToken,
1352
- unverifiedContext: auditData?.metadata
1353
- }),
1354
- keyMap
1355
- );
1356
- return models.map((_, modelIndex) => {
1357
- const result = { ...otherFields[modelIndex] };
1358
- for (const [key, value] of Object.entries(nullFields[modelIndex])) {
1359
- const parts = key.split(".");
1360
- setNestedValue(result, parts, value);
1361
- }
1362
- const modelData = Object.fromEntries(
1363
- Object.entries(decryptedFields).filter(([key]) => {
1364
- const [idx] = key.split("-");
1365
- return Number.parseInt(idx) === modelIndex;
1366
- }).map(([key, value]) => {
1367
- const [_2, fieldKey] = key.split("-");
1368
- return [fieldKey, value];
1369
- })
1370
- );
1371
- for (const [key, value] of Object.entries(modelData)) {
1372
- const parts = key.split(".");
1373
- setNestedValue(result, parts, value);
1374
- }
1375
- return result;
1376
- });
1377
- }
1378
- async function bulkEncryptModelsWithLockContext(models, table, client, lockContext, auditData) {
1379
- if (!client) {
1380
- throw new Error("Client not initialized");
1381
- }
1382
- if (!lockContext) {
1383
- throw new Error("Lock context is not initialized");
1384
- }
1385
- const { otherFields, operationFields, keyMap, nullFields } = prepareBulkModelsForOperation(models, table);
1386
- const bulkEncryptPayload = operationFields.flatMap(
1387
- (fields, modelIndex) => Object.entries(fields).map(([key, value]) => ({
1388
- id: `${modelIndex}-${key}`,
1389
- plaintext: value,
1390
- table: table.tableName,
1391
- column: key,
1392
- lockContext: lockContext.context
1393
- }))
1394
- );
1395
- const encryptedData = await handleMultiModelBulkOperation(
1396
- bulkEncryptPayload,
1397
- (items) => (0, import_protect_ffi4.encryptBulk)(client, {
1398
- plaintexts: items,
1399
- serviceToken: lockContext.ctsToken,
1400
- unverifiedContext: auditData?.metadata
1401
- }),
1402
- keyMap
1403
- );
1404
- return models.map((_, modelIndex) => {
1405
- const result = { ...otherFields[modelIndex] };
1406
- for (const [key, value] of Object.entries(nullFields[modelIndex])) {
1407
- const parts = key.split(".");
1408
- setNestedValue(result, parts, value);
1409
- }
1410
- const modelData = Object.fromEntries(
1411
- Object.entries(encryptedData).filter(([key]) => {
1412
- const [idx] = key.split("-");
1413
- return Number.parseInt(idx) === modelIndex;
1414
- }).map(([key, value]) => {
1415
- const [_2, fieldKey] = key.split("-");
1416
- return [fieldKey, value];
1417
- })
1418
- );
1419
- for (const [key, value] of Object.entries(modelData)) {
1420
- const parts = key.split(".");
1421
- setNestedValue(result, parts, value);
1422
- }
1423
- return result;
1424
- });
1425
- }
1426
-
1427
- // src/encryption/operations/bulk-decrypt-models.ts
1428
- var BulkDecryptModelsOperation = class extends EncryptionOperation {
1429
- client;
1430
- models;
1431
- constructor(client, models) {
1432
- super();
1433
- this.client = client;
1434
- this.models = models;
1435
- }
1436
- withLockContext(lockContext) {
1437
- return new BulkDecryptModelsOperationWithLockContext(this, lockContext);
1438
- }
1439
- async execute() {
1440
- const log = (0, import_evlog.createRequestLogger)();
1441
- log.set({
1442
- op: "bulkDecryptModels",
1443
- count: this.models.length,
1444
- lockContext: false
1445
- });
1446
- const result = await (0, import_result3.withResult)(
1447
- async () => {
1448
- if (!this.client) {
1449
- throw noClientError();
1450
- }
1451
- const auditData = this.getAuditData();
1452
- return await bulkDecryptModels(this.models, this.client, auditData);
1453
- },
1454
- (error) => {
1455
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1456
- return {
1457
- type: EncryptionErrorTypes.DecryptionError,
1458
- message: error.message,
1459
- code: getErrorCode(error)
1460
- };
1461
- }
1462
- );
1463
- log.emit();
1464
- return result;
1465
- }
1466
- getOperation() {
1467
- return {
1468
- client: this.client,
1469
- models: this.models
1470
- };
1471
- }
1472
- };
1473
- var BulkDecryptModelsOperationWithLockContext = class extends EncryptionOperation {
1474
- operation;
1475
- lockContext;
1476
- constructor(operation, lockContext) {
1477
- super();
1478
- this.operation = operation;
1479
- this.lockContext = lockContext;
1480
- const auditData = operation.getAuditData();
1481
- if (auditData) {
1482
- this.audit(auditData);
1483
- }
1484
- }
1485
- async execute() {
1486
- const { client, models } = this.operation.getOperation();
1487
- const log = (0, import_evlog.createRequestLogger)();
1488
- log.set({
1489
- op: "bulkDecryptModels",
1490
- count: models.length,
1491
- lockContext: true
1492
- });
1493
- const result = await (0, import_result3.withResult)(
1494
- async () => {
1495
- if (!client) {
1496
- throw noClientError();
1497
- }
1498
- const context = await this.lockContext.getLockContext();
1499
- if (context.failure) {
1500
- throw new Error(`[encryption]: ${context.failure.message}`);
1501
- }
1502
- const auditData = this.getAuditData();
1503
- return await bulkDecryptModelsWithLockContext(
1504
- models,
1505
- client,
1506
- context.data,
1507
- auditData
1508
- );
1509
- },
1510
- (error) => {
1511
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1512
- return {
1513
- type: EncryptionErrorTypes.DecryptionError,
1514
- message: error.message,
1515
- code: getErrorCode(error)
1516
- };
1517
- }
1518
- );
1519
- log.emit();
1520
- return result;
1521
- }
1522
- };
1523
-
1524
- // src/encryption/operations/bulk-encrypt.ts
1525
- var import_result4 = require("@byteslice/result");
1526
- var import_protect_ffi5 = require("@cipherstash/protect-ffi");
1527
- var createEncryptPayloads = (plaintexts, column, table, lockContext) => {
1528
- return plaintexts.map(({ id, plaintext }) => ({
1529
- id,
1530
- plaintext,
1531
- column: column.getName(),
1532
- table: table.tableName,
1533
- ...lockContext && { lockContext }
1534
- }));
1535
- };
1536
- var BulkEncryptOperation = class extends EncryptionOperation {
1537
- client;
1538
- plaintexts;
1539
- column;
1540
- table;
1541
- constructor(client, plaintexts, opts) {
1542
- super();
1543
- this.client = client;
1544
- this.plaintexts = plaintexts;
1545
- this.column = opts.column;
1546
- this.table = opts.table;
1547
- }
1548
- withLockContext(lockContext) {
1549
- return new BulkEncryptOperationWithLockContext(this, lockContext);
1550
- }
1551
- async execute() {
1552
- const log = (0, import_evlog.createRequestLogger)();
1553
- log.set({
1554
- op: "bulkEncrypt",
1555
- table: this.table.tableName,
1556
- column: this.column.getName(),
1557
- count: this.plaintexts?.length ?? 0,
1558
- lockContext: false
1559
- });
1560
- const result = await (0, import_result4.withResult)(
1561
- async () => {
1562
- if (!this.client) {
1563
- throw noClientError();
1564
- }
1565
- if (!this.plaintexts || this.plaintexts.length === 0) {
1566
- return [];
1567
- }
1568
- const payloads = createEncryptPayloads(
1569
- this.plaintexts,
1570
- this.column,
1571
- this.table
1572
- );
1573
- const { metadata } = this.getAuditData();
1574
- const encryptedData = await (0, import_protect_ffi5.encryptBulk)(this.client, {
1575
- plaintexts: payloads,
1576
- unverifiedContext: metadata
1577
- });
1578
- return encryptedData.map((data, i) => ({
1579
- id: this.plaintexts[i].id,
1580
- data
1581
- }));
1582
- },
1583
- (error) => {
1584
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1585
- return {
1586
- type: EncryptionErrorTypes.EncryptionError,
1587
- message: error.message,
1588
- code: getErrorCode(error)
1589
- };
1590
- }
1591
- );
1592
- log.emit();
1593
- return result;
1594
- }
1595
- getOperation() {
1596
- return {
1597
- client: this.client,
1598
- plaintexts: this.plaintexts,
1599
- column: this.column,
1600
- table: this.table
1601
- };
1602
- }
1603
- };
1604
- var BulkEncryptOperationWithLockContext = class extends EncryptionOperation {
1605
- operation;
1606
- lockContext;
1607
- constructor(operation, lockContext) {
1608
- super();
1609
- this.operation = operation;
1610
- this.lockContext = lockContext;
1611
- const auditData = operation.getAuditData();
1612
- if (auditData) {
1613
- this.audit(auditData);
1614
- }
1615
- }
1616
- async execute() {
1617
- const { client, plaintexts, column, table } = this.operation.getOperation();
1618
- const log = (0, import_evlog.createRequestLogger)();
1619
- log.set({
1620
- op: "bulkEncrypt",
1621
- table: table.tableName,
1622
- column: column.getName(),
1623
- count: plaintexts?.length ?? 0,
1624
- lockContext: true
1625
- });
1626
- const result = await (0, import_result4.withResult)(
1627
- async () => {
1628
- if (!client) {
1629
- throw noClientError();
1630
- }
1631
- if (!plaintexts || plaintexts.length === 0) {
1632
- return [];
1633
- }
1634
- const context = await this.lockContext.getLockContext();
1635
- if (context.failure) {
1636
- throw new Error(`[encryption]: ${context.failure.message}`);
1637
- }
1638
- const payloads = createEncryptPayloads(
1639
- plaintexts,
1640
- column,
1641
- table,
1642
- context.data.context
1643
- );
1644
- const { metadata } = this.getAuditData();
1645
- const encryptedData = await (0, import_protect_ffi5.encryptBulk)(client, {
1646
- plaintexts: payloads,
1647
- serviceToken: context.data.ctsToken,
1648
- unverifiedContext: metadata
1649
- });
1650
- return encryptedData.map((data, i) => ({
1651
- id: plaintexts[i].id,
1652
- data
1653
- }));
1654
- },
1655
- (error) => {
1656
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1657
- return {
1658
- type: EncryptionErrorTypes.EncryptionError,
1659
- message: error.message,
1660
- code: getErrorCode(error)
1661
- };
1662
- }
1663
- );
1664
- log.emit();
1665
- return result;
1666
- }
1667
- };
1668
-
1669
- // src/encryption/operations/bulk-encrypt-models.ts
1670
- var import_result5 = require("@byteslice/result");
1671
- var BulkEncryptModelsOperation = class extends EncryptionOperation {
1672
- client;
1673
- models;
1674
- table;
1675
- constructor(client, models, table) {
1676
- super();
1677
- this.client = client;
1678
- this.models = models;
1679
- this.table = table;
1680
- }
1681
- withLockContext(lockContext) {
1682
- return new BulkEncryptModelsOperationWithLockContext(this, lockContext);
1683
- }
1684
- async execute() {
1685
- const log = (0, import_evlog.createRequestLogger)();
1686
- log.set({
1687
- op: "bulkEncryptModels",
1688
- table: this.table.tableName,
1689
- count: this.models.length,
1690
- lockContext: false
1691
- });
1692
- const result = await (0, import_result5.withResult)(
1693
- async () => {
1694
- if (!this.client) {
1695
- throw noClientError();
1696
- }
1697
- const auditData = this.getAuditData();
1698
- return await bulkEncryptModels(
1699
- this.models,
1700
- this.table,
1701
- this.client,
1702
- auditData
1703
- );
1704
- },
1705
- (error) => {
1706
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1707
- return {
1708
- type: EncryptionErrorTypes.EncryptionError,
1709
- message: error.message,
1710
- code: getErrorCode(error)
1711
- };
1712
- }
1713
- );
1714
- log.emit();
1715
- return result;
1716
- }
1717
- getOperation() {
1718
- return {
1719
- client: this.client,
1720
- models: this.models,
1721
- table: this.table
1722
- };
1723
- }
1724
- };
1725
- var BulkEncryptModelsOperationWithLockContext = class extends EncryptionOperation {
1726
- operation;
1727
- lockContext;
1728
- constructor(operation, lockContext) {
1729
- super();
1730
- this.operation = operation;
1731
- this.lockContext = lockContext;
1732
- const auditData = operation.getAuditData();
1733
- if (auditData) {
1734
- this.audit(auditData);
1735
- }
1736
- }
1737
- async execute() {
1738
- const { client, models, table } = this.operation.getOperation();
1739
- const log = (0, import_evlog.createRequestLogger)();
1740
- log.set({
1741
- op: "bulkEncryptModels",
1742
- table: table.tableName,
1743
- count: models.length,
1744
- lockContext: true
1745
- });
1746
- const result = await (0, import_result5.withResult)(
1747
- async () => {
1748
- if (!client) {
1749
- throw noClientError();
1750
- }
1751
- const context = await this.lockContext.getLockContext();
1752
- if (context.failure) {
1753
- throw new Error(`[encryption]: ${context.failure.message}`);
1754
- }
1755
- const auditData = this.getAuditData();
1756
- return await bulkEncryptModelsWithLockContext(
1757
- models,
1758
- table,
1759
- client,
1760
- context.data,
1761
- auditData
1762
- );
1763
- },
1764
- (error) => {
1765
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1766
- return {
1767
- type: EncryptionErrorTypes.EncryptionError,
1768
- message: error.message,
1769
- code: getErrorCode(error)
1770
- };
1771
- }
1772
- );
1773
- log.emit();
1774
- return result;
1775
- }
1776
- };
1777
-
1778
- // src/encryption/operations/decrypt.ts
1779
- var import_result6 = require("@byteslice/result");
1780
- var import_protect_ffi6 = require("@cipherstash/protect-ffi");
1781
- var DecryptOperation = class extends EncryptionOperation {
1782
- client;
1783
- encryptedData;
1784
- constructor(client, encryptedData) {
1785
- super();
1786
- this.client = client;
1787
- this.encryptedData = encryptedData;
1788
- }
1789
- withLockContext(lockContext) {
1790
- return new DecryptOperationWithLockContext(this, lockContext);
1791
- }
1792
- async execute() {
1793
- const log = (0, import_evlog.createRequestLogger)();
1794
- log.set({
1795
- op: "decrypt",
1796
- lockContext: false
1797
- });
1798
- const result = await (0, import_result6.withResult)(
1799
- async () => {
1800
- if (!this.client) {
1801
- throw noClientError();
1802
- }
1803
- const { metadata } = this.getAuditData();
1804
- return await (0, import_protect_ffi6.decrypt)(this.client, {
1805
- ciphertext: this.encryptedData,
1806
- unverifiedContext: metadata
1807
- });
1808
- },
1809
- (error) => {
1810
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1811
- return {
1812
- type: EncryptionErrorTypes.DecryptionError,
1813
- message: error.message,
1814
- code: getErrorCode(error)
1815
- };
1816
- }
1817
- );
1818
- log.emit();
1819
- return result;
1820
- }
1821
- getOperation() {
1822
- return {
1823
- client: this.client,
1824
- encryptedData: this.encryptedData,
1825
- auditData: this.getAuditData()
1826
- };
1827
- }
1828
- };
1829
- var DecryptOperationWithLockContext = class extends EncryptionOperation {
1830
- operation;
1831
- lockContext;
1832
- constructor(operation, lockContext) {
1833
- super();
1834
- this.operation = operation;
1835
- this.lockContext = lockContext;
1836
- const auditData = operation.getAuditData();
1837
- if (auditData) {
1838
- this.audit(auditData);
1839
- }
1840
- }
1841
- async execute() {
1842
- const log = (0, import_evlog.createRequestLogger)();
1843
- log.set({
1844
- op: "decrypt",
1845
- lockContext: true
1846
- });
1847
- const result = await (0, import_result6.withResult)(
1848
- async () => {
1849
- const { client, encryptedData } = this.operation.getOperation();
1850
- if (!client) {
1851
- throw noClientError();
1852
- }
1853
- const { metadata } = this.getAuditData();
1854
- const context = await this.lockContext.getLockContext();
1855
- if (context.failure) {
1856
- throw new Error(`[encryption]: ${context.failure.message}`);
1857
- }
1858
- return await (0, import_protect_ffi6.decrypt)(client, {
1859
- ciphertext: encryptedData,
1860
- unverifiedContext: metadata,
1861
- lockContext: context.data.context,
1862
- serviceToken: context.data.ctsToken
1863
- });
1864
- },
1865
- (error) => {
1866
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1867
- return {
1868
- type: EncryptionErrorTypes.DecryptionError,
1869
- message: error.message,
1870
- code: getErrorCode(error)
1871
- };
1872
- }
1873
- );
1874
- log.emit();
1875
- return result;
1876
- }
1877
- };
1878
-
1879
- // src/encryption/operations/decrypt-model.ts
1880
- var import_result7 = require("@byteslice/result");
1881
- var DecryptModelOperation = class extends EncryptionOperation {
1882
- client;
1883
- model;
1884
- constructor(client, model) {
1885
- super();
1886
- this.client = client;
1887
- this.model = model;
1888
- }
1889
- withLockContext(lockContext) {
1890
- return new DecryptModelOperationWithLockContext(this, lockContext);
1891
- }
1892
- async execute() {
1893
- const log = (0, import_evlog.createRequestLogger)();
1894
- log.set({
1895
- op: "decryptModel",
1896
- lockContext: false
1897
- });
1898
- const result = await (0, import_result7.withResult)(
1899
- async () => {
1900
- if (!this.client) {
1901
- throw noClientError();
1902
- }
1903
- const auditData = this.getAuditData();
1904
- return await decryptModelFields(this.model, this.client, auditData);
1905
- },
1906
- (error) => {
1907
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1908
- return {
1909
- type: EncryptionErrorTypes.DecryptionError,
1910
- message: error.message,
1911
- code: getErrorCode(error)
1912
- };
1913
- }
1914
- );
1915
- log.emit();
1916
- return result;
1917
- }
1918
- getOperation() {
1919
- return {
1920
- client: this.client,
1921
- model: this.model
1922
- };
1923
- }
1924
- };
1925
- var DecryptModelOperationWithLockContext = class extends EncryptionOperation {
1926
- operation;
1927
- lockContext;
1928
- constructor(operation, lockContext) {
1929
- super();
1930
- this.operation = operation;
1931
- this.lockContext = lockContext;
1932
- const auditData = operation.getAuditData();
1933
- if (auditData) {
1934
- this.audit(auditData);
1935
- }
1936
- }
1937
- async execute() {
1938
- const log = (0, import_evlog.createRequestLogger)();
1939
- log.set({
1940
- op: "decryptModel",
1941
- lockContext: true
1942
- });
1943
- const result = await (0, import_result7.withResult)(
1944
- async () => {
1945
- const { client, model } = this.operation.getOperation();
1946
- if (!client) {
1947
- throw noClientError();
1948
- }
1949
- const context = await this.lockContext.getLockContext();
1950
- if (context.failure) {
1951
- throw new Error(`[encryption]: ${context.failure.message}`);
1952
- }
1953
- const auditData = this.getAuditData();
1954
- return await decryptModelFieldsWithLockContext(
1955
- model,
1956
- client,
1957
- context.data,
1958
- auditData
1959
- );
1960
- },
1961
- (error) => {
1962
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1963
- return {
1964
- type: EncryptionErrorTypes.DecryptionError,
1965
- message: error.message,
1966
- code: getErrorCode(error)
1967
- };
1968
- }
1969
- );
1970
- log.emit();
1971
- return result;
1972
- }
1973
- };
1974
-
1975
- // src/encryption/operations/encrypt.ts
1976
- var import_result8 = require("@byteslice/result");
1977
- var import_protect_ffi7 = require("@cipherstash/protect-ffi");
1978
- var EncryptOperation = class extends EncryptionOperation {
1979
- client;
1980
- plaintext;
1981
- column;
1982
- table;
1983
- constructor(client, plaintext, opts) {
1984
- super();
1985
- this.client = client;
1986
- this.plaintext = plaintext;
1987
- this.column = opts.column;
1988
- this.table = opts.table;
1989
- }
1990
- withLockContext(lockContext) {
1991
- return new EncryptOperationWithLockContext(this, lockContext);
1992
- }
1993
- async execute() {
1994
- const log = (0, import_evlog.createRequestLogger)();
1995
- log.set({
1996
- op: "encrypt",
1997
- table: this.table.tableName,
1998
- column: this.column.getName(),
1999
- lockContext: false
2000
- });
2001
- const result = await (0, import_result8.withResult)(
2002
- async () => {
2003
- if (!this.client) {
2004
- throw noClientError();
2005
- }
2006
- if (typeof this.plaintext === "number" && Number.isNaN(this.plaintext)) {
2007
- throw new Error("[encryption]: Cannot encrypt NaN value");
2008
- }
2009
- if (typeof this.plaintext === "number" && !Number.isFinite(this.plaintext)) {
2010
- throw new Error("[encryption]: Cannot encrypt Infinity value");
2011
- }
2012
- const { metadata } = this.getAuditData();
2013
- return await (0, import_protect_ffi7.encrypt)(this.client, {
2014
- plaintext: this.plaintext,
2015
- column: this.column.getName(),
2016
- table: this.table.tableName,
2017
- unverifiedContext: metadata
2018
- });
2019
- },
2020
- (error) => {
2021
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
2022
- return {
2023
- type: EncryptionErrorTypes.EncryptionError,
2024
- message: error.message,
2025
- code: getErrorCode(error)
2026
- };
2027
- }
2028
- );
2029
- log.emit();
2030
- return result;
2031
- }
2032
- getOperation() {
2033
- return {
2034
- client: this.client,
2035
- plaintext: this.plaintext,
2036
- column: this.column,
2037
- table: this.table
2038
- };
2039
- }
2040
- };
2041
- var EncryptOperationWithLockContext = class extends EncryptionOperation {
2042
- operation;
2043
- lockContext;
2044
- constructor(operation, lockContext) {
2045
- super();
2046
- this.operation = operation;
2047
- this.lockContext = lockContext;
2048
- const auditData = operation.getAuditData();
2049
- if (auditData) {
2050
- this.audit(auditData);
2051
- }
2052
- }
2053
- async execute() {
2054
- const { client, plaintext, column, table } = this.operation.getOperation();
2055
- const log = (0, import_evlog.createRequestLogger)();
2056
- log.set({
2057
- op: "encrypt",
2058
- table: table.tableName,
2059
- column: column.getName(),
2060
- lockContext: true
2061
- });
2062
- const result = await (0, import_result8.withResult)(
2063
- async () => {
2064
- if (!client) {
2065
- throw noClientError();
2066
- }
2067
- const { metadata } = this.getAuditData();
2068
- const context = await this.lockContext.getLockContext();
2069
- if (context.failure) {
2070
- throw new Error(`[encryption]: ${context.failure.message}`);
2071
- }
2072
- return await (0, import_protect_ffi7.encrypt)(client, {
2073
- plaintext,
2074
- column: column.getName(),
2075
- table: table.tableName,
2076
- lockContext: context.data.context,
2077
- serviceToken: context.data.ctsToken,
2078
- unverifiedContext: metadata
2079
- });
2080
- },
2081
- (error) => {
2082
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
2083
- return {
2084
- type: EncryptionErrorTypes.EncryptionError,
2085
- message: error.message,
2086
- code: getErrorCode(error)
2087
- };
2088
- }
2089
- );
2090
- log.emit();
2091
- return result;
2092
- }
2093
- };
2094
-
2095
- // src/encryption/operations/encrypt-model.ts
2096
- var import_result9 = require("@byteslice/result");
2097
- var EncryptModelOperation = class extends EncryptionOperation {
2098
- client;
2099
- model;
2100
- table;
2101
- constructor(client, model, table) {
2102
- super();
2103
- this.client = client;
2104
- this.model = model;
2105
- this.table = table;
2106
- }
2107
- withLockContext(lockContext) {
2108
- return new EncryptModelOperationWithLockContext(this, lockContext);
2109
- }
2110
- async execute() {
2111
- const log = (0, import_evlog.createRequestLogger)();
2112
- log.set({
2113
- op: "encryptModel",
2114
- table: this.table.tableName,
2115
- lockContext: false
2116
- });
2117
- const result = await (0, import_result9.withResult)(
2118
- async () => {
2119
- if (!this.client) {
2120
- throw noClientError();
2121
- }
2122
- const auditData = this.getAuditData();
2123
- return await encryptModelFields(
2124
- this.model,
2125
- this.table,
2126
- this.client,
2127
- auditData
2128
- );
2129
- },
2130
- (error) => {
2131
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
2132
- return {
2133
- type: EncryptionErrorTypes.EncryptionError,
2134
- message: error.message,
2135
- code: getErrorCode(error)
2136
- };
2137
- }
2138
- );
2139
- log.emit();
2140
- return result;
2141
- }
2142
- getOperation() {
2143
- return {
2144
- client: this.client,
2145
- model: this.model,
2146
- table: this.table
2147
- };
2148
- }
2149
- };
2150
- var EncryptModelOperationWithLockContext = class extends EncryptionOperation {
2151
- operation;
2152
- lockContext;
2153
- constructor(operation, lockContext) {
2154
- super();
2155
- this.operation = operation;
2156
- this.lockContext = lockContext;
2157
- const auditData = operation.getAuditData();
2158
- if (auditData) {
2159
- this.audit(auditData);
2160
- }
2161
- }
2162
- async execute() {
2163
- const { client, model, table } = this.operation.getOperation();
2164
- const log = (0, import_evlog.createRequestLogger)();
2165
- log.set({
2166
- op: "encryptModel",
2167
- table: table.tableName,
2168
- lockContext: true
2169
- });
2170
- const result = await (0, import_result9.withResult)(
2171
- async () => {
2172
- if (!client) {
2173
- throw noClientError();
2174
- }
2175
- const context = await this.lockContext.getLockContext();
2176
- if (context.failure) {
2177
- throw new Error(`[encryption]: ${context.failure.message}`);
2178
- }
2179
- const auditData = this.getAuditData();
2180
- return await encryptModelFieldsWithLockContext(
2181
- model,
2182
- table,
2183
- client,
2184
- context.data,
2185
- auditData
2186
- );
2187
- },
2188
- (error) => {
2189
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
2190
- return {
2191
- type: EncryptionErrorTypes.EncryptionError,
2192
- message: error.message,
2193
- code: getErrorCode(error)
2194
- };
2195
- }
2196
- );
2197
- log.emit();
2198
- return result;
2199
- }
2200
- };
2201
-
2202
- // src/encryption/operations/encrypt-query.ts
2203
- var import_result10 = require("@byteslice/result");
2204
- var import_protect_ffi8 = require("@cipherstash/protect-ffi");
2205
- var EncryptQueryOperation = class extends EncryptionOperation {
2206
- constructor(client, plaintext, opts) {
2207
- super();
2208
- this.client = client;
2209
- this.plaintext = plaintext;
2210
- this.opts = opts;
2211
- }
2212
- withLockContext(lockContext) {
2213
- return new EncryptQueryOperationWithLockContext(
2214
- this.client,
2215
- this.plaintext,
2216
- this.opts,
2217
- lockContext,
2218
- this.auditMetadata
2219
- );
2220
- }
2221
- async execute() {
2222
- const log = (0, import_evlog.createRequestLogger)();
2223
- log.set({
2224
- op: "encryptQuery",
2225
- table: this.opts.table.tableName,
2226
- column: this.opts.column.getName(),
2227
- queryType: this.opts.queryType,
2228
- lockContext: false
2229
- });
2230
- const validationError = validateNumericValue(this.plaintext);
2231
- if (validationError?.failure) {
2232
- log.emit();
2233
- return { failure: validationError.failure };
2234
- }
2235
- const result = await (0, import_result10.withResult)(
2236
- async () => {
2237
- if (!this.client) throw noClientError();
2238
- const { metadata } = this.getAuditData();
2239
- const { indexType, queryOp } = resolveIndexType(
2240
- this.opts.column,
2241
- this.opts.queryType,
2242
- this.plaintext
2243
- );
2244
- assertValueIndexCompatibility(
2245
- this.plaintext,
2246
- indexType,
2247
- this.opts.column.getName()
2248
- );
2249
- const encrypted = await (0, import_protect_ffi8.encryptQuery)(this.client, {
2250
- plaintext: this.plaintext,
2251
- column: this.opts.column.getName(),
2252
- table: this.opts.table.tableName,
2253
- indexType,
2254
- queryOp,
2255
- unverifiedContext: metadata
2256
- });
2257
- return formatEncryptedResult(encrypted, this.opts.returnType);
2258
- },
2259
- (error) => {
2260
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
2261
- return {
2262
- type: EncryptionErrorTypes.EncryptionError,
2263
- message: error.message,
2264
- code: getErrorCode(error)
2265
- };
2266
- }
2267
- );
2268
- log.emit();
2269
- return result;
2270
- }
2271
- getOperation() {
2272
- return { client: this.client, plaintext: this.plaintext, ...this.opts };
2273
- }
2274
- };
2275
- var EncryptQueryOperationWithLockContext = class extends EncryptionOperation {
2276
- constructor(client, plaintext, opts, lockContext, auditMetadata) {
2277
- super();
2278
- this.client = client;
2279
- this.plaintext = plaintext;
2280
- this.opts = opts;
2281
- this.lockContext = lockContext;
2282
- this.auditMetadata = auditMetadata;
2283
- }
2284
- async execute() {
2285
- const log = (0, import_evlog.createRequestLogger)();
2286
- log.set({
2287
- op: "encryptQuery",
2288
- table: this.opts.table.tableName,
2289
- column: this.opts.column.getName(),
2290
- queryType: this.opts.queryType,
2291
- lockContext: true
2292
- });
2293
- const validationError = validateNumericValue(this.plaintext);
2294
- if (validationError?.failure) {
2295
- log.emit();
2296
- return { failure: validationError.failure };
2297
- }
2298
- const lockContextResult = await this.lockContext.getLockContext();
2299
- if (lockContextResult.failure) {
2300
- log.emit();
2301
- return { failure: lockContextResult.failure };
2302
- }
2303
- const { ctsToken, context } = lockContextResult.data;
2304
- const result = await (0, import_result10.withResult)(
2305
- async () => {
2306
- if (!this.client) throw noClientError();
2307
- const { metadata } = this.getAuditData();
2308
- const { indexType, queryOp } = resolveIndexType(
2309
- this.opts.column,
2310
- this.opts.queryType,
2311
- this.plaintext
2312
- );
2313
- assertValueIndexCompatibility(
2314
- this.plaintext,
2315
- indexType,
2316
- this.opts.column.getName()
2317
- );
2318
- const encrypted = await (0, import_protect_ffi8.encryptQuery)(this.client, {
2319
- plaintext: this.plaintext,
2320
- column: this.opts.column.getName(),
2321
- table: this.opts.table.tableName,
2322
- indexType,
2323
- queryOp,
2324
- lockContext: context,
2325
- serviceToken: ctsToken,
2326
- unverifiedContext: metadata
2327
- });
2328
- return formatEncryptedResult(encrypted, this.opts.returnType);
2329
- },
2330
- (error) => {
2331
- log.set({ errorCode: getErrorCode(error) ?? "unknown" });
2332
- return {
2333
- type: EncryptionErrorTypes.EncryptionError,
2334
- message: error.message,
2335
- code: getErrorCode(error)
2336
- };
2337
- }
2338
- );
2339
- log.emit();
2340
- return result;
2341
- }
2342
- };
2343
-
2344
- // src/encryption/index.ts
2345
- var noClientError = () => new Error(
2346
- "The Encryption client has not been initialized. Please call init() before using the client."
2347
- );
2348
- var EncryptionClient = class {
2349
- client;
2350
- encryptConfig;
2351
- /**
2352
- * Initializes the EncryptionClient with the provided configuration.
2353
- * @internal
2354
- * @param config - The configuration object for initializing the client.
2355
- * @returns A promise that resolves to a {@link Result} containing the initialized EncryptionClient or an {@link EncryptionError}.
2356
- **/
2357
- async init(config) {
2358
- return await (0, import_result11.withResult)(
2359
- async () => {
2360
- const validated = encryptConfigSchema.parse(
2361
- config.encryptConfig
2362
- );
2363
- logger.debug(
2364
- "Initializing the Encryption client with the following config:",
2365
- {
2366
- encryptConfig: validated
2367
- }
2368
- );
2369
- this.client = await (0, import_protect_ffi9.newClient)({
2370
- encryptConfig: validated,
2371
- clientOpts: {
2372
- workspaceCrn: config.workspaceCrn,
2373
- accessKey: config.accessKey,
2374
- clientId: config.clientId,
2375
- clientKey: config.clientKey,
2376
- keyset: toFfiKeysetIdentifier(config.keyset)
2377
- }
2378
- });
2379
- this.encryptConfig = validated;
2380
- logger.debug("Successfully initialized the Encryption client.");
2381
- return this;
2382
- },
2383
- (error) => ({
2384
- type: EncryptionErrorTypes.ClientInitError,
2385
- message: error.message
2386
- })
2387
- );
2388
- }
2389
- /**
2390
- * Encrypt a value - returns a promise which resolves to an encrypted value.
2391
- *
2392
- * @param plaintext - The plaintext value to be encrypted.
2393
- * @param opts - Options specifying the column (or nested field) and table for encryption. See {@link EncryptOptions}.
2394
- * @returns An EncryptOperation that can be awaited or chained with additional methods.
2395
- *
2396
- * @example
2397
- * The following example demonstrates how to encrypt a value using the Encryption client.
2398
- * It includes defining an encryption schema with {@link encryptedTable} and {@link encryptedColumn},
2399
- * initializing the client with {@link Encryption}, and performing the encryption.
2400
- *
2401
- * `encrypt` returns an {@link EncryptOperation} which can be awaited to get a {@link Result}
2402
- * which can either be the encrypted value or an {@link EncryptionError}.
2403
- *
2404
- * ```typescript
2405
- * // Define encryption schema
2406
- * import { Encryption } from "@cipherstash/stack"
2407
- * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
2408
- * const userSchema = encryptedTable("users", {
2409
- * email: encryptedColumn("email"),
2410
- * });
2411
- *
2412
- * // Initialize Encryption client
2413
- * const client = await Encryption({ schemas: [userSchema] })
2414
- *
2415
- * // Encrypt a value
2416
- * const encryptedResult = await client.encrypt(
2417
- * "person@example.com",
2418
- * { column: userSchema.email, table: userSchema }
2419
- * )
2420
- *
2421
- * // Handle encryption result
2422
- * if (encryptedResult.failure) {
2423
- * throw new Error(`Encryption failed: ${encryptedResult.failure.message}`);
2424
- * }
2425
- *
2426
- * console.log("Encrypted data:", encryptedResult.data);
2427
- * ```
2428
- *
2429
- * @example
2430
- * When encrypting data, a {@link LockContext} can be provided to tie the encryption to a specific user or session.
2431
- * This ensures that the same lock context is required for decryption.
2432
- *
2433
- * The following example demonstrates how to create a lock context using a user's JWT token
2434
- * and use it during encryption.
2435
- *
2436
- * ```typescript
2437
- * // Define encryption schema and initialize client as above
2438
- *
2439
- * // Create a lock for the user's `sub` claim from their JWT
2440
- * const lc = new LockContext();
2441
- * const lockContext = await lc.identify(userJwt);
2442
- *
2443
- * if (lockContext.failure) {
2444
- * // Handle the failure
2445
- * }
2446
- *
2447
- * // Encrypt a value with the lock context
2448
- * // Decryption will then require the same lock context
2449
- * const encryptedResult = await client.encrypt(
2450
- * "person@example.com",
2451
- * { column: userSchema.email, table: userSchema }
2452
- * )
2453
- * .withLockContext(lockContext)
2454
- * ```
2455
- *
2456
- * @see {@link EncryptOptions}
2457
- * @see {@link Result}
2458
- * @see {@link encryptedTable}
2459
- * @see {@link encryptedColumn}
2460
- * @see {@link encryptedField}
2461
- * @see {@link LockContext}
2462
- * @see {@link EncryptOperation}
2463
- */
2464
- encrypt(plaintext, opts) {
2465
- return new EncryptOperation(this.client, plaintext, opts);
2466
- }
2467
- encryptQuery(plaintextOrTerms, opts) {
2468
- if (isScalarQueryTermArray(plaintextOrTerms)) {
2469
- return new BatchEncryptQueryOperation(this.client, plaintextOrTerms);
2470
- }
2471
- if (Array.isArray(plaintextOrTerms) && plaintextOrTerms.length === 0 && !opts) {
2472
- return new BatchEncryptQueryOperation(
2473
- this.client,
2474
- []
2475
- );
2476
- }
2477
- if (!opts) {
2478
- throw new Error("EncryptQueryOptions are required");
2479
- }
2480
- return new EncryptQueryOperation(
2481
- this.client,
2482
- plaintextOrTerms,
2483
- opts
2484
- );
2485
- }
2486
- /**
2487
- * Decryption - returns a promise which resolves to a decrypted value.
2488
- *
2489
- * @param encryptedData - The encrypted data to be decrypted.
2490
- * @returns A DecryptOperation that can be awaited or chained with additional methods.
2491
- *
2492
- * @example
2493
- * The following example demonstrates how to decrypt a value that was previously encrypted using the {@link encrypt} method.
2494
- * It includes encrypting a value first, then decrypting it, and handling the result.
2495
- *
2496
- * ```typescript
2497
- * const encryptedData = await client.encrypt(
2498
- * "person@example.com",
2499
- * { column: "email", table: "users" }
2500
- * )
2501
- * const decryptResult = await client.decrypt(encryptedData)
2502
- * if (decryptResult.failure) {
2503
- * throw new Error(`Decryption failed: ${decryptResult.failure.message}`);
2504
- * }
2505
- * console.log("Decrypted data:", decryptResult.data);
2506
- * ```
2507
- *
2508
- * @example
2509
- * Provide a lock context when decrypting:
2510
- * ```typescript
2511
- * await client.decrypt(encryptedData)
2512
- * .withLockContext(lockContext)
2513
- * ```
2514
- *
2515
- * @see {@link LockContext}
2516
- * @see {@link DecryptOperation}
2517
- */
2518
- decrypt(encryptedData) {
2519
- return new DecryptOperation(this.client, encryptedData);
2520
- }
2521
- /**
2522
- * Encrypt a model (object) based on the table schema.
2523
- *
2524
- * Only fields whose keys match columns defined in the table schema are encrypted.
2525
- * All other fields are passed through unchanged. Returns a thenable operation
2526
- * that supports `.withLockContext()` for identity-aware encryption.
2527
- *
2528
- * The return type is **schema-aware**: fields matching the table schema are
2529
- * typed as `Encrypted`, while other fields retain their original types. For
2530
- * best results, let TypeScript infer the type parameters from the arguments
2531
- * rather than providing an explicit type argument.
2532
- *
2533
- * @param input - The model object with plaintext values to encrypt.
2534
- * @param table - The table schema defining which fields to encrypt.
2535
- * @returns An `EncryptModelOperation` that can be awaited to get a `Result`
2536
- * containing the model with schema-defined fields typed as `Encrypted`,
2537
- * or an `EncryptionError`.
2538
- *
2539
- * @example
2540
- * ```typescript
2541
- * import { Encryption } from "@cipherstash/stack"
2542
- * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
2543
- *
2544
- * type User = { id: string; email: string; createdAt: Date }
2545
- *
2546
- * const usersSchema = encryptedTable("users", {
2547
- * email: encryptedColumn("email").equality(),
2548
- * })
2549
- *
2550
- * const client = await Encryption({ schemas: [usersSchema] })
2551
- *
2552
- * // Let TypeScript infer the return type from the schema.
2553
- * // result.data.email is typed as `Encrypted`, result.data.id stays `string`.
2554
- * const result = await client.encryptModel(
2555
- * { id: "user_123", email: "alice@example.com", createdAt: new Date() },
2556
- * usersSchema,
2557
- * )
2558
- *
2559
- * if (result.failure) {
2560
- * console.error(result.failure.message)
2561
- * } else {
2562
- * console.log(result.data.id) // string
2563
- * console.log(result.data.email) // Encrypted
2564
- * }
2565
- * ```
2566
- */
2567
- encryptModel(input, table) {
2568
- return new EncryptModelOperation(
2569
- this.client,
2570
- input,
2571
- table
2572
- );
2573
- }
2574
- /**
2575
- * Decrypt a model (object) whose fields contain encrypted values.
2576
- *
2577
- * Identifies encrypted fields automatically and decrypts them, returning the
2578
- * model with plaintext values. Returns a thenable operation that supports
2579
- * `.withLockContext()` for identity-aware decryption.
2580
- *
2581
- * @param input - The model object with encrypted field values.
2582
- * @returns A `DecryptModelOperation<T>` that can be awaited to get a `Result`
2583
- * containing the model with decrypted plaintext fields, or an `EncryptionError`.
2584
- *
2585
- * @example
2586
- * ```typescript
2587
- * // Decrypt a previously encrypted model
2588
- * const decrypted = await client.decryptModel<User>(encryptedUser)
2589
- *
2590
- * if (decrypted.failure) {
2591
- * console.error(decrypted.failure.message)
2592
- * } else {
2593
- * console.log(decrypted.data.email) // "alice@example.com"
2594
- * }
2595
- *
2596
- * // With a lock context
2597
- * const decrypted = await client
2598
- * .decryptModel<User>(encryptedUser)
2599
- * .withLockContext(lockContext)
2600
- * ```
2601
- */
2602
- decryptModel(input) {
2603
- return new DecryptModelOperation(this.client, input);
2604
- }
2605
- /**
2606
- * Encrypt multiple models (objects) in a single bulk operation.
2607
- *
2608
- * Performs a single call to ZeroKMS regardless of the number of models,
2609
- * while still using a unique key for each encrypted value. Only fields
2610
- * matching the table schema are encrypted; other fields pass through unchanged.
2611
- *
2612
- * The return type is **schema-aware**: fields matching the table schema are
2613
- * typed as `Encrypted`, while other fields retain their original types. For
2614
- * best results, let TypeScript infer the type parameters from the arguments.
2615
- *
2616
- * @param input - An array of model objects with plaintext values to encrypt.
2617
- * @param table - The table schema defining which fields to encrypt.
2618
- * @returns A `BulkEncryptModelsOperation` that can be awaited to get a `Result`
2619
- * containing an array of models with schema-defined fields typed as `Encrypted`,
2620
- * or an `EncryptionError`.
2621
- *
2622
- * @example
2623
- * ```typescript
2624
- * import { Encryption } from "@cipherstash/stack"
2625
- * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
2626
- *
2627
- * type User = { id: string; email: string }
2628
- *
2629
- * const usersSchema = encryptedTable("users", {
2630
- * email: encryptedColumn("email"),
2631
- * })
2632
- *
2633
- * const client = await Encryption({ schemas: [usersSchema] })
2634
- *
2635
- * // Let TypeScript infer the return type from the schema.
2636
- * // Each item's email is typed as `Encrypted`, id stays `string`.
2637
- * const result = await client.bulkEncryptModels(
2638
- * [
2639
- * { id: "1", email: "alice@example.com" },
2640
- * { id: "2", email: "bob@example.com" },
2641
- * ],
2642
- * usersSchema,
2643
- * )
2644
- *
2645
- * if (!result.failure) {
2646
- * console.log(result.data) // array of models with encrypted email fields
2647
- * }
2648
- * ```
2649
- */
2650
- bulkEncryptModels(input, table) {
2651
- return new BulkEncryptModelsOperation(
2652
- this.client,
2653
- input,
2654
- table
2655
- );
2656
- }
2657
- /**
2658
- * Decrypt multiple models (objects) in a single bulk operation.
2659
- *
2660
- * Performs a single call to ZeroKMS regardless of the number of models,
2661
- * restoring all encrypted fields to their original plaintext values.
2662
- *
2663
- * @param input - An array of model objects with encrypted field values.
2664
- * @returns A `BulkDecryptModelsOperation<T>` that can be awaited to get a `Result`
2665
- * containing an array of models with decrypted plaintext fields, or an `EncryptionError`.
2666
- *
2667
- * @example
2668
- * ```typescript
2669
- * const encryptedUsers = encryptedResult.data // from bulkEncryptModels
2670
- *
2671
- * const result = await client.bulkDecryptModels<User>(encryptedUsers)
2672
- *
2673
- * if (!result.failure) {
2674
- * for (const user of result.data) {
2675
- * console.log(user.email) // plaintext email
2676
- * }
2677
- * }
2678
- *
2679
- * // With a lock context
2680
- * const result = await client
2681
- * .bulkDecryptModels<User>(encryptedUsers)
2682
- * .withLockContext(lockContext)
2683
- * ```
2684
- */
2685
- bulkDecryptModels(input) {
2686
- return new BulkDecryptModelsOperation(this.client, input);
2687
- }
2688
- /**
2689
- * Encrypt multiple plaintext values in a single bulk operation.
2690
- *
2691
- * Each value is encrypted with its own unique key via a single call to ZeroKMS.
2692
- * Values can include optional `id` fields for correlating results back to
2693
- * your application data.
2694
- *
2695
- * @param plaintexts - An array of objects with `plaintext` (and optional `id`) fields.
2696
- * @param opts - Options specifying the target column (or nested {@link encryptedField}) and table. See {@link EncryptOptions}.
2697
- * @returns A `BulkEncryptOperation` that can be awaited to get a `Result`
2698
- * containing an array of `{ id?, data: Encrypted }` objects, or an `EncryptionError`.
2699
- *
2700
- * @example
2701
- * ```typescript
2702
- * import { Encryption } from "@cipherstash/stack"
2703
- * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
2704
- *
2705
- * const users = encryptedTable("users", {
2706
- * email: encryptedColumn("email"),
2707
- * })
2708
- * const client = await Encryption({ schemas: [users] })
2709
- *
2710
- * const result = await client.bulkEncrypt(
2711
- * [
2712
- * { id: "u1", plaintext: "alice@example.com" },
2713
- * { id: "u2", plaintext: "bob@example.com" },
2714
- * ],
2715
- * { column: users.email, table: users },
2716
- * )
2717
- *
2718
- * if (!result.failure) {
2719
- * // result.data = [{ id: "u1", data: Encrypted }, { id: "u2", data: Encrypted }, ...]
2720
- * console.log(result.data)
2721
- * }
2722
- * ```
2723
- */
2724
- bulkEncrypt(plaintexts, opts) {
2725
- return new BulkEncryptOperation(this.client, plaintexts, opts);
2726
- }
2727
- /**
2728
- * Decrypt multiple encrypted values in a single bulk operation.
2729
- *
2730
- * Performs a single call to ZeroKMS to decrypt all values. The result uses
2731
- * a multi-status pattern: each item in the returned array has either a `data`
2732
- * field (success) or an `error` field (failure), allowing graceful handling
2733
- * of partial failures.
2734
- *
2735
- * @param encryptedPayloads - An array of objects with `data` (encrypted payload) and optional `id` fields.
2736
- * @returns A `BulkDecryptOperation` that can be awaited to get a `Result`
2737
- * containing an array of `{ id?, data: plaintext }` or `{ id?, error: string }` objects,
2738
- * or an `EncryptionError` if the entire operation fails.
2739
- *
2740
- * @example
2741
- * ```typescript
2742
- * const encrypted = await client.bulkEncrypt(plaintexts, { column: users.email, table: users })
2743
- *
2744
- * const result = await client.bulkDecrypt(encrypted.data)
2745
- *
2746
- * if (!result.failure) {
2747
- * for (const item of result.data) {
2748
- * if ("data" in item) {
2749
- * console.log(`${item.id}: ${item.data}`)
2750
- * } else {
2751
- * console.error(`${item.id} failed: ${item.error}`)
2752
- * }
2753
- * }
2754
- * }
2755
- * ```
2756
- */
2757
- bulkDecrypt(encryptedPayloads) {
2758
- return new BulkDecryptOperation(this.client, encryptedPayloads);
2759
- }
2760
- /**
2761
- * Get the encrypt config object.
2762
- *
2763
- * @returns The encrypt config object.
2764
- */
2765
- getEncryptConfig() {
2766
- return this.encryptConfig;
2767
- }
2768
- };
2769
- var Encryption = async (config) => {
2770
- const { schemas, config: clientConfig } = config;
2771
- if (!schemas.length) {
2772
- throw new Error(
2773
- "[encryption]: At least one encryptedTable must be provided to initialize the encryption client"
2774
- );
2775
- }
2776
- if (clientConfig?.keyset && "id" in clientConfig.keyset && !(0, import_uuid.validate)(clientConfig.keyset.id)) {
2777
- throw new Error(
2778
- "[encryption]: Invalid UUID provided for keyset id. Must be a valid UUID."
2779
- );
2780
- }
2781
- const client = new EncryptionClient();
2782
- const encryptConfig = buildEncryptConfig(...schemas);
2783
- const result = await client.init({
2784
- encryptConfig,
2785
- ...clientConfig
2786
- });
2787
- if (result.failure) {
2788
- throw new Error(`[encryption]: ${result.failure.message}`);
2789
- }
2790
- return result.data;
2791
- };
2792
-
2793
- // src/utils/config/index.ts
2794
- var import_node_fs = __toESM(require("fs"), 1);
2795
- var import_node_path = __toESM(require("path"), 1);
2796
- function extractWorkspaceIdFromCrn(crn) {
2797
- const match = crn.match(/crn:[^:]+:([^:]+)$/);
2798
- if (!match) {
2799
- throw new Error("Invalid CRN format");
2800
- }
2801
- return match[1];
2802
- }
2803
-
2804
- // src/secrets/index.ts
2805
- var Secrets = class {
2806
- encryptionClient = null;
2807
- config;
2808
- apiBaseUrl = process.env.STASH_API_URL || "https://dashboard.cipherstash.com/api/secrets";
2809
- secretsSchema = encryptedTable("secrets", {
2810
- value: encryptedColumn("value")
2811
- });
2812
- constructor(config) {
2813
- const workspaceCRN = config.workspaceCRN ?? process.env.CS_WORKSPACE_CRN;
2814
- const clientId = config.clientId ?? process.env.CS_CLIENT_ID;
2815
- const clientKey = config.clientKey ?? process.env.CS_CLIENT_KEY;
2816
- const accessKey = config.accessKey ?? process.env.CS_CLIENT_ACCESS_KEY;
2817
- if (!workspaceCRN || !clientId || !clientKey || !accessKey) {
2818
- throw new Error(
2819
- "Missing required configuration or environment variables."
2820
- );
2821
- }
2822
- this.config = {
2823
- environment: config.environment,
2824
- workspaceCRN,
2825
- clientId,
2826
- clientKey,
2827
- accessKey
2828
- };
2829
- }
2830
- initPromise = null;
2831
- /**
2832
- * Initialize the Secrets client and underlying Encryption client
2833
- */
2834
- async ensureInitialized() {
2835
- if (!this.initPromise) {
2836
- this.initPromise = this._doInit();
2837
- }
2838
- return this.initPromise;
2839
- }
2840
- async _doInit() {
2841
- logger.debug("Initializing the Secrets client.");
2842
- this.encryptionClient = await Encryption({
2843
- schemas: [this.secretsSchema],
2844
- config: {
2845
- workspaceCrn: this.config.workspaceCRN,
2846
- clientId: this.config.clientId,
2847
- clientKey: this.config.clientKey,
2848
- accessKey: this.config.accessKey,
2849
- keyset: { name: this.config.environment }
2850
- }
2851
- });
2852
- logger.debug("Successfully initialized the Secrets client.");
2853
- }
2854
- /**
2855
- * Get the authorization header for API requests
2856
- */
2857
- getAuthHeader() {
2858
- return `Bearer ${this.config.accessKey}`;
2859
- }
2860
- /**
2861
- * Make an API request with error handling.
2862
- *
2863
- * For GET requests, `params` are appended as URL query parameters.
2864
- * For POST requests, `body` is sent as JSON in the request body.
2865
- */
2866
- async apiRequest(method, path2, options) {
2867
- try {
2868
- let url = `${this.apiBaseUrl}${path2}`;
2869
- if (options?.params) {
2870
- const searchParams = new URLSearchParams(options.params);
2871
- url = `${url}?${searchParams.toString()}`;
2872
- }
2873
- logger.debug(`Secrets API request: ${method} ${path2}`);
2874
- const headers = {
2875
- "Content-Type": "application/json",
2876
- Authorization: this.getAuthHeader()
2877
- };
2878
- const response = await fetch(url, {
2879
- method,
2880
- headers,
2881
- body: options?.body ? JSON.stringify(options.body) : void 0
2882
- });
2883
- if (!response.ok) {
2884
- const errorText = await response.text();
2885
- let errorMessage = `API request failed with status ${response.status}`;
2886
- try {
2887
- const errorJson = JSON.parse(errorText);
2888
- errorMessage = errorJson.message || errorJson.error || errorMessage;
2889
- } catch {
2890
- errorMessage = errorText || errorMessage;
2891
- }
2892
- logger.error(`Secrets API error on ${method} ${path2}: ${errorMessage}`);
2893
- return {
2894
- failure: {
2895
- type: "ApiError",
2896
- message: errorMessage
2897
- }
2898
- };
2899
- }
2900
- logger.debug(`Secrets API request successful: ${method} ${path2}`);
2901
- const data = await response.json();
2902
- return { data };
2903
- } catch (error) {
2904
- const message = error instanceof Error ? error.message : "Unknown network error occurred";
2905
- logger.error(`Secrets network error on ${method} ${path2}: ${message}`);
2906
- return {
2907
- failure: {
2908
- type: "NetworkError",
2909
- message
2910
- }
2911
- };
2912
- }
2913
- }
2914
- /**
2915
- * Store an encrypted secret in the vault.
2916
- * The value is encrypted locally before being sent to the API.
2917
- *
2918
- * API: POST /api/secrets/set
2919
- *
2920
- * @param name - The name of the secret
2921
- * @param value - The plaintext value to encrypt and store
2922
- * @returns A Result containing the API response or an error
2923
- */
2924
- async set(name, value) {
2925
- logger.debug("Setting secret");
2926
- await this.ensureInitialized();
2927
- if (!this.encryptionClient) {
2928
- return {
2929
- failure: {
2930
- type: "ClientError",
2931
- message: "Failed to initialize Encryption client"
2932
- }
2933
- };
2934
- }
2935
- const encryptResult = await this.encryptionClient.encrypt(value, {
2936
- column: this.secretsSchema.value,
2937
- table: this.secretsSchema
2938
- });
2939
- if (encryptResult.failure) {
2940
- logger.error("Failed to encrypt secret");
2941
- return {
2942
- failure: {
2943
- type: "EncryptionError",
2944
- message: encryptResult.failure.message
2945
- }
2946
- };
2947
- }
2948
- const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
2949
- return await this.apiRequest("POST", "/set", {
2950
- body: {
2951
- workspaceId,
2952
- environment: this.config.environment,
2953
- name,
2954
- encryptedValue: encryptedToPgComposite(encryptResult.data)
2955
- }
2956
- });
2957
- }
2958
- /**
2959
- * Retrieve and decrypt a secret from the vault.
2960
- * The secret is decrypted locally after retrieval.
2961
- *
2962
- * API: GET /api/secrets/get?workspaceId=...&environment=...&name=...
2963
- *
2964
- * @param name - The name of the secret to retrieve
2965
- * @returns A Result containing the decrypted value or an error
2966
- */
2967
- async get(name) {
2968
- logger.debug("Getting secret");
2969
- await this.ensureInitialized();
2970
- if (!this.encryptionClient) {
2971
- return {
2972
- failure: {
2973
- type: "ClientError",
2974
- message: "Failed to initialize Encryption client"
2975
- }
2976
- };
2977
- }
2978
- const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
2979
- const apiResult = await this.apiRequest("GET", "/get", {
2980
- params: {
2981
- workspaceId,
2982
- environment: this.config.environment,
2983
- name
2984
- }
2985
- });
2986
- if (apiResult.failure) {
2987
- return apiResult;
2988
- }
2989
- const decryptResult = await this.encryptionClient.decrypt(
2990
- apiResult.data.encryptedValue.data
2991
- );
2992
- if (decryptResult.failure) {
2993
- logger.error("Failed to decrypt secret");
2994
- return {
2995
- failure: {
2996
- type: "DecryptionError",
2997
- message: decryptResult.failure.message
2998
- }
2999
- };
3000
- }
3001
- if (typeof decryptResult.data !== "string") {
3002
- logger.error("Decrypted secret value is not a string");
3003
- return {
3004
- failure: {
3005
- type: "DecryptionError",
3006
- message: "Decrypted value is not a string"
3007
- }
3008
- };
3009
- }
3010
- return { data: decryptResult.data };
3011
- }
3012
- /**
3013
- * Retrieve and decrypt many secrets from the vault.
3014
- * The secrets are decrypted locally after retrieval.
3015
- * This method only triggers a single network request to the ZeroKMS.
3016
- *
3017
- * API: GET /api/secrets/get-many?workspaceId=...&environment=...&names=name1,name2,...
3018
- *
3019
- * Constraints:
3020
- * - Minimum 2 secret names required
3021
- * - Maximum 100 secret names per request
3022
- *
3023
- * @param names - The names of the secrets to retrieve (min 2, max 100)
3024
- * @returns A Result containing an object mapping secret names to their decrypted values
3025
- */
3026
- async getMany(names) {
3027
- logger.debug(`Getting ${names.length} secrets.`);
3028
- await this.ensureInitialized();
3029
- if (!this.encryptionClient) {
3030
- return {
3031
- failure: {
3032
- type: "ClientError",
3033
- message: "Failed to initialize Encryption client"
3034
- }
3035
- };
3036
- }
3037
- if (names.length < 2) {
3038
- return {
3039
- failure: {
3040
- type: "ClientError",
3041
- message: "At least 2 secret names are required for getMany"
3042
- }
3043
- };
3044
- }
3045
- if (names.length > 100) {
3046
- return {
3047
- failure: {
3048
- type: "ClientError",
3049
- message: "Maximum 100 secret names per request"
3050
- }
3051
- };
3052
- }
3053
- const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
3054
- const apiResult = await this.apiRequest(
3055
- "GET",
3056
- "/get-many",
3057
- {
3058
- params: {
3059
- workspaceId,
3060
- environment: this.config.environment,
3061
- names: names.join(",")
3062
- }
3063
- }
3064
- );
3065
- if (apiResult.failure) {
3066
- return apiResult;
3067
- }
3068
- const dataToDecrypt = apiResult.data.map((item) => ({
3069
- name: item.name,
3070
- value: item.encryptedValue.data
3071
- }));
3072
- const decryptResult = await this.encryptionClient.bulkDecryptModels(dataToDecrypt);
3073
- if (decryptResult.failure) {
3074
- logger.error(
3075
- `Failed to decrypt secrets: ${decryptResult.failure.message}`
3076
- );
3077
- return {
3078
- failure: {
3079
- type: "DecryptionError",
3080
- message: decryptResult.failure.message
3081
- }
3082
- };
3083
- }
3084
- const decryptedSecrets = decryptResult.data;
3085
- const secretsMap = {};
3086
- for (const secret of decryptedSecrets) {
3087
- if (secret.name && secret.value) {
3088
- secretsMap[secret.name] = secret.value;
3089
- }
3090
- }
3091
- return { data: secretsMap };
3092
- }
3093
- /**
3094
- * List all secrets in the environment.
3095
- * Only names and metadata are returned; values remain encrypted.
3096
- *
3097
- * API: GET /api/secrets/list?workspaceId=...&environment=...
3098
- *
3099
- * @returns A Result containing the list of secrets or an error
3100
- */
3101
- async list() {
3102
- logger.debug("Listing secrets.");
3103
- const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
3104
- const apiResult = await this.apiRequest(
3105
- "GET",
3106
- "/list",
3107
- {
3108
- params: {
3109
- workspaceId,
3110
- environment: this.config.environment
3111
- }
3112
- }
3113
- );
3114
- if (apiResult.failure) {
3115
- return apiResult;
3116
- }
3117
- return { data: apiResult.data.secrets };
3118
- }
3119
- /**
3120
- * Delete a secret from the vault.
3121
- *
3122
- * API: POST /api/secrets/delete
3123
- *
3124
- * @param name - The name of the secret to delete
3125
- * @returns A Result containing the API response or an error
3126
- */
3127
- async delete(name) {
3128
- logger.debug("Deleting secret");
3129
- const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
3130
- return await this.apiRequest("POST", "/delete", {
3131
- body: {
3132
- workspaceId,
3133
- environment: this.config.environment,
3134
- name
3135
- }
3136
- });
3137
- }
3138
- };
3139
- // Annotate the CommonJS export names for ESM import in node:
3140
- 0 && (module.exports = {
3141
- Secrets
3142
- });
3143
- //# sourceMappingURL=index.cjs.map