@cipherstash/stack 0.4.0 → 0.5.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 (80) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +9 -36
  3. package/dist/bin/stash.js +215 -179
  4. package/dist/bin/stash.js.map +1 -1
  5. package/dist/{chunk-OAPLZLR5.js → chunk-GXGEW6T4.js} +2 -76
  6. package/dist/chunk-GXGEW6T4.js.map +1 -0
  7. package/dist/{chunk-KOU2MHXJ.js → chunk-HCL75LXQ.js} +1 -1
  8. package/dist/chunk-HCL75LXQ.js.map +1 -0
  9. package/dist/chunk-LBMC4D6D.js +19 -0
  10. package/dist/chunk-LBMC4D6D.js.map +1 -0
  11. package/dist/chunk-Q5FTQLYG.js +68 -0
  12. package/dist/chunk-Q5FTQLYG.js.map +1 -0
  13. package/dist/{chunk-WUZGKTMG.js → chunk-TGUELLJS.js} +16 -19
  14. package/dist/chunk-TGUELLJS.js.map +1 -0
  15. package/dist/chunk-YLISU7FD.js +357 -0
  16. package/dist/chunk-YLISU7FD.js.map +1 -0
  17. package/dist/{client-CUGXheRq.d.cts → client-BFrHTgC6.d.ts} +36 -3
  18. package/dist/{client-CrGQnSlk.d.ts → client-BVQvgvdY.d.cts} +36 -3
  19. package/dist/client.d.cts +4 -4
  20. package/dist/client.d.ts +4 -4
  21. package/dist/drizzle/index.cjs.map +1 -1
  22. package/dist/drizzle/index.d.cts +14 -6
  23. package/dist/drizzle/index.d.ts +14 -6
  24. package/dist/drizzle/index.js +3 -3
  25. package/dist/drizzle/index.js.map +1 -1
  26. package/dist/dynamodb/index.cjs +68 -0
  27. package/dist/dynamodb/index.cjs.map +1 -1
  28. package/dist/dynamodb/index.d.cts +4 -4
  29. package/dist/dynamodb/index.d.ts +4 -4
  30. package/dist/dynamodb/index.js +9 -0
  31. package/dist/dynamodb/index.js.map +1 -1
  32. package/dist/encryption/index.cjs +2572 -0
  33. package/dist/encryption/index.cjs.map +1 -0
  34. package/dist/encryption/index.d.cts +7 -0
  35. package/dist/encryption/index.d.ts +7 -0
  36. package/dist/encryption/index.js +17 -0
  37. package/dist/encryption/index.js.map +1 -0
  38. package/dist/errors/index.cjs +44 -0
  39. package/dist/errors/index.cjs.map +1 -0
  40. package/dist/errors/index.d.cts +94 -0
  41. package/dist/errors/index.d.ts +94 -0
  42. package/dist/errors/index.js +9 -0
  43. package/dist/errors/index.js.map +1 -0
  44. package/dist/identity/index.cjs +21 -13
  45. package/dist/identity/index.cjs.map +1 -1
  46. package/dist/identity/index.d.cts +78 -2
  47. package/dist/identity/index.d.ts +78 -2
  48. package/dist/identity/index.js +7 -3
  49. package/dist/identity/index.js.map +1 -1
  50. package/dist/index.cjs +379 -41
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +21 -19
  53. package/dist/index.d.ts +21 -19
  54. package/dist/index.js +14 -8
  55. package/dist/schema/index.d.cts +1 -2
  56. package/dist/schema/index.d.ts +1 -2
  57. package/dist/secrets/index.cjs +70 -39
  58. package/dist/secrets/index.cjs.map +1 -1
  59. package/dist/secrets/index.d.cts +4 -6
  60. package/dist/secrets/index.d.ts +4 -6
  61. package/dist/secrets/index.js +9 -318
  62. package/dist/secrets/index.js.map +1 -1
  63. package/dist/supabase/index.cjs +84 -1
  64. package/dist/supabase/index.cjs.map +1 -1
  65. package/dist/supabase/index.d.cts +4 -4
  66. package/dist/supabase/index.d.ts +4 -4
  67. package/dist/supabase/index.js +24 -1
  68. package/dist/supabase/index.js.map +1 -1
  69. package/dist/{types-public-Wj6tB_ux.d.ts → types-public-dKxDzTIL.d.cts} +1 -9
  70. package/dist/{types-public-Wj6tB_ux.d.cts → types-public-dKxDzTIL.d.ts} +1 -9
  71. package/dist/types-public.cjs.map +1 -1
  72. package/dist/types-public.d.cts +1 -2
  73. package/dist/types-public.d.ts +1 -2
  74. package/dist/types-public.js +1 -1
  75. package/package.json +29 -1
  76. package/dist/chunk-KOU2MHXJ.js.map +0 -1
  77. package/dist/chunk-OAPLZLR5.js.map +0 -1
  78. package/dist/chunk-WUZGKTMG.js.map +0 -1
  79. package/dist/index-9-Ya3fDK.d.cts +0 -169
  80. package/dist/index-9-Ya3fDK.d.ts +0 -169
