@cipherstash/stack 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +670 -0
  4. package/dist/bin/stash.js +5049 -0
  5. package/dist/bin/stash.js.map +1 -0
  6. package/dist/chunk-2GZMIJFO.js +2400 -0
  7. package/dist/chunk-2GZMIJFO.js.map +1 -0
  8. package/dist/chunk-5DCT6YU2.js +138 -0
  9. package/dist/chunk-5DCT6YU2.js.map +1 -0
  10. package/dist/chunk-7XRPN2KX.js +336 -0
  11. package/dist/chunk-7XRPN2KX.js.map +1 -0
  12. package/dist/chunk-SJ7JO4ME.js +28 -0
  13. package/dist/chunk-SJ7JO4ME.js.map +1 -0
  14. package/dist/chunk-SUYMGQBY.js +67 -0
  15. package/dist/chunk-SUYMGQBY.js.map +1 -0
  16. package/dist/client-BxJG56Ey.d.cts +647 -0
  17. package/dist/client-DtGq9dJp.d.ts +647 -0
  18. package/dist/client.cjs +347 -0
  19. package/dist/client.cjs.map +1 -0
  20. package/dist/client.d.cts +7 -0
  21. package/dist/client.d.ts +7 -0
  22. package/dist/client.js +11 -0
  23. package/dist/client.js.map +1 -0
  24. package/dist/drizzle/index.cjs +1528 -0
  25. package/dist/drizzle/index.cjs.map +1 -0
  26. package/dist/drizzle/index.d.cts +350 -0
  27. package/dist/drizzle/index.d.ts +350 -0
  28. package/dist/drizzle/index.js +1212 -0
  29. package/dist/drizzle/index.js.map +1 -0
  30. package/dist/dynamodb/index.cjs +382 -0
  31. package/dist/dynamodb/index.cjs.map +1 -0
  32. package/dist/dynamodb/index.d.cts +125 -0
  33. package/dist/dynamodb/index.d.ts +125 -0
  34. package/dist/dynamodb/index.js +355 -0
  35. package/dist/dynamodb/index.js.map +1 -0
  36. package/dist/identity/index.cjs +271 -0
  37. package/dist/identity/index.cjs.map +1 -0
  38. package/dist/identity/index.d.cts +3 -0
  39. package/dist/identity/index.d.ts +3 -0
  40. package/dist/identity/index.js +117 -0
  41. package/dist/identity/index.js.map +1 -0
  42. package/dist/index-9-Ya3fDK.d.cts +169 -0
  43. package/dist/index-9-Ya3fDK.d.ts +169 -0
  44. package/dist/index.cjs +2915 -0
  45. package/dist/index.cjs.map +1 -0
  46. package/dist/index.d.cts +22 -0
  47. package/dist/index.d.ts +22 -0
  48. package/dist/index.js +23 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/schema/index.cjs +368 -0
  51. package/dist/schema/index.cjs.map +1 -0
  52. package/dist/schema/index.d.cts +4 -0
  53. package/dist/schema/index.d.ts +4 -0
  54. package/dist/schema/index.js +23 -0
  55. package/dist/schema/index.js.map +1 -0
  56. package/dist/secrets/index.cjs +3207 -0
  57. package/dist/secrets/index.cjs.map +1 -0
  58. package/dist/secrets/index.d.cts +227 -0
  59. package/dist/secrets/index.d.ts +227 -0
  60. package/dist/secrets/index.js +323 -0
  61. package/dist/secrets/index.js.map +1 -0
  62. package/dist/supabase/index.cjs +1113 -0
  63. package/dist/supabase/index.cjs.map +1 -0
  64. package/dist/supabase/index.d.cts +144 -0
  65. package/dist/supabase/index.d.ts +144 -0
  66. package/dist/supabase/index.js +864 -0
  67. package/dist/supabase/index.js.map +1 -0
  68. package/dist/types-public-BCj1L4fi.d.cts +1013 -0
  69. package/dist/types-public-BCj1L4fi.d.ts +1013 -0
  70. package/dist/types-public.cjs +40 -0
  71. package/dist/types-public.cjs.map +1 -0
  72. package/dist/types-public.d.cts +4 -0
  73. package/dist/types-public.d.ts +4 -0
  74. package/dist/types-public.js +7 -0
  75. package/dist/types-public.js.map +1 -0
  76. package/package.json +202 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/dynamodb/operations/bulk-decrypt-models.ts","../../src/dynamodb/helpers.ts","../../src/dynamodb/operations/base-operation.ts","../../src/dynamodb/operations/bulk-encrypt-models.ts","../../src/dynamodb/operations/decrypt-model.ts","../../src/dynamodb/operations/encrypt-model.ts","../../src/dynamodb/index.ts"],"sourcesContent":["import type { EncryptionClient } from '@/encryption/ffi'\nimport type { ProtectTable, ProtectTableColumn } from '@/schema'\nimport type { Decrypted, EncryptedValue } from '@/types'\nimport { type Result, withResult } from '@byteslice/result'\nimport { handleError, toItemWithEqlPayloads } from '../helpers'\nimport type { EncryptedDynamoDBError } from '../types'\nimport {\n DynamoDBOperation,\n type DynamoDBOperationOptions,\n} from './base-operation'\n\nexport class BulkDecryptModelsOperation<\n T extends Record<string, unknown>,\n> extends DynamoDBOperation<Decrypted<T>[]> {\n private encryptionClient: EncryptionClient\n private items: Record<string, EncryptedValue | unknown>[]\n private table: ProtectTable<ProtectTableColumn>\n\n constructor(\n encryptionClient: EncryptionClient,\n items: Record<string, EncryptedValue | unknown>[],\n table: ProtectTable<ProtectTableColumn>,\n options?: DynamoDBOperationOptions,\n ) {\n super(options)\n this.encryptionClient = encryptionClient\n this.items = items\n this.table = table\n }\n\n public async execute(): Promise<\n Result<Decrypted<T>[], EncryptedDynamoDBError>\n > {\n return await withResult(\n async () => {\n const itemsWithEqlPayloads = this.items.map((item) =>\n toItemWithEqlPayloads(item, this.table),\n )\n\n const decryptResult = await this.encryptionClient\n .bulkDecryptModels<T>(itemsWithEqlPayloads as T[])\n .audit(this.getAuditData())\n\n if (decryptResult.failure) {\n // Create an Error object that preserves the FFI error code\n // This is necessary because withResult's ensureError wraps non-Error objects\n const error = new Error(decryptResult.failure.message) as Error & {\n code?: string\n }\n error.code = decryptResult.failure.code\n throw error\n }\n\n return decryptResult.data\n },\n (error) =>\n handleError(error, 'bulkDecryptModels', {\n logger: this.logger,\n errorHandler: this.errorHandler,\n }),\n )\n }\n}\n","import type { ProtectTable, ProtectTableColumn } from '@/schema'\nimport type { EncryptedValue } from '@/types'\nimport type { ProtectErrorCode } from '@cipherstash/protect-ffi'\nimport { ProtectError as FfiProtectError } from '@cipherstash/protect-ffi'\nimport type { EncryptedDynamoDBError } from './types'\n\nexport const ciphertextAttrSuffix = '__source'\nexport const searchTermAttrSuffix = '__hmac'\n\nexport class EncryptedDynamoDBErrorImpl\n extends Error\n implements EncryptedDynamoDBError\n{\n constructor(\n message: string,\n public code: ProtectErrorCode | 'DYNAMODB_ENCRYPTION_ERROR',\n public details?: Record<string, unknown>,\n ) {\n super(message)\n this.name = 'EncryptedDynamoDBError'\n }\n}\n\nexport function handleError(\n error: unknown,\n context: string,\n options?: {\n logger?: {\n error: (message: string, error: Error) => void\n }\n errorHandler?: (error: EncryptedDynamoDBError) => void\n },\n): EncryptedDynamoDBError {\n // Preserve FFI error code if available, otherwise use generic DynamoDB error code\n // Check for FfiProtectError instance or plain error objects with code property\n const errorObj = error as Record<string, unknown>\n const errorCode =\n error instanceof FfiProtectError\n ? error.code\n : errorObj &&\n typeof errorObj === 'object' &&\n 'code' in errorObj &&\n typeof errorObj.code === 'string'\n ? (errorObj.code as ProtectErrorCode)\n : 'DYNAMODB_ENCRYPTION_ERROR'\n\n const errorMessage =\n error instanceof Error\n ? error.message\n : errorObj && typeof errorObj.message === 'string'\n ? errorObj.message\n : String(error)\n\n const dynamoError = new EncryptedDynamoDBErrorImpl(errorMessage, errorCode, {\n context,\n })\n\n if (options?.errorHandler) {\n options.errorHandler(dynamoError)\n }\n\n if (options?.logger) {\n options.logger.error(`Error in ${context}`, dynamoError)\n }\n\n return dynamoError\n}\n\nexport function deepClone<T>(obj: T): T {\n if (obj === null || typeof obj !== 'object') {\n return obj\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => deepClone(item)) as unknown as T\n }\n\n return Object.entries(obj as Record<string, unknown>).reduce(\n (acc, [key, value]) => ({\n // biome-ignore lint/performance/noAccumulatingSpread: TODO later\n ...acc,\n [key]: deepClone(value),\n }),\n {} as T,\n )\n}\n\nexport function toEncryptedDynamoItem(\n encrypted: Record<string, unknown>,\n encryptedAttrs: string[],\n): Record<string, unknown> {\n function processValue(\n attrName: string,\n attrValue: unknown,\n isNested: boolean,\n ): Record<string, unknown> {\n if (attrValue === null || attrValue === undefined) {\n return { [attrName]: attrValue }\n }\n\n // Handle encrypted payload\n if (\n encryptedAttrs.includes(attrName) ||\n (isNested &&\n typeof attrValue === 'object' &&\n 'c' in (attrValue as object))\n ) {\n const encryptPayload = attrValue as EncryptedValue\n if (encryptPayload?.c) {\n const result: Record<string, unknown> = {}\n if (encryptPayload.hm) {\n result[`${attrName}${searchTermAttrSuffix}`] = encryptPayload.hm\n }\n result[`${attrName}${ciphertextAttrSuffix}`] = encryptPayload.c\n return result\n }\n\n if (encryptPayload?.sv) {\n const result: Record<string, unknown> = {}\n result[`${attrName}${ciphertextAttrSuffix}`] = encryptPayload.sv\n return result\n }\n }\n\n // Handle nested objects recursively\n if (typeof attrValue === 'object' && !Array.isArray(attrValue)) {\n const nestedResult = Object.entries(\n attrValue as Record<string, unknown>,\n ).reduce(\n (acc, [key, val]) => {\n const processed = processValue(key, val, true)\n return Object.assign({}, acc, processed)\n },\n {} as Record<string, unknown>,\n )\n return { [attrName]: nestedResult }\n }\n\n // Handle non-encrypted values\n return { [attrName]: attrValue }\n }\n\n return Object.entries(encrypted).reduce(\n (putItem, [attrName, attrValue]) => {\n const processed = processValue(attrName, attrValue, false)\n return Object.assign({}, putItem, processed)\n },\n {} as Record<string, unknown>,\n )\n}\n\nexport function toItemWithEqlPayloads(\n decrypted: Record<string, EncryptedValue | unknown>,\n encryptionSchema: ProtectTable<ProtectTableColumn>,\n): Record<string, unknown> {\n function processValue(\n attrName: string,\n attrValue: unknown,\n isNested: boolean,\n ): Record<string, unknown> {\n if (attrValue === null || attrValue === undefined) {\n return { [attrName]: attrValue }\n }\n\n // Skip HMAC fields\n if (attrName.endsWith(searchTermAttrSuffix)) {\n return {}\n }\n\n const encryptConfig = encryptionSchema.build()\n const encryptedAttrs = Object.keys(encryptConfig.columns)\n const columnName = attrName.slice(0, -ciphertextAttrSuffix.length)\n\n // Handle encrypted payload\n if (\n attrName.endsWith(ciphertextAttrSuffix) &&\n (encryptedAttrs.includes(columnName) || isNested)\n ) {\n const i = { c: columnName, t: encryptConfig.tableName }\n const v = 2\n\n // Nested values are not searchable, so we can just return the standard EQL payload.\n // Worth noting, that encryptConfig.columns[columnName] will be undefined if isNested is true.\n if (\n !isNested &&\n encryptConfig.columns[columnName].cast_as === 'json' &&\n encryptConfig.columns[columnName].indexes.ste_vec\n ) {\n return {\n [columnName]: {\n i,\n v,\n k: 'sv',\n sv: attrValue,\n },\n }\n }\n\n return {\n [columnName]: {\n i,\n v,\n k: 'ct',\n c: attrValue,\n },\n }\n }\n\n // Handle nested objects recursively\n if (typeof attrValue === 'object' && !Array.isArray(attrValue)) {\n const nestedResult = Object.entries(\n attrValue as Record<string, unknown>,\n ).reduce(\n (acc, [key, val]) => {\n const processed = processValue(key, val, true)\n return Object.assign({}, acc, processed)\n },\n {} as Record<string, unknown>,\n )\n return { [attrName]: nestedResult }\n }\n\n // Handle non-encrypted values\n return { [attrName]: attrValue }\n }\n\n return Object.entries(decrypted).reduce(\n (formattedItem, [attrName, attrValue]) => {\n const processed = processValue(attrName, attrValue, false)\n return Object.assign({}, formattedItem, processed)\n },\n {} as Record<string, unknown>,\n )\n}\n","import type { Result } from '@byteslice/result'\nimport type { EncryptedDynamoDBError } from '../types'\n\nexport type AuditConfig = {\n metadata?: Record<string, unknown>\n}\n\nexport type AuditData = {\n metadata?: Record<string, unknown>\n}\n\nexport type DynamoDBOperationOptions = {\n logger?: {\n error: (message: string, error: Error) => void\n }\n errorHandler?: (error: EncryptedDynamoDBError) => void\n}\n\nexport abstract class DynamoDBOperation<T> {\n protected auditMetadata?: Record<string, unknown>\n protected logger?: DynamoDBOperationOptions['logger']\n protected errorHandler?: DynamoDBOperationOptions['errorHandler']\n\n constructor(options?: DynamoDBOperationOptions) {\n this.logger = options?.logger\n this.errorHandler = options?.errorHandler\n }\n\n /**\n * Attach audit metadata to this operation. Can be chained.\n */\n audit(config: AuditConfig): this {\n this.auditMetadata = config.metadata\n return this\n }\n\n /**\n * Get the audit metadata for this operation.\n */\n protected getAuditData(): AuditData {\n return {\n metadata: this.auditMetadata,\n }\n }\n\n /**\n * Execute the operation and return a Result\n */\n abstract execute(): Promise<Result<T, EncryptedDynamoDBError>>\n\n /**\n * Make the operation thenable\n */\n public then<TResult1 = Result<T, EncryptedDynamoDBError>, TResult2 = never>(\n onfulfilled?:\n | ((\n value: Result<T, EncryptedDynamoDBError>,\n ) => TResult1 | PromiseLike<TResult1>)\n | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,\n ): Promise<TResult1 | TResult2> {\n return this.execute().then(onfulfilled, onrejected)\n }\n}\n","import type { EncryptionClient } from '@/encryption/ffi'\nimport type { ProtectTable, ProtectTableColumn } from '@/schema'\nimport { type Result, withResult } from '@byteslice/result'\nimport { deepClone, handleError, toEncryptedDynamoItem } from '../helpers'\nimport type { EncryptedDynamoDBError } from '../types'\nimport {\n DynamoDBOperation,\n type DynamoDBOperationOptions,\n} from './base-operation'\n\nexport class BulkEncryptModelsOperation<\n T extends Record<string, unknown>,\n> extends DynamoDBOperation<T[]> {\n private encryptionClient: EncryptionClient\n private items: T[]\n private table: ProtectTable<ProtectTableColumn>\n\n constructor(\n encryptionClient: EncryptionClient,\n items: T[],\n table: ProtectTable<ProtectTableColumn>,\n options?: DynamoDBOperationOptions,\n ) {\n super(options)\n this.encryptionClient = encryptionClient\n this.items = items\n this.table = table\n }\n\n public async execute(): Promise<Result<T[], EncryptedDynamoDBError>> {\n return await withResult(\n async () => {\n const encryptResult = await this.encryptionClient\n .bulkEncryptModels(\n this.items.map((item) => deepClone(item)),\n this.table,\n )\n .audit(this.getAuditData())\n\n if (encryptResult.failure) {\n // Create an Error object that preserves the FFI error code\n // This is necessary because withResult's ensureError wraps non-Error objects\n const error = new Error(encryptResult.failure.message) as Error & {\n code?: string\n }\n error.code = encryptResult.failure.code\n throw error\n }\n\n const data = encryptResult.data.map((item) => deepClone(item))\n const encryptedAttrs = Object.keys(this.table.build().columns)\n\n return data.map(\n (encrypted) => toEncryptedDynamoItem(encrypted, encryptedAttrs) as T,\n )\n },\n (error) =>\n handleError(error, 'bulkEncryptModels', {\n logger: this.logger,\n errorHandler: this.errorHandler,\n }),\n )\n }\n}\n","import type { EncryptionClient } from '@/encryption/ffi'\nimport type { ProtectTable, ProtectTableColumn } from '@/schema'\nimport type { Decrypted, EncryptedValue } from '@/types'\nimport { type Result, withResult } from '@byteslice/result'\nimport { handleError, toItemWithEqlPayloads } from '../helpers'\nimport type { EncryptedDynamoDBError } from '../types'\nimport {\n DynamoDBOperation,\n type DynamoDBOperationOptions,\n} from './base-operation'\n\nexport class DecryptModelOperation<\n T extends Record<string, unknown>,\n> extends DynamoDBOperation<Decrypted<T>> {\n private encryptionClient: EncryptionClient\n private item: Record<string, EncryptedValue | unknown>\n private table: ProtectTable<ProtectTableColumn>\n\n constructor(\n encryptionClient: EncryptionClient,\n item: Record<string, EncryptedValue | unknown>,\n table: ProtectTable<ProtectTableColumn>,\n options?: DynamoDBOperationOptions,\n ) {\n super(options)\n this.encryptionClient = encryptionClient\n this.item = item\n this.table = table\n }\n\n public async execute(): Promise<\n Result<Decrypted<T>, EncryptedDynamoDBError>\n > {\n return await withResult(\n async () => {\n const withEqlPayloads = toItemWithEqlPayloads(this.item, this.table)\n\n const decryptResult = await this.encryptionClient\n .decryptModel<T>(withEqlPayloads as T)\n .audit(this.getAuditData())\n\n if (decryptResult.failure) {\n // Create an Error object that preserves the FFI error code\n // This is necessary because withResult's ensureError wraps non-Error objects\n const error = new Error(decryptResult.failure.message) as Error & {\n code?: string\n }\n error.code = decryptResult.failure.code\n throw error\n }\n\n return decryptResult.data\n },\n (error) =>\n handleError(error, 'decryptModel', {\n logger: this.logger,\n errorHandler: this.errorHandler,\n }),\n )\n }\n}\n","import type { EncryptionClient } from '@/encryption/ffi'\nimport type { ProtectTable, ProtectTableColumn } from '@/schema'\nimport { type Result, withResult } from '@byteslice/result'\nimport { deepClone, handleError, toEncryptedDynamoItem } from '../helpers'\nimport type { EncryptedDynamoDBError } from '../types'\nimport {\n DynamoDBOperation,\n type DynamoDBOperationOptions,\n} from './base-operation'\n\nexport class EncryptModelOperation<\n T extends Record<string, unknown>,\n> extends DynamoDBOperation<T> {\n private encryptionClient: EncryptionClient\n private item: T\n private table: ProtectTable<ProtectTableColumn>\n\n constructor(\n encryptionClient: EncryptionClient,\n item: T,\n table: ProtectTable<ProtectTableColumn>,\n options?: DynamoDBOperationOptions,\n ) {\n super(options)\n this.encryptionClient = encryptionClient\n this.item = item\n this.table = table\n }\n\n public async execute(): Promise<Result<T, EncryptedDynamoDBError>> {\n return await withResult(\n async () => {\n const encryptResult = await this.encryptionClient\n .encryptModel(deepClone(this.item), this.table)\n .audit(this.getAuditData())\n\n if (encryptResult.failure) {\n // Create an Error object that preserves the FFI error code\n // This is necessary because withResult's ensureError wraps non-Error objects\n const error = new Error(encryptResult.failure.message) as Error & {\n code?: string\n }\n error.code = encryptResult.failure.code\n throw error\n }\n\n const data = deepClone(encryptResult.data)\n const encryptedAttrs = Object.keys(this.table.build().columns)\n\n return toEncryptedDynamoItem(data, encryptedAttrs) as T\n },\n (error) =>\n handleError(error, 'encryptModel', {\n logger: this.logger,\n errorHandler: this.errorHandler,\n }),\n )\n }\n}\n","import type { ProtectTable, ProtectTableColumn } from '@/schema'\nimport type { EncryptedValue } from '@/types'\nimport { BulkDecryptModelsOperation } from './operations/bulk-decrypt-models'\nimport { BulkEncryptModelsOperation } from './operations/bulk-encrypt-models'\nimport { DecryptModelOperation } from './operations/decrypt-model'\nimport { EncryptModelOperation } from './operations/encrypt-model'\nimport type {\n EncryptedDynamoDBConfig,\n EncryptedDynamoDBInstance,\n} from './types'\n\n/**\n * Create an encrypted DynamoDB helper bound to an `EncryptionClient`.\n *\n * Returns an object with `encryptModel`, `decryptModel`, `bulkEncryptModels`,\n * and `bulkDecryptModels` methods that transparently encrypt/decrypt DynamoDB\n * items according to the provided table schema.\n *\n * @param config - Configuration containing the `encryptionClient` and optional\n * logging / error-handling callbacks.\n * @returns An {@link EncryptedDynamoDBInstance} with encrypt/decrypt operations.\n *\n * @example\n * ```typescript\n * import { Encryption } from \"@cipherstash/stack\"\n * import { encryptedDynamoDB } from \"@cipherstash/stack/dynamodb\"\n * import { encryptedTable, encryptedColumn } from \"@cipherstash/stack/schema\"\n *\n * const users = encryptedTable(\"users\", {\n * email: encryptedColumn(\"email\").equality(),\n * })\n *\n * const client = await Encryption({ schemas: [users] })\n * const dynamo = encryptedDynamoDB({ encryptionClient: client })\n *\n * const encrypted = await dynamo.encryptModel({ email: \"a@b.com\" }, users)\n * ```\n */\nexport function encryptedDynamoDB(\n config: EncryptedDynamoDBConfig,\n): EncryptedDynamoDBInstance {\n const { encryptionClient, options } = config\n\n return {\n encryptModel<T extends Record<string, unknown>>(\n item: T,\n table: ProtectTable<ProtectTableColumn>,\n ) {\n return new EncryptModelOperation<T>(\n encryptionClient,\n item,\n table,\n options,\n )\n },\n\n bulkEncryptModels<T extends Record<string, unknown>>(\n items: T[],\n table: ProtectTable<ProtectTableColumn>,\n ) {\n return new BulkEncryptModelsOperation<T>(\n encryptionClient,\n items,\n table,\n options,\n )\n },\n\n decryptModel<T extends Record<string, unknown>>(\n item: Record<string, EncryptedValue | unknown>,\n table: ProtectTable<ProtectTableColumn>,\n ) {\n return new DecryptModelOperation<T>(\n encryptionClient,\n item,\n table,\n options,\n )\n },\n\n bulkDecryptModels<T extends Record<string, unknown>>(\n items: Record<string, EncryptedValue | unknown>[],\n table: ProtectTable<ProtectTableColumn>,\n ) {\n return new BulkDecryptModelsOperation<T>(\n encryptionClient,\n items,\n table,\n options,\n )\n },\n }\n}\n\nexport type {\n EncryptedDynamoDBConfig,\n EncryptedDynamoDBError,\n EncryptedDynamoDBInstance,\n} from './types'\n"],"mappings":"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","names":["withResult","withResult","withResult","withResult","withResult","withResult"]}
@@ -0,0 +1,271 @@
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/identity/index.ts
31
+ var identity_exports = {};
32
+ __export(identity_exports, {
33
+ LockContext: () => LockContext
34
+ });
35
+ module.exports = __toCommonJS(identity_exports);
36
+
37
+ // src/errors/index.ts
38
+ var EncryptionErrorTypes = {
39
+ ClientInitError: "ClientInitError",
40
+ EncryptionError: "EncryptionError",
41
+ DecryptionError: "DecryptionError",
42
+ LockContextError: "LockContextError",
43
+ CtsTokenError: "CtsTokenError"
44
+ };
45
+
46
+ // src/utils/config/index.ts
47
+ var import_node_fs = __toESM(require("fs"), 1);
48
+ var import_node_path = __toESM(require("path"), 1);
49
+ function getWorkspaceCrn(tomlString) {
50
+ let currentSection = "";
51
+ let workspaceCrn;
52
+ const lines = tomlString.split(/\r?\n/);
53
+ for (const line of lines) {
54
+ const trimmedLine = line.trim();
55
+ if (!trimmedLine || trimmedLine.startsWith("#")) {
56
+ continue;
57
+ }
58
+ const sectionMatch = trimmedLine.match(/^\[([^\]]+)\]$/);
59
+ if (sectionMatch) {
60
+ currentSection = sectionMatch[1];
61
+ continue;
62
+ }
63
+ const kvMatch = trimmedLine.match(/^(\w+)\s*=\s*"([^"]+)"$/);
64
+ if (kvMatch) {
65
+ const [_, key, value] = kvMatch;
66
+ if (currentSection === "auth" && key === "workspace_crn") {
67
+ workspaceCrn = value;
68
+ break;
69
+ }
70
+ }
71
+ }
72
+ return workspaceCrn;
73
+ }
74
+ function extractWorkspaceIdFromCrn(crn) {
75
+ const match = crn.match(/crn:[^:]+:([^:]+)$/);
76
+ if (!match) {
77
+ throw new Error("Invalid CRN format");
78
+ }
79
+ return match[1];
80
+ }
81
+ function loadWorkSpaceId(suppliedCrn) {
82
+ const configPath = import_node_path.default.join(process.cwd(), "cipherstash.toml");
83
+ if (suppliedCrn) {
84
+ return extractWorkspaceIdFromCrn(suppliedCrn);
85
+ }
86
+ if (!import_node_fs.default.existsSync(configPath) && !process.env.CS_WORKSPACE_CRN) {
87
+ throw new Error(
88
+ "You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable."
89
+ );
90
+ }
91
+ if (process.env.CS_WORKSPACE_CRN) {
92
+ return extractWorkspaceIdFromCrn(process.env.CS_WORKSPACE_CRN);
93
+ }
94
+ if (!import_node_fs.default.existsSync(configPath)) {
95
+ throw new Error(
96
+ "You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable."
97
+ );
98
+ }
99
+ const tomlString = import_node_fs.default.readFileSync(configPath, "utf8");
100
+ const workspaceCrn = getWorkspaceCrn(tomlString);
101
+ if (!workspaceCrn) {
102
+ throw new Error(
103
+ "You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable."
104
+ );
105
+ }
106
+ return extractWorkspaceIdFromCrn(workspaceCrn);
107
+ }
108
+
109
+ // src/utils/logger/index.ts
110
+ var import_evlog = require("evlog");
111
+ function samplingFromEnv() {
112
+ const env = process.env.STASH_LOG_LEVEL;
113
+ if (!env) return void 0;
114
+ const levels = ["debug", "info", "warn", "error"];
115
+ const idx = levels.indexOf(env);
116
+ if (idx === -1) return void 0;
117
+ return Object.fromEntries(levels.map((l, i) => [l, i >= idx ? 100 : 0]));
118
+ }
119
+ var initialized = false;
120
+ function initStackLogger(config) {
121
+ if (initialized) return;
122
+ initialized = true;
123
+ const rates = samplingFromEnv();
124
+ (0, import_evlog.initLogger)({
125
+ env: { service: "@cipherstash/stack" },
126
+ enabled: config?.enabled ?? true,
127
+ pretty: config?.pretty,
128
+ ...rates && { sampling: { rates } },
129
+ ...config?.drain && { drain: config.drain }
130
+ });
131
+ }
132
+ initStackLogger();
133
+ function safeMessage(args) {
134
+ return typeof args[0] === "string" ? args[0] : "";
135
+ }
136
+ var logger = {
137
+ debug(...args) {
138
+ const log = (0, import_evlog.createRequestLogger)();
139
+ log.set({ level: "debug", source: "@cipherstash/stack", message: safeMessage(args) });
140
+ log.emit();
141
+ },
142
+ info(...args) {
143
+ const log = (0, import_evlog.createRequestLogger)();
144
+ log.set({ source: "@cipherstash/stack" });
145
+ log.info(safeMessage(args));
146
+ log.emit();
147
+ },
148
+ warn(...args) {
149
+ const log = (0, import_evlog.createRequestLogger)();
150
+ log.warn(safeMessage(args));
151
+ log.emit();
152
+ },
153
+ error(...args) {
154
+ const log = (0, import_evlog.createRequestLogger)();
155
+ log.error(safeMessage(args));
156
+ log.emit();
157
+ }
158
+ };
159
+
160
+ // src/identity/index.ts
161
+ var import_result = require("@byteslice/result");
162
+ var LockContext = class {
163
+ ctsToken;
164
+ workspaceId;
165
+ context;
166
+ constructor({
167
+ context = { identityClaim: ["sub"] },
168
+ ctsToken
169
+ } = {}) {
170
+ const workspaceId = loadWorkSpaceId();
171
+ if (!workspaceId) {
172
+ throw new Error(
173
+ "You have not defined a workspace ID in your config file, or the CS_WORKSPACE_CRN environment variable."
174
+ );
175
+ }
176
+ if (ctsToken) {
177
+ this.ctsToken = ctsToken;
178
+ }
179
+ this.workspaceId = workspaceId;
180
+ this.context = context;
181
+ logger.debug("Successfully initialized the EQL lock context.");
182
+ }
183
+ /**
184
+ * Exchange a user's JWT for a CTS token and bind it to this lock context.
185
+ *
186
+ * @param jwtToken - A valid OIDC / JWT token for the current user.
187
+ * @returns A `Result` containing this `LockContext` (now authenticated) or an error.
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * const lc = new LockContext()
192
+ * const result = await lc.identify(userJwt)
193
+ * if (result.failure) {
194
+ * console.error("Auth failed:", result.failure.message)
195
+ * }
196
+ * ```
197
+ */
198
+ async identify(jwtToken) {
199
+ const workspaceId = this.workspaceId;
200
+ const ctsEndpoint = process.env.CS_CTS_ENDPOINT || "https://ap-southeast-2.aws.auth.viturhosted.net";
201
+ const ctsFetchResult = await (0, import_result.withResult)(
202
+ () => fetch(`${ctsEndpoint}/api/authorize`, {
203
+ method: "POST",
204
+ headers: {
205
+ "Content-Type": "application/json"
206
+ },
207
+ body: JSON.stringify({
208
+ workspaceId,
209
+ oidcToken: jwtToken
210
+ })
211
+ }),
212
+ (error) => ({
213
+ type: EncryptionErrorTypes.CtsTokenError,
214
+ message: error.message
215
+ })
216
+ );
217
+ if (ctsFetchResult.failure) {
218
+ return ctsFetchResult;
219
+ }
220
+ const identifiedLockContext = await (0, import_result.withResult)(
221
+ async () => {
222
+ const ctsToken = await ctsFetchResult.data.json();
223
+ if (!ctsToken.accessToken) {
224
+ throw new Error(
225
+ "The response from the CipherStash API did not contain an access token. Please contact support."
226
+ );
227
+ }
228
+ this.ctsToken = ctsToken;
229
+ return this;
230
+ },
231
+ (error) => ({
232
+ type: EncryptionErrorTypes.CtsTokenError,
233
+ message: error.message
234
+ })
235
+ );
236
+ return identifiedLockContext;
237
+ }
238
+ /**
239
+ * Retrieve the current CTS token and context for use with encryption operations.
240
+ *
241
+ * Must be called after {@link identify}. Returns the token/context pair that
242
+ * `.withLockContext()` expects.
243
+ *
244
+ * @returns A `Result` containing the CTS token and identity context, or an error
245
+ * if {@link identify} has not been called.
246
+ */
247
+ getLockContext() {
248
+ return (0, import_result.withResult)(
249
+ () => {
250
+ if (!this.ctsToken?.accessToken || !this.ctsToken?.expiry) {
251
+ throw new Error(
252
+ "The CTS token is not set. Please call identify() with a users JWT token, or pass an existing CTS token to the LockContext constructor before calling getLockContext()."
253
+ );
254
+ }
255
+ return {
256
+ context: this.context,
257
+ ctsToken: this.ctsToken
258
+ };
259
+ },
260
+ (error) => ({
261
+ type: EncryptionErrorTypes.CtsTokenError,
262
+ message: error.message
263
+ })
264
+ );
265
+ }
266
+ };
267
+ // Annotate the CommonJS export names for ESM import in node:
268
+ 0 && (module.exports = {
269
+ LockContext
270
+ });
271
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/identity/index.ts","../../src/errors/index.ts","../../src/utils/config/index.ts","../../src/utils/logger/index.ts"],"sourcesContent":["import { type EncryptionError, EncryptionErrorTypes } from '@/errors'\nimport { loadWorkSpaceId } from '@/utils/config'\nimport { logger } from '@/utils/logger'\nimport { type Result, withResult } from '@byteslice/result'\n\nexport type CtsRegions = 'ap-southeast-2'\n\nexport type IdentifyOptions = {\n fetchFromCts?: boolean\n}\n\nexport type CtsToken = {\n accessToken: string\n expiry: number\n}\n\nexport type Context = {\n identityClaim: string[]\n}\n\nexport type LockContextOptions = {\n context?: Context\n ctsToken?: CtsToken\n}\n\nexport type GetLockContextResponse = {\n ctsToken: CtsToken\n context: Context\n}\n\n/**\n * Manages CipherStash lock contexts for row-level access control.\n *\n * A `LockContext` ties encryption/decryption operations to an authenticated\n * user identity via CTS (CipherStash Token Service). Call {@link identify}\n * with a user's JWT to obtain a CTS token, then pass the `LockContext`\n * to `.withLockContext()` on any encrypt/decrypt operation.\n *\n * @example\n * ```typescript\n * import { LockContext } from \"@cipherstash/stack/identity\"\n *\n * const lc = new LockContext()\n * const identified = await lc.identify(userJwt)\n *\n * if (identified.failure) throw new Error(identified.failure.message)\n *\n * const result = await client\n * .encrypt(value, { column: users.email, table: users })\n * .withLockContext(identified.data)\n * ```\n */\nexport class LockContext {\n private ctsToken: CtsToken | undefined\n private workspaceId: string\n private context: Context\n\n constructor({\n context = { identityClaim: ['sub'] },\n ctsToken,\n }: LockContextOptions = {}) {\n const workspaceId = loadWorkSpaceId()\n\n if (!workspaceId) {\n throw new Error(\n 'You have not defined a workspace ID in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n if (ctsToken) {\n this.ctsToken = ctsToken\n }\n\n this.workspaceId = workspaceId\n this.context = context\n logger.debug('Successfully initialized the EQL lock context.')\n }\n\n /**\n * Exchange a user's JWT for a CTS token and bind it to this lock context.\n *\n * @param jwtToken - A valid OIDC / JWT token for the current user.\n * @returns A `Result` containing this `LockContext` (now authenticated) or an error.\n *\n * @example\n * ```typescript\n * const lc = new LockContext()\n * const result = await lc.identify(userJwt)\n * if (result.failure) {\n * console.error(\"Auth failed:\", result.failure.message)\n * }\n * ```\n */\n async identify(\n jwtToken: string,\n ): Promise<Result<LockContext, EncryptionError>> {\n const workspaceId = this.workspaceId\n\n const ctsEndpoint =\n process.env.CS_CTS_ENDPOINT ||\n 'https://ap-southeast-2.aws.auth.viturhosted.net'\n\n const ctsFetchResult = await withResult(\n () =>\n fetch(`${ctsEndpoint}/api/authorize`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n workspaceId,\n oidcToken: jwtToken,\n }),\n }),\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n\n if (ctsFetchResult.failure) {\n return ctsFetchResult\n }\n\n const identifiedLockContext = await withResult(\n async () => {\n const ctsToken = (await ctsFetchResult.data.json()) as CtsToken\n\n if (!ctsToken.accessToken) {\n throw new Error(\n 'The response from the CipherStash API did not contain an access token. Please contact support.',\n )\n }\n\n this.ctsToken = ctsToken\n return this\n },\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n\n return identifiedLockContext\n }\n\n /**\n * Retrieve the current CTS token and context for use with encryption operations.\n *\n * Must be called after {@link identify}. Returns the token/context pair that\n * `.withLockContext()` expects.\n *\n * @returns A `Result` containing the CTS token and identity context, or an error\n * if {@link identify} has not been called.\n */\n getLockContext(): Promise<Result<GetLockContextResponse, EncryptionError>> {\n return withResult(\n () => {\n if (!this.ctsToken?.accessToken || !this.ctsToken?.expiry) {\n throw new Error(\n 'The CTS token is not set. Please call identify() with a users JWT token, or pass an existing CTS token to the LockContext constructor before calling getLockContext().',\n )\n }\n\n return {\n context: this.context,\n ctsToken: this.ctsToken,\n }\n },\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n }\n}\n","import type { ProtectErrorCode } from '@cipherstash/protect-ffi'\n\nexport const EncryptionErrorTypes = {\n ClientInitError: 'ClientInitError',\n EncryptionError: 'EncryptionError',\n DecryptionError: 'DecryptionError',\n LockContextError: 'LockContextError',\n CtsTokenError: 'CtsTokenError',\n}\n\n/**\n * Base error interface returned by all encryption operations.\n *\n * Every operation that can fail returns `Result<T, EncryptionError>`.\n * Use the `type` field to narrow to a specific error kind, or use\n * {@link StackError} for an exhaustive discriminated union.\n *\n * @example\n * ```typescript\n * const result = await client.encrypt(value, opts)\n * if (result.failure) {\n * switch (result.failure.type) {\n * case 'EncryptionError':\n * console.error('Encryption failed:', result.failure.message)\n * break\n * case 'LockContextError':\n * console.error('Lock context issue:', result.failure.message)\n * break\n * }\n * }\n * ```\n */\nexport interface EncryptionError {\n type: (typeof EncryptionErrorTypes)[keyof typeof EncryptionErrorTypes]\n message: string\n code?: ProtectErrorCode\n}\n\n// ---------------------------------------------------------------------------\n// Specific error types (discriminated union members)\n// ---------------------------------------------------------------------------\n\nexport interface ClientInitError {\n type: typeof EncryptionErrorTypes.ClientInitError\n message: string\n}\n\nexport interface EncryptionOperationError {\n type: typeof EncryptionErrorTypes.EncryptionError\n message: string\n code?: ProtectErrorCode\n}\n\nexport interface DecryptionOperationError {\n type: typeof EncryptionErrorTypes.DecryptionError\n message: string\n code?: ProtectErrorCode\n}\n\nexport interface LockContextError {\n type: typeof EncryptionErrorTypes.LockContextError\n message: string\n}\n\nexport interface CtsTokenError {\n type: typeof EncryptionErrorTypes.CtsTokenError\n message: string\n}\n\n/**\n * Discriminated union of all specific error types.\n *\n * Use `StackError` when you need exhaustive error handling via `switch` on the `type` field.\n *\n * @example\n * ```typescript\n * function handleError(error: StackError) {\n * switch (error.type) {\n * case 'ClientInitError':\n * // re-initialize client\n * break\n * case 'EncryptionError':\n * case 'DecryptionError':\n * // log and retry\n * break\n * case 'LockContextError':\n * // re-authenticate\n * break\n * case 'CtsTokenError':\n * // refresh token\n * break\n * default:\n * error satisfies never\n * }\n * }\n * ```\n */\nexport type StackError =\n | ClientInitError\n | EncryptionOperationError\n | DecryptionOperationError\n | LockContextError\n | CtsTokenError\n\n// ---------------------------------------------------------------------------\n// Error utilities\n// ---------------------------------------------------------------------------\n\n/**\n * Safely extract an error message from an unknown thrown value.\n * Unlike `(error as Error).message`, this handles non-Error values gracefully.\n */\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message\n if (typeof error === 'string') return error\n return String(error)\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\n/**\n * A lightweight function that parses a TOML-like string\n * and returns the `workspace_crn` value found under `[auth]`.\n *\n * @param tomlString The contents of the TOML file as a string.\n * @returns The workspace_crn if found, otherwise undefined.\n */\nfunction getWorkspaceCrn(tomlString: string): string | undefined {\n let currentSection = ''\n let workspaceCrn: string | undefined\n\n const lines = tomlString.split(/\\r?\\n/)\n\n for (const line of lines) {\n const trimmedLine = line.trim()\n\n if (!trimmedLine || trimmedLine.startsWith('#')) {\n continue\n }\n\n const sectionMatch = trimmedLine.match(/^\\[([^\\]]+)\\]$/)\n if (sectionMatch) {\n currentSection = sectionMatch[1]\n continue\n }\n\n const kvMatch = trimmedLine.match(/^(\\w+)\\s*=\\s*\"([^\"]+)\"$/)\n if (kvMatch) {\n const [_, key, value] = kvMatch\n\n if (currentSection === 'auth' && key === 'workspace_crn') {\n workspaceCrn = value\n break\n }\n }\n }\n\n return workspaceCrn\n}\n\n/**\n * Extracts the workspace ID from a CRN string.\n * CRN format: crn:region.aws:ID\n *\n * @param crn The CRN string to extract from\n * @returns The workspace ID portion of the CRN\n */\nexport function extractWorkspaceIdFromCrn(crn: string): string {\n const match = crn.match(/crn:[^:]+:([^:]+)$/)\n if (!match) {\n throw new Error('Invalid CRN format')\n }\n return match[1]\n}\n\nexport function loadWorkSpaceId(suppliedCrn?: string): string {\n const configPath = path.join(process.cwd(), 'cipherstash.toml')\n\n if (suppliedCrn) {\n return extractWorkspaceIdFromCrn(suppliedCrn)\n }\n\n if (!fs.existsSync(configPath) && !process.env.CS_WORKSPACE_CRN) {\n throw new Error(\n 'You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n // Environment variables take precedence over config files\n if (process.env.CS_WORKSPACE_CRN) {\n return extractWorkspaceIdFromCrn(process.env.CS_WORKSPACE_CRN)\n }\n\n if (!fs.existsSync(configPath)) {\n throw new Error(\n 'You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n const tomlString = fs.readFileSync(configPath, 'utf8')\n const workspaceCrn = getWorkspaceCrn(tomlString)\n\n if (!workspaceCrn) {\n throw new Error(\n 'You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n return extractWorkspaceIdFromCrn(workspaceCrn)\n}\n","import { initLogger, createRequestLogger } from 'evlog'\nimport type { LoggerConfig } from 'evlog'\n\nexport type LoggingConfig = {\n enabled?: boolean\n pretty?: boolean\n drain?: LoggerConfig['drain']\n}\n\nfunction samplingFromEnv() {\n const env = process.env.STASH_LOG_LEVEL\n if (!env) return undefined\n const levels = ['debug', 'info', 'warn', 'error'] as const\n const idx = levels.indexOf(env as (typeof levels)[number])\n if (idx === -1) return undefined\n return Object.fromEntries(levels.map((l, i) => [l, i >= idx ? 100 : 0]))\n}\n\nlet initialized = false\n\nexport function initStackLogger(config?: LoggingConfig): void {\n if (initialized) return\n initialized = true\n const rates = samplingFromEnv()\n initLogger({\n env: { service: '@cipherstash/stack' },\n enabled: config?.enabled ?? true,\n pretty: config?.pretty,\n ...(rates && { sampling: { rates } }),\n ...(config?.drain && { drain: config.drain }),\n })\n}\n\n// Auto-init with defaults on first import\ninitStackLogger()\n\nexport { createRequestLogger }\n\n// Stringify only the first arg (the message string); drop subsequent args\n// which may contain sensitive objects (e.g. encryptConfig, plaintext).\nfunction safeMessage(args: unknown[]): string {\n return typeof args[0] === 'string' ? args[0] : ''\n}\n\n// Legacy logger for simple one-off logs (used by encryption/ffi/index.ts + identity/index.ts)\nexport const logger = {\n debug(...args: unknown[]) {\n const log = createRequestLogger()\n log.set({ level: 'debug', source: '@cipherstash/stack', message: safeMessage(args) })\n log.emit()\n },\n info(...args: unknown[]) {\n const log = createRequestLogger()\n log.set({ source: '@cipherstash/stack' })\n log.info(safeMessage(args))\n log.emit()\n },\n warn(...args: unknown[]) {\n const log = createRequestLogger()\n log.warn(safeMessage(args))\n log.emit()\n },\n error(...args: unknown[]) {\n const log = createRequestLogger()\n log.error(safeMessage(args))\n log.emit()\n },\n}\n"],"mappings":"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","names":["path","fs"]}
@@ -0,0 +1,3 @@
1
+ export { h as Context, e as CtsRegions, f as CtsToken, G as GetLockContextResponse, I as IdentifyOptions, d as LockContext, i as LockContextOptions } from '../index-9-Ya3fDK.cjs';
2
+ import '@byteslice/result';
3
+ import '@cipherstash/protect-ffi';
@@ -0,0 +1,3 @@
1
+ export { h as Context, e as CtsRegions, f as CtsToken, G as GetLockContextResponse, I as IdentifyOptions, d as LockContext, i as LockContextOptions } from '../index-9-Ya3fDK.js';
2
+ import '@byteslice/result';
3
+ import '@cipherstash/protect-ffi';
@@ -0,0 +1,117 @@
1
+ import {
2
+ EncryptionErrorTypes,
3
+ loadWorkSpaceId,
4
+ logger
5
+ } from "../chunk-5DCT6YU2.js";
6
+
7
+ // src/identity/index.ts
8
+ import { withResult } from "@byteslice/result";
9
+ var LockContext = class {
10
+ ctsToken;
11
+ workspaceId;
12
+ context;
13
+ constructor({
14
+ context = { identityClaim: ["sub"] },
15
+ ctsToken
16
+ } = {}) {
17
+ const workspaceId = loadWorkSpaceId();
18
+ if (!workspaceId) {
19
+ throw new Error(
20
+ "You have not defined a workspace ID in your config file, or the CS_WORKSPACE_CRN environment variable."
21
+ );
22
+ }
23
+ if (ctsToken) {
24
+ this.ctsToken = ctsToken;
25
+ }
26
+ this.workspaceId = workspaceId;
27
+ this.context = context;
28
+ logger.debug("Successfully initialized the EQL lock context.");
29
+ }
30
+ /**
31
+ * Exchange a user's JWT for a CTS token and bind it to this lock context.
32
+ *
33
+ * @param jwtToken - A valid OIDC / JWT token for the current user.
34
+ * @returns A `Result` containing this `LockContext` (now authenticated) or an error.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const lc = new LockContext()
39
+ * const result = await lc.identify(userJwt)
40
+ * if (result.failure) {
41
+ * console.error("Auth failed:", result.failure.message)
42
+ * }
43
+ * ```
44
+ */
45
+ async identify(jwtToken) {
46
+ const workspaceId = this.workspaceId;
47
+ const ctsEndpoint = process.env.CS_CTS_ENDPOINT || "https://ap-southeast-2.aws.auth.viturhosted.net";
48
+ const ctsFetchResult = await withResult(
49
+ () => fetch(`${ctsEndpoint}/api/authorize`, {
50
+ method: "POST",
51
+ headers: {
52
+ "Content-Type": "application/json"
53
+ },
54
+ body: JSON.stringify({
55
+ workspaceId,
56
+ oidcToken: jwtToken
57
+ })
58
+ }),
59
+ (error) => ({
60
+ type: EncryptionErrorTypes.CtsTokenError,
61
+ message: error.message
62
+ })
63
+ );
64
+ if (ctsFetchResult.failure) {
65
+ return ctsFetchResult;
66
+ }
67
+ const identifiedLockContext = await withResult(
68
+ async () => {
69
+ const ctsToken = await ctsFetchResult.data.json();
70
+ if (!ctsToken.accessToken) {
71
+ throw new Error(
72
+ "The response from the CipherStash API did not contain an access token. Please contact support."
73
+ );
74
+ }
75
+ this.ctsToken = ctsToken;
76
+ return this;
77
+ },
78
+ (error) => ({
79
+ type: EncryptionErrorTypes.CtsTokenError,
80
+ message: error.message
81
+ })
82
+ );
83
+ return identifiedLockContext;
84
+ }
85
+ /**
86
+ * Retrieve the current CTS token and context for use with encryption operations.
87
+ *
88
+ * Must be called after {@link identify}. Returns the token/context pair that
89
+ * `.withLockContext()` expects.
90
+ *
91
+ * @returns A `Result` containing the CTS token and identity context, or an error
92
+ * if {@link identify} has not been called.
93
+ */
94
+ getLockContext() {
95
+ return withResult(
96
+ () => {
97
+ if (!this.ctsToken?.accessToken || !this.ctsToken?.expiry) {
98
+ throw new Error(
99
+ "The CTS token is not set. Please call identify() with a users JWT token, or pass an existing CTS token to the LockContext constructor before calling getLockContext()."
100
+ );
101
+ }
102
+ return {
103
+ context: this.context,
104
+ ctsToken: this.ctsToken
105
+ };
106
+ },
107
+ (error) => ({
108
+ type: EncryptionErrorTypes.CtsTokenError,
109
+ message: error.message
110
+ })
111
+ );
112
+ }
113
+ };
114
+ export {
115
+ LockContext
116
+ };
117
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/identity/index.ts"],"sourcesContent":["import { type EncryptionError, EncryptionErrorTypes } from '@/errors'\nimport { loadWorkSpaceId } from '@/utils/config'\nimport { logger } from '@/utils/logger'\nimport { type Result, withResult } from '@byteslice/result'\n\nexport type CtsRegions = 'ap-southeast-2'\n\nexport type IdentifyOptions = {\n fetchFromCts?: boolean\n}\n\nexport type CtsToken = {\n accessToken: string\n expiry: number\n}\n\nexport type Context = {\n identityClaim: string[]\n}\n\nexport type LockContextOptions = {\n context?: Context\n ctsToken?: CtsToken\n}\n\nexport type GetLockContextResponse = {\n ctsToken: CtsToken\n context: Context\n}\n\n/**\n * Manages CipherStash lock contexts for row-level access control.\n *\n * A `LockContext` ties encryption/decryption operations to an authenticated\n * user identity via CTS (CipherStash Token Service). Call {@link identify}\n * with a user's JWT to obtain a CTS token, then pass the `LockContext`\n * to `.withLockContext()` on any encrypt/decrypt operation.\n *\n * @example\n * ```typescript\n * import { LockContext } from \"@cipherstash/stack/identity\"\n *\n * const lc = new LockContext()\n * const identified = await lc.identify(userJwt)\n *\n * if (identified.failure) throw new Error(identified.failure.message)\n *\n * const result = await client\n * .encrypt(value, { column: users.email, table: users })\n * .withLockContext(identified.data)\n * ```\n */\nexport class LockContext {\n private ctsToken: CtsToken | undefined\n private workspaceId: string\n private context: Context\n\n constructor({\n context = { identityClaim: ['sub'] },\n ctsToken,\n }: LockContextOptions = {}) {\n const workspaceId = loadWorkSpaceId()\n\n if (!workspaceId) {\n throw new Error(\n 'You have not defined a workspace ID in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n if (ctsToken) {\n this.ctsToken = ctsToken\n }\n\n this.workspaceId = workspaceId\n this.context = context\n logger.debug('Successfully initialized the EQL lock context.')\n }\n\n /**\n * Exchange a user's JWT for a CTS token and bind it to this lock context.\n *\n * @param jwtToken - A valid OIDC / JWT token for the current user.\n * @returns A `Result` containing this `LockContext` (now authenticated) or an error.\n *\n * @example\n * ```typescript\n * const lc = new LockContext()\n * const result = await lc.identify(userJwt)\n * if (result.failure) {\n * console.error(\"Auth failed:\", result.failure.message)\n * }\n * ```\n */\n async identify(\n jwtToken: string,\n ): Promise<Result<LockContext, EncryptionError>> {\n const workspaceId = this.workspaceId\n\n const ctsEndpoint =\n process.env.CS_CTS_ENDPOINT ||\n 'https://ap-southeast-2.aws.auth.viturhosted.net'\n\n const ctsFetchResult = await withResult(\n () =>\n fetch(`${ctsEndpoint}/api/authorize`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n workspaceId,\n oidcToken: jwtToken,\n }),\n }),\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n\n if (ctsFetchResult.failure) {\n return ctsFetchResult\n }\n\n const identifiedLockContext = await withResult(\n async () => {\n const ctsToken = (await ctsFetchResult.data.json()) as CtsToken\n\n if (!ctsToken.accessToken) {\n throw new Error(\n 'The response from the CipherStash API did not contain an access token. Please contact support.',\n )\n }\n\n this.ctsToken = ctsToken\n return this\n },\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n\n return identifiedLockContext\n }\n\n /**\n * Retrieve the current CTS token and context for use with encryption operations.\n *\n * Must be called after {@link identify}. Returns the token/context pair that\n * `.withLockContext()` expects.\n *\n * @returns A `Result` containing the CTS token and identity context, or an error\n * if {@link identify} has not been called.\n */\n getLockContext(): Promise<Result<GetLockContextResponse, EncryptionError>> {\n return withResult(\n () => {\n if (!this.ctsToken?.accessToken || !this.ctsToken?.expiry) {\n throw new Error(\n 'The CTS token is not set. Please call identify() with a users JWT token, or pass an existing CTS token to the LockContext constructor before calling getLockContext().',\n )\n }\n\n return {\n context: this.context,\n ctsToken: this.ctsToken,\n }\n },\n (error) => ({\n type: EncryptionErrorTypes.CtsTokenError,\n message: error.message,\n }),\n )\n }\n}\n"],"mappings":";;;;;;;AAGA,SAAsB,kBAAkB;AAiDjC,IAAM,cAAN,MAAkB;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY;AAAA,IACV,UAAU,EAAE,eAAe,CAAC,KAAK,EAAE;AAAA,IACnC;AAAA,EACF,IAAwB,CAAC,GAAG;AAC1B,UAAM,cAAc,gBAAgB;AAEpC,QAAI,CAAC,aAAa;AAChB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,WAAK,WAAW;AAAA,IAClB;AAEA,SAAK,cAAc;AACnB,SAAK,UAAU;AACf,WAAO,MAAM,gDAAgD;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,SACJ,UAC+C;AAC/C,UAAM,cAAc,KAAK;AAEzB,UAAM,cACJ,QAAQ,IAAI,mBACZ;AAEF,UAAM,iBAAiB,MAAM;AAAA,MAC3B,MACE,MAAM,GAAG,WAAW,kBAAkB;AAAA,QACpC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB;AAAA,UACA,WAAW;AAAA,QACb,CAAC;AAAA,MACH,CAAC;AAAA,MACH,CAAC,WAAW;AAAA,QACV,MAAM,qBAAqB;AAAA,QAC3B,SAAS,MAAM;AAAA,MACjB;AAAA,IACF;AAEA,QAAI,eAAe,SAAS;AAC1B,aAAO;AAAA,IACT;AAEA,UAAM,wBAAwB,MAAM;AAAA,MAClC,YAAY;AACV,cAAM,WAAY,MAAM,eAAe,KAAK,KAAK;AAEjD,YAAI,CAAC,SAAS,aAAa;AACzB,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAEA,aAAK,WAAW;AAChB,eAAO;AAAA,MACT;AAAA,MACA,CAAC,WAAW;AAAA,QACV,MAAM,qBAAqB;AAAA,QAC3B,SAAS,MAAM;AAAA,MACjB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,iBAA2E;AACzE,WAAO;AAAA,MACL,MAAM;AACJ,YAAI,CAAC,KAAK,UAAU,eAAe,CAAC,KAAK,UAAU,QAAQ;AACzD,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAEA,eAAO;AAAA,UACL,SAAS,KAAK;AAAA,UACd,UAAU,KAAK;AAAA,QACjB;AAAA,MACF;AAAA,MACA,CAAC,WAAW;AAAA,QACV,MAAM,qBAAqB;AAAA,QAC3B,SAAS,MAAM;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AACF;","names":[]}