package/dist/index.cjs CHANGED
@@ -31,28 +31,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  Encryption: () => Encryption,
34
- EncryptionErrorTypes: () => EncryptionErrorTypes,
34
+ Secrets: () => Secrets,
35
35
  encryptedColumn: () => encryptedColumn,
36
36
  encryptedField: () => encryptedField,
37
37
  encryptedTable: () => encryptedTable,
38
- getErrorMessage: () => getErrorMessage
38
+ encryptedToPgComposite: () => encryptedToPgComposite,
39
+ isEncryptedPayload: () => isEncryptedPayload
39
40
  });
40
41
  module.exports = __toCommonJS(index_exports);
41
42
 
42
- // src/errors/index.ts
43
- var EncryptionErrorTypes = {
44
- ClientInitError: "ClientInitError",
45
- EncryptionError: "EncryptionError",
46
- DecryptionError: "DecryptionError",
47
- LockContextError: "LockContextError",
48
- CtsTokenError: "CtsTokenError"
49
- };
50
- function getErrorMessage(error) {
51
- if (error instanceof Error) return error.message;
52
- if (typeof error === "string") return error;
53
- return String(error);
54
- }
55
-
56
43
  // src/schema/index.ts
57
44
  var import_zod = require("zod");
58
45
  var castAsEnum = import_zod.z.enum(["bigint", "boolean", "date", "number", "string", "json", "text"]).default("text");
@@ -380,6 +367,15 @@ function buildEncryptConfig(...protectTables) {
380
367
  return config;
381
368
  }
382
369
 
370
+ // src/errors/index.ts
371
+ var EncryptionErrorTypes = {
372
+ ClientInitError: "ClientInitError",
373
+ EncryptionError: "EncryptionError",
374
+ DecryptionError: "DecryptionError",
375
+ LockContextError: "LockContextError",
376
+ CtsTokenError: "CtsTokenError"
377
+ };
378
+
383
379
  // src/utils/config/index.ts
384
380
  var import_node_fs = __toESM(require("fs"), 1);
385
381
  var import_node_path = __toESM(require("path"), 1);
@@ -445,25 +441,33 @@ function loadWorkSpaceId(suppliedCrn) {
445
441
 
446
442
  // src/utils/logger/index.ts
447
443
  var import_evlog = require("evlog");
448
- function samplingFromEnv() {
449
- const env = process.env.STASH_LOG_LEVEL;
450
- if (!env) return void 0;
451
- const levels = ["debug", "info", "warn", "error"];
452
- const idx = levels.indexOf(env);
453
- if (idx === -1) return void 0;
454
- return Object.fromEntries(levels.map((l, i) => [l, i >= idx ? 100 : 0]));
444
+ var validLevels = ["debug", "info", "error"];
445
+ function levelFromEnv() {
446
+ const env = process.env.STASH_STACK_LOG;
447
+ if (env && validLevels.includes(env)) return env;
448
+ return "error";
449
+ }
450
+ function samplingRatesForLevel(level) {
451
+ switch (level) {
452
+ case "debug":
453
+ return { debug: 100, info: 100, warn: 100, error: 100 };
454
+ case "info":
455
+ return { debug: 0, info: 100, warn: 100, error: 100 };
456
+ case "error":
457
+ default:
458
+ return { debug: 0, info: 0, warn: 0, error: 100 };
459
+ }
455
460
  }
456
461
  var initialized = false;
457
- function initStackLogger(config) {
462
+ function initStackLogger() {
458
463
  if (initialized) return;
459
464
  initialized = true;
460
- const rates = samplingFromEnv();
465
+ const level = levelFromEnv();
466
+ const rates = samplingRatesForLevel(level);
461
467
  (0, import_evlog.initLogger)({
462
468
  env: { service: "@cipherstash/stack" },
463
- enabled: config?.enabled ?? !!rates,
464
- pretty: config?.pretty,
465
- ...rates && { sampling: { rates } },
466
- ...config?.drain && { drain: config.drain }
469
+ enabled: true,
470
+ sampling: { rates }
467
471
  });
468
472
  }
469
473
  initStackLogger();
@@ -501,8 +505,14 @@ var logger = {
501
505
  // src/encryption/index.ts
502
506
  var import_result11 = require("@byteslice/result");
503
507
  var import_protect_ffi9 = require("@cipherstash/protect-ffi");
508
+ var import_uuid = require("uuid");
504
509
 
505
510
  // src/encryption/helpers/index.ts
511
+ function encryptedToPgComposite(obj) {
512
+ return {
513
+ data: obj
514
+ };
515
+ }
506
516
  function encryptedToCompositeLiteral(obj) {
507
517
  return `(${JSON.stringify(JSON.stringify(obj))})`;
508
518
  }
@@ -2809,23 +2819,14 @@ var EncryptionClient = class {
2809
2819
  };
2810
2820
  }
2811
2821
  };
2812
-
2813
- // src/index.ts
2814
- function isValidUuid(uuid) {
2815
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
2816
- return uuidRegex.test(uuid);
2817
- }
2818
2822
  var Encryption = async (config) => {
2819
- if (config.logging) {
2820
- initStackLogger(config.logging);
2821
- }
2822
2823
  const { schemas, config: clientConfig } = config;
2823
2824
  if (!schemas.length) {
2824
2825
  throw new Error(
2825
2826
  "[encryption]: At least one encryptedTable must be provided to initialize the encryption client"
2826
2827
  );
2827
2828
  }
2828
- if (clientConfig?.keyset && "id" in clientConfig.keyset && !isValidUuid(clientConfig.keyset.id)) {
2829
+ if (clientConfig?.keyset && "id" in clientConfig.keyset && !(0, import_uuid.validate)(clientConfig.keyset.id)) {
2829
2830
  throw new Error(
2830
2831
  "[encryption]: Invalid UUID provided for keyset id. Must be a valid UUID."
2831
2832
  );
@@ -2841,13 +2842,350 @@ var Encryption = async (config) => {
2841
2842
  }
2842
2843
  return result.data;
2843
2844
  };
2845
+
2846
+ // src/secrets/index.ts
2847
+ var Secrets = class {
2848
+ encryptionClient = null;
2849
+ config;
2850
+ apiBaseUrl = process.env.STASH_API_URL || "https://dashboard.cipherstash.com/api/secrets";
2851
+ secretsSchema = encryptedTable("secrets", {
2852
+ value: encryptedColumn("value")
2853
+ });
2854
+ constructor(config) {
2855
+ const workspaceCRN = config.workspaceCRN ?? process.env.CS_WORKSPACE_CRN;
2856
+ const clientId = config.clientId ?? process.env.CS_CLIENT_ID;
2857
+ const clientKey = config.clientKey ?? process.env.CS_CLIENT_KEY;
2858
+ const accessKey = config.accessKey ?? process.env.CS_CLIENT_ACCESS_KEY;
2859
+ if (!workspaceCRN || !clientId || !clientKey || !accessKey) {
2860
+ throw new Error(
2861
+ "Missing required configuration or environment variables."
2862
+ );
2863
+ }
2864
+ this.config = {
2865
+ environment: config.environment,
2866
+ workspaceCRN,
2867
+ clientId,
2868
+ clientKey,
2869
+ accessKey
2870
+ };
2871
+ }
2872
+ initPromise = null;
2873
+ /**
2874
+ * Initialize the Secrets client and underlying Encryption client
2875
+ */
2876
+ async ensureInitialized() {
2877
+ if (!this.initPromise) {
2878
+ this.initPromise = this._doInit();
2879
+ }
2880
+ return this.initPromise;
2881
+ }
2882
+ async _doInit() {
2883
+ logger.debug("Initializing the Secrets client.");
2884
+ this.encryptionClient = await Encryption({
2885
+ schemas: [this.secretsSchema],
2886
+ config: {
2887
+ workspaceCrn: this.config.workspaceCRN,
2888
+ clientId: this.config.clientId,
2889
+ clientKey: this.config.clientKey,
2890
+ accessKey: this.config.accessKey,
2891
+ keyset: { name: this.config.environment }
2892
+ }
2893
+ });
2894
+ logger.debug("Successfully initialized the Secrets client.");
2895
+ }
2896
+ /**
2897
+ * Get the authorization header for API requests
2898
+ */
2899
+ getAuthHeader() {
2900
+ return `Bearer ${this.config.accessKey}`;
2901
+ }
2902
+ /**
2903
+ * Make an API request with error handling.
2904
+ *
2905
+ * For GET requests, `params` are appended as URL query parameters.
2906
+ * For POST requests, `body` is sent as JSON in the request body.
2907
+ */
2908
+ async apiRequest(method, path2, options) {
2909
+ try {
2910
+ let url = `${this.apiBaseUrl}${path2}`;
2911
+ if (options?.params) {
2912
+ const searchParams = new URLSearchParams(options.params);
2913
+ url = `${url}?${searchParams.toString()}`;
2914
+ }
2915
+ logger.debug(`Secrets API request: ${method} ${path2}`);
2916
+ const headers = {
2917
+ "Content-Type": "application/json",
2918
+ Authorization: this.getAuthHeader()
2919
+ };
2920
+ const response = await fetch(url, {
2921
+ method,
2922
+ headers,
2923
+ body: options?.body ? JSON.stringify(options.body) : void 0
2924
+ });
2925
+ if (!response.ok) {
2926
+ const errorText = await response.text();
2927
+ let errorMessage = `API request failed with status ${response.status}`;
2928
+ try {
2929
+ const errorJson = JSON.parse(errorText);
2930
+ errorMessage = errorJson.message || errorJson.error || errorMessage;
2931
+ } catch {
2932
+ errorMessage = errorText || errorMessage;
2933
+ }
2934
+ logger.error(`Secrets API error on ${method} ${path2}: ${errorMessage}`);
2935
+ return {
2936
+ failure: {
2937
+ type: "ApiError",
2938
+ message: errorMessage
2939
+ }
2940
+ };
2941
+ }
2942
+ logger.debug(`Secrets API request successful: ${method} ${path2}`);
2943
+ const data = await response.json();
2944
+ return { data };
2945
+ } catch (error) {
2946
+ const message = error instanceof Error ? error.message : "Unknown network error occurred";
2947
+ logger.error(`Secrets network error on ${method} ${path2}: ${message}`);
2948
+ return {
2949
+ failure: {
2950
+ type: "NetworkError",
2951
+ message
2952
+ }
2953
+ };
2954
+ }
2955
+ }
2956
+ /**
2957
+ * Store an encrypted secret in the vault.
2958
+ * The value is encrypted locally before being sent to the API.
2959
+ *
2960
+ * API: POST /api/secrets/set
2961
+ *
2962
+ * @param name - The name of the secret
2963
+ * @param value - The plaintext value to encrypt and store
2964
+ * @returns A Result containing the API response or an error
2965
+ */
2966
+ async set(name, value) {
2967
+ logger.debug("Setting secret");
2968
+ await this.ensureInitialized();
2969
+ if (!this.encryptionClient) {
2970
+ return {
2971
+ failure: {
2972
+ type: "ClientError",
2973
+ message: "Failed to initialize Encryption client"
2974
+ }
2975
+ };
2976
+ }
2977
+ const encryptResult = await this.encryptionClient.encrypt(value, {
2978
+ column: this.secretsSchema.value,
2979
+ table: this.secretsSchema
2980
+ });
2981
+ if (encryptResult.failure) {
2982
+ logger.error("Failed to encrypt secret");
2983
+ return {
2984
+ failure: {
2985
+ type: "EncryptionError",
2986
+ message: encryptResult.failure.message
2987
+ }
2988
+ };
2989
+ }
2990
+ const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
2991
+ return await this.apiRequest("POST", "/set", {
2992
+ body: {
2993
+ workspaceId,
2994
+ environment: this.config.environment,
2995
+ name,
2996
+ encryptedValue: encryptedToPgComposite(encryptResult.data)
2997
+ }
2998
+ });
2999
+ }
3000
+ /**
3001
+ * Retrieve and decrypt a secret from the vault.
3002
+ * The secret is decrypted locally after retrieval.
3003
+ *
3004
+ * API: GET /api/secrets/get?workspaceId=...&environment=...&name=...
3005
+ *
3006
+ * @param name - The name of the secret to retrieve
3007
+ * @returns A Result containing the decrypted value or an error
3008
+ */
3009
+ async get(name) {
3010
+ logger.debug("Getting secret");
3011
+ await this.ensureInitialized();
3012
+ if (!this.encryptionClient) {
3013
+ return {
3014
+ failure: {
3015
+ type: "ClientError",
3016
+ message: "Failed to initialize Encryption client"
3017
+ }
3018
+ };
3019
+ }
3020
+ const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
3021
+ const apiResult = await this.apiRequest("GET", "/get", {
3022
+ params: {
3023
+ workspaceId,
3024
+ environment: this.config.environment,
3025
+ name
3026
+ }
3027
+ });
3028
+ if (apiResult.failure) {
3029
+ return apiResult;
3030
+ }
3031
+ const decryptResult = await this.encryptionClient.decrypt(
3032
+ apiResult.data.encryptedValue.data
3033
+ );
3034
+ if (decryptResult.failure) {
3035
+ logger.error("Failed to decrypt secret");
3036
+ return {
3037
+ failure: {
3038
+ type: "DecryptionError",
3039
+ message: decryptResult.failure.message
3040
+ }
3041
+ };
3042
+ }
3043
+ if (typeof decryptResult.data !== "string") {
3044
+ logger.error("Decrypted secret value is not a string");
3045
+ return {
3046
+ failure: {
3047
+ type: "DecryptionError",
3048
+ message: "Decrypted value is not a string"
3049
+ }
3050
+ };
3051
+ }
3052
+ return { data: decryptResult.data };
3053
+ }
3054
+ /**
3055
+ * Retrieve and decrypt many secrets from the vault.
3056
+ * The secrets are decrypted locally after retrieval.
3057
+ * This method only triggers a single network request to the ZeroKMS.
3058
+ *
3059
+ * API: GET /api/secrets/get-many?workspaceId=...&environment=...&names=name1,name2,...
3060
+ *
3061
+ * Constraints:
3062
+ * - Minimum 2 secret names required
3063
+ * - Maximum 100 secret names per request
3064
+ *
3065
+ * @param names - The names of the secrets to retrieve (min 2, max 100)
3066
+ * @returns A Result containing an object mapping secret names to their decrypted values
3067
+ */
3068
+ async getMany(names) {
3069
+ logger.debug(`Getting ${names.length} secrets.`);
3070
+ await this.ensureInitialized();
3071
+ if (!this.encryptionClient) {
3072
+ return {
3073
+ failure: {
3074
+ type: "ClientError",
3075
+ message: "Failed to initialize Encryption client"
3076
+ }
3077
+ };
3078
+ }
3079
+ if (names.length < 2) {
3080
+ return {
3081
+ failure: {
3082
+ type: "ClientError",
3083
+ message: "At least 2 secret names are required for getMany"
3084
+ }
3085
+ };
3086
+ }
3087
+ if (names.length > 100) {
3088
+ return {
3089
+ failure: {
3090
+ type: "ClientError",
3091
+ message: "Maximum 100 secret names per request"
3092
+ }
3093
+ };
3094
+ }
3095
+ const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
3096
+ const apiResult = await this.apiRequest(
3097
+ "GET",
3098
+ "/get-many",
3099
+ {
3100
+ params: {
3101
+ workspaceId,
3102
+ environment: this.config.environment,
3103
+ names: names.join(",")
3104
+ }
3105
+ }
3106
+ );
3107
+ if (apiResult.failure) {
3108
+ return apiResult;
3109
+ }
3110
+ const dataToDecrypt = apiResult.data.map((item) => ({
3111
+ name: item.name,
3112
+ value: item.encryptedValue.data
3113
+ }));
3114
+ const decryptResult = await this.encryptionClient.bulkDecryptModels(dataToDecrypt);
3115
+ if (decryptResult.failure) {
3116
+ logger.error(
3117
+ `Failed to decrypt secrets: ${decryptResult.failure.message}`
3118
+ );
3119
+ return {
3120
+ failure: {
3121
+ type: "DecryptionError",
3122
+ message: decryptResult.failure.message
3123
+ }
3124
+ };
3125
+ }
3126
+ const decryptedSecrets = decryptResult.data;
3127
+ const secretsMap = {};
3128
+ for (const secret of decryptedSecrets) {
3129
+ if (secret.name && secret.value) {
3130
+ secretsMap[secret.name] = secret.value;
3131
+ }
3132
+ }
3133
+ return { data: secretsMap };
3134
+ }
3135
+ /**
3136
+ * List all secrets in the environment.
3137
+ * Only names and metadata are returned; values remain encrypted.
3138
+ *
3139
+ * API: GET /api/secrets/list?workspaceId=...&environment=...
3140
+ *
3141
+ * @returns A Result containing the list of secrets or an error
3142
+ */
3143
+ async list() {
3144
+ logger.debug("Listing secrets.");
3145
+ const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
3146
+ const apiResult = await this.apiRequest(
3147
+ "GET",
3148
+ "/list",
3149
+ {
3150
+ params: {
3151
+ workspaceId,
3152
+ environment: this.config.environment
3153
+ }
3154
+ }
3155
+ );
3156
+ if (apiResult.failure) {
3157
+ return apiResult;
3158
+ }
3159
+ return { data: apiResult.data.secrets };
3160
+ }
3161
+ /**
3162
+ * Delete a secret from the vault.
3163
+ *
3164
+ * API: POST /api/secrets/delete
3165
+ *
3166
+ * @param name - The name of the secret to delete
3167
+ * @returns A Result containing the API response or an error
3168
+ */
3169
+ async delete(name) {
3170
+ logger.debug("Deleting secret");
3171
+ const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
3172
+ return await this.apiRequest("POST", "/delete", {
3173
+ body: {
3174
+ workspaceId,
3175
+ environment: this.config.environment,
3176
+ name
3177
+ }
3178
+ });
3179
+ }
3180
+ };
2844
3181
  // Annotate the CommonJS export names for ESM import in node:
2845
3182
  0 && (module.exports = {
2846
3183
  Encryption,
2847
- EncryptionErrorTypes,
3184
+ Secrets,
2848
3185
  encryptedColumn,
2849
3186
  encryptedField,
2850
3187
  encryptedTable,
2851
- getErrorMessage
3188
+ encryptedToPgComposite,
3189
+ isEncryptedPayload
2852
3190
  });
2853
3191
  //# sourceMappingURL=index.cjs.map