@cipherstash/stack 0.10.0 → 0.12.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 (61) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +38 -18
  3. package/dist/bin/stash.js +160 -117
  4. package/dist/bin/stash.js.map +1 -1
  5. package/dist/{chunk-HCL75LXQ.js → chunk-46PDK2Z6.js} +1 -1
  6. package/dist/chunk-46PDK2Z6.js.map +1 -0
  7. package/dist/{chunk-3BOJHROP.js → chunk-EHDOOLWU.js} +3 -3
  8. package/dist/{chunk-Y3KSTOVR.js → chunk-GNLU3I27.js} +14 -3
  9. package/dist/chunk-GNLU3I27.js.map +1 -0
  10. package/dist/{chunk-4C52Q5R6.js → chunk-O4MBO4DJ.js} +8 -22
  11. package/dist/chunk-O4MBO4DJ.js.map +1 -0
  12. package/dist/{client-_63nbmO7.d.ts → client-CZLBiToT.d.ts} +1 -7
  13. package/dist/{client-CV7X5Hrt.d.cts → client-bW-5wG9Y.d.cts} +1 -7
  14. package/dist/client.cjs +13 -2
  15. package/dist/client.cjs.map +1 -1
  16. package/dist/client.d.cts +2 -2
  17. package/dist/client.d.ts +2 -2
  18. package/dist/client.js +1 -1
  19. package/dist/drizzle/index.cjs +13 -2
  20. package/dist/drizzle/index.cjs.map +1 -1
  21. package/dist/drizzle/index.d.cts +2 -2
  22. package/dist/drizzle/index.d.ts +2 -2
  23. package/dist/drizzle/index.js +2 -2
  24. package/dist/dynamodb/index.d.cts +2 -2
  25. package/dist/dynamodb/index.d.ts +2 -2
  26. package/dist/encryption/index.cjs +22 -96
  27. package/dist/encryption/index.cjs.map +1 -1
  28. package/dist/encryption/index.d.cts +2 -2
  29. package/dist/encryption/index.d.ts +2 -2
  30. package/dist/encryption/index.js +3 -4
  31. package/dist/index.cjs +29 -81
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +3 -3
  34. package/dist/index.d.ts +3 -3
  35. package/dist/index.js +4 -4
  36. package/dist/schema/index.cjs +13 -2
  37. package/dist/schema/index.cjs.map +1 -1
  38. package/dist/schema/index.d.cts +1 -1
  39. package/dist/schema/index.d.ts +1 -1
  40. package/dist/schema/index.js +1 -1
  41. package/dist/secrets/index.cjs +29 -81
  42. package/dist/secrets/index.cjs.map +1 -1
  43. package/dist/secrets/index.d.cts +1 -1
  44. package/dist/secrets/index.d.ts +1 -1
  45. package/dist/secrets/index.js +4 -4
  46. package/dist/supabase/index.cjs +12 -2
  47. package/dist/supabase/index.cjs.map +1 -1
  48. package/dist/supabase/index.d.cts +2 -2
  49. package/dist/supabase/index.d.ts +2 -2
  50. package/dist/supabase/index.js +1 -1
  51. package/dist/{types-public-B5YxQhR6.d.cts → types-public-WADaA-TW.d.cts} +119 -1
  52. package/dist/{types-public-B5YxQhR6.d.ts → types-public-WADaA-TW.d.ts} +119 -1
  53. package/dist/types-public.cjs.map +1 -1
  54. package/dist/types-public.d.cts +1 -1
  55. package/dist/types-public.d.ts +1 -1
  56. package/dist/types-public.js +1 -1
  57. package/package.json +6 -5
  58. package/dist/chunk-4C52Q5R6.js.map +0 -1
  59. package/dist/chunk-HCL75LXQ.js.map +0 -1
  60. package/dist/chunk-Y3KSTOVR.js.map +0 -1
  61. /package/dist/{chunk-3BOJHROP.js.map → chunk-EHDOOLWU.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @cipherstash/stack
2
2
 
3
+ ## 0.12.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 15764a8: Implement stack auth into stash cli flow.
8
+
9
+ ## 0.11.0
10
+
11
+ ### Minor Changes
12
+
13
+ - b0e56b8: Upgrade protect-ffi to 0.21.0 and enable array_index_mode for searchable JSON
14
+
15
+ - Upgrade `@cipherstash/protect-ffi` to 0.21.0 across all packages
16
+ - Enable `array_index_mode: 'all'` on STE vec indexes so JSON array operations
17
+ (jsonb_array_elements, jsonb_array_length, array containment) work correctly
18
+ - Delegate credential resolution entirely to protect-ffi's `withEnvCredentials`
19
+ - Download latest EQL at build/runtime instead of bundling hardcoded SQL files
20
+
3
21
  ## 0.10.0
4
22
 
5
23
  ### Minor Changes
package/README.md CHANGED
@@ -44,19 +44,29 @@ pnpm add @cipherstash/stack
44
44
 
45
45
  ## Quick Start
46
46
 
47
+ ### 1. Initialize and authenticate your project
48
+
49
+ ```bash
50
+ npx stash init
51
+ ```
52
+
53
+ The wizard will authenticate you, walk you through choosing a database connection method, build an encryption schema, and install the required dependencies.
54
+
55
+ ### 2. Encrypt and decrypt
56
+
47
57
  ```typescript
48
58
  import { Encryption } from "@cipherstash/stack"
49
59
  import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
50
60
 
51
- // 1. Define a schema
61
+ // Define a schema
52
62
  const users = encryptedTable("users", {
53
63
  email: encryptedColumn("email").equality().freeTextSearch(),
54
64
  })
55
65
 
56
- // 2. Create a client (reads CS_* env vars automatically)
66
+ // Create a client
57
67
  const client = await Encryption({ schemas: [users] })
58
68
 
59
- // 3. Encrypt a value
69
+ // Encrypt a value
60
70
  const encrypted = await client.encrypt("hello@example.com", {
61
71
  column: users.email,
62
72
  table: users,
@@ -68,7 +78,7 @@ if (encrypted.failure) {
68
78
  console.log("Encrypted payload:", encrypted.data)
69
79
  }
70
80
 
71
- // 4. Decrypt the value
81
+ // Decrypt the value
72
82
  const decrypted = await client.decrypt(encrypted.data)
73
83
 
74
84
  if (decrypted.failure) {
@@ -430,6 +440,16 @@ await secrets.delete("DATABASE_URL")
430
440
 
431
441
  The `stash` CLI is bundled with the package and available after install.
432
442
 
443
+ ### `stash auth`
444
+
445
+ Authenticate with CipherStash.
446
+
447
+ ```bash
448
+ npx stash auth login
449
+ ```
450
+
451
+ This runs the device code flow: it opens your browser, you confirm the code, and a token is saved to `~/.cipherstash/auth.json`. No environment variables or credentials files are needed for local development.
452
+
433
453
  ### `stash init`
434
454
 
435
455
  Initialize CipherStash for your project with an interactive wizard.
@@ -440,11 +460,13 @@ npx stash init --supabase
440
460
  ```
441
461
 
442
462
  The wizard will:
443
- 1. Choose your database connection method (Drizzle ORM, Supabase JS, Prisma, or Raw SQL)
444
- 2. Build an encryption schema interactively or use a placeholder, then generate the encryption client file
445
- 3. Install `@cipherstash/stack-forge` as a dev dependency for database tooling
463
+ 1. Authenticate with CipherStash (device code flow)
464
+ 2. Bind your device to the default Keyset
465
+ 3. Choose your database connection method (Drizzle ORM, Supabase JS, Prisma, or Raw SQL)
466
+ 4. Build an encryption schema interactively or use a placeholder, then generate the encryption client file
467
+ 5. Install `@cipherstash/stack-forge` as a dev dependency for database tooling
446
468
 
447
- After `stash init`, create a CipherStash account at [dashboard.cipherstash.com/sign-up](https://dashboard.cipherstash.com/sign-up) to get your credentials, then run `npx stash-forge setup` to configure your database connection.
469
+ After `stash init`, run `npx stash-forge setup` to configure your database.
448
470
 
449
471
  | Flag | Description |
450
472
  |------|-------------|
@@ -468,11 +490,15 @@ npx stash secrets delete -name DATABASE_URL -environment production
468
490
  | `stash secrets list` | `-environment` | `-e` | List all secret names in an environment |
469
491
  | `stash secrets delete` | `-name`, `-environment`, `-yes` | `-n`, `-e`, `-y` | Delete a secret (prompts for confirmation unless `-yes`) |
470
492
 
471
- The CLI reads credentials from the same `CS_*` environment variables described in [Configuration](#configuration).
472
-
473
493
  ## Configuration
474
494
 
475
- ### Environment Variables
495
+ ### Local Development
496
+
497
+ No environment variables or credentials are needed for local development. Run `npx @cipherstash/stack auth login` to authenticate via the device code flow, and the SDK and CLI will use the token saved to `~/.cipherstash/auth.json`.
498
+
499
+ ### Going to Production
500
+
501
+ For production, CI/CD, and deployed environments, you'll need to set up machine credentials via environment variables:
476
502
 
477
503
  | Variable | Description |
478
504
  |-----|-------|
@@ -481,13 +507,7 @@ The CLI reads credentials from the same `CS_*` environment variables described i
481
507
  | `CS_CLIENT_KEY` | Client key material used with ZeroKMS for encryption |
482
508
  | `CS_CLIENT_ACCESS_KEY` | API key for authenticating with the CipherStash API |
483
509
 
484
- Store these in a `.env` file or set them in your hosting platform.
485
-
486
- Sign up at [cipherstash.com/signup](https://cipherstash.com/signup) and follow the onboarding to generate credentials.
487
-
488
- ### TOML Config
489
-
490
- You can also configure credentials via `cipherstash.toml` and `cipherstash.secret.toml` files in your project root. See the [CipherStash docs](https://cipherstash.com/docs) for format details.
510
+ See the [Going to Production](https://cipherstash.com/docs/stack/going-to-production) guide for full details on creating machine clients, setting up access keys, and configuring CI/CD pipelines.
491
511
 
492
512
  ### Programmatic Config
493
513
 
package/dist/bin/stash.js CHANGED
@@ -104,8 +104,8 @@ var require_package = __commonJS({
104
104
  var require_main = __commonJS({
105
105
  "../../node_modules/.pnpm/dotenv@16.4.7/node_modules/dotenv/lib/main.js"(exports, module) {
106
106
  "use strict";
107
- var fs2 = __require("fs");
108
- var path2 = __require("path");
107
+ var fs = __require("fs");
108
+ var path = __require("path");
109
109
  var os = __require("os");
110
110
  var crypto = __require("crypto");
111
111
  var packageJson = require_package();
@@ -211,7 +211,7 @@ var require_main = __commonJS({
211
211
  if (options && options.path && options.path.length > 0) {
212
212
  if (Array.isArray(options.path)) {
213
213
  for (const filepath of options.path) {
214
- if (fs2.existsSync(filepath)) {
214
+ if (fs.existsSync(filepath)) {
215
215
  possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
216
216
  }
217
217
  }
@@ -219,15 +219,15 @@ var require_main = __commonJS({
219
219
  possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
220
220
  }
221
221
  } else {
222
- possibleVaultPath = path2.resolve(process.cwd(), ".env.vault");
222
+ possibleVaultPath = path.resolve(process.cwd(), ".env.vault");
223
223
  }
224
- if (fs2.existsSync(possibleVaultPath)) {
224
+ if (fs.existsSync(possibleVaultPath)) {
225
225
  return possibleVaultPath;
226
226
  }
227
227
  return null;
228
228
  }
229
229
  function _resolveHome(envPath) {
230
- return envPath[0] === "~" ? path2.join(os.homedir(), envPath.slice(1)) : envPath;
230
+ return envPath[0] === "~" ? path.join(os.homedir(), envPath.slice(1)) : envPath;
231
231
  }
232
232
  function _configVault(options) {
233
233
  _log("Loading env from encrypted .env.vault");
@@ -240,7 +240,7 @@ var require_main = __commonJS({
240
240
  return { parsed };
241
241
  }
242
242
  function configDotenv(options) {
243
- const dotenvPath = path2.resolve(process.cwd(), ".env");
243
+ const dotenvPath = path.resolve(process.cwd(), ".env");
244
244
  let encoding = "utf8";
245
245
  const debug = Boolean(options && options.debug);
246
246
  if (options && options.encoding) {
@@ -263,13 +263,13 @@ var require_main = __commonJS({
263
263
  }
264
264
  let lastError;
265
265
  const parsedAll = {};
266
- for (const path3 of optionPaths) {
266
+ for (const path2 of optionPaths) {
267
267
  try {
268
- const parsed = DotenvModule.parse(fs2.readFileSync(path3, { encoding }));
268
+ const parsed = DotenvModule.parse(fs.readFileSync(path2, { encoding }));
269
269
  DotenvModule.populate(parsedAll, parsed, options);
270
270
  } catch (e2) {
271
271
  if (debug) {
272
- _debug(`Failed to load ${path3} ${e2.message}`);
272
+ _debug(`Failed to load ${path2} ${e2.message}`);
273
273
  }
274
274
  lastError = e2;
275
275
  }
@@ -1200,6 +1200,91 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
1200
1200
  } };
1201
1201
  };
1202
1202
 
1203
+ // src/bin/commands/auth/login.ts
1204
+ import auth from "@cipherstash/auth";
1205
+ var { beginDeviceCodeFlow, bindClientDevice } = auth;
1206
+ var regions = [
1207
+ { value: "ap-southeast-2.aws", label: "Asia Pacific (Sydney)" },
1208
+ { value: "eu-central-1.aws", label: "Europe (Frankfurt)" },
1209
+ { value: "eu-west-1.aws", label: "Europe (Ireland)" },
1210
+ { value: "us-east-1.aws", label: "US East (N. Virginia)" },
1211
+ { value: "us-east-2.aws", label: "US East (Ohio)" },
1212
+ { value: "us-west-1.aws", label: "US West (N. California)" },
1213
+ { value: "us-west-2.aws", label: "US West (Oregon)" }
1214
+ ];
1215
+ async function selectRegion() {
1216
+ const region = await ve({
1217
+ message: "Select a region",
1218
+ options: regions
1219
+ });
1220
+ if (pD(region)) {
1221
+ xe("Cancelled.");
1222
+ process.exit(0);
1223
+ }
1224
+ return region;
1225
+ }
1226
+ async function login(region) {
1227
+ const s = Y2();
1228
+ const pending = await beginDeviceCodeFlow(region, "cli");
1229
+ M2.info(`Your code is: ${pending.userCode}`);
1230
+ M2.info(`Visit: ${pending.verificationUriComplete}`);
1231
+ M2.info(`Code expires in: ${pending.expiresIn}s`);
1232
+ const opened = pending.openInBrowser();
1233
+ if (!opened) {
1234
+ M2.warn("Could not open browser \u2014 please visit the URL above manually.");
1235
+ }
1236
+ s.start("Waiting for authorization...");
1237
+ const auth2 = await pending.pollForToken();
1238
+ s.stop("Authenticated! Token saved to ~/.cipherstash/auth.json");
1239
+ M2.info(
1240
+ `Token expires at: ${new Date(auth2.expiresAt * 1e3).toISOString()}`
1241
+ );
1242
+ }
1243
+ async function bindDevice() {
1244
+ const s = Y2();
1245
+ s.start("Binding device to the default Keyset...");
1246
+ try {
1247
+ await bindClientDevice();
1248
+ s.stop("Your device has been bound to the default Keyset!");
1249
+ } catch (error) {
1250
+ s.stop("Failed to bind your device to the default Keyset!");
1251
+ M2.error(error instanceof Error ? error.message : "Unknown error");
1252
+ process.exit(1);
1253
+ }
1254
+ }
1255
+
1256
+ // src/bin/commands/auth/index.ts
1257
+ var HELP = `
1258
+ Usage: stash auth <command>
1259
+
1260
+ Commands:
1261
+ login Authenticate with CipherStash
1262
+
1263
+ Examples:
1264
+ stash auth login
1265
+ `.trim();
1266
+ async function authCommand(args) {
1267
+ const subcommand = args[0];
1268
+ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
1269
+ console.log(HELP);
1270
+ return;
1271
+ }
1272
+ switch (subcommand) {
1273
+ case "login":
1274
+ {
1275
+ const region = await selectRegion();
1276
+ await login(region);
1277
+ await bindDevice();
1278
+ }
1279
+ break;
1280
+ default:
1281
+ console.error(`Unknown auth command: ${subcommand}
1282
+ `);
1283
+ console.log(HELP);
1284
+ process.exit(1);
1285
+ }
1286
+ }
1287
+
1203
1288
  // src/bin/commands/init/providers/base.ts
1204
1289
  function createBaseProvider() {
1205
1290
  return {
@@ -1212,10 +1297,7 @@ function createBaseProvider() {
1212
1297
  { value: "raw-sql", label: "Raw SQL / pg" }
1213
1298
  ],
1214
1299
  getNextSteps(state) {
1215
- const steps = [
1216
- "Create a CipherStash account and get your credentials:\n https://dashboard.cipherstash.com/sign-up\n Then set: CS_WORKSPACE_CRN, CS_CLIENT_ID, CS_CLIENT_KEY, CS_CLIENT_ACCESS_KEY",
1217
- "Set up your database: npx stash-forge setup"
1218
- ];
1300
+ const steps = ["Set up your database: npx stash-forge setup"];
1219
1301
  if (state.clientFilePath) {
1220
1302
  steps.push(`Edit your encryption schema: ${state.clientFilePath}`);
1221
1303
  }
@@ -1241,10 +1323,7 @@ function createSupabaseProvider() {
1241
1323
  { value: "raw-sql", label: "Raw SQL / pg" }
1242
1324
  ],
1243
1325
  getNextSteps(state) {
1244
- const steps = [
1245
- "Create a CipherStash account and get your credentials:\n https://dashboard.cipherstash.com/sign-up\n Then set: CS_WORKSPACE_CRN, CS_CLIENT_ID, CS_CLIENT_KEY, CS_CLIENT_ACCESS_KEY",
1246
- "Set up your database: npx stash-forge setup"
1247
- ];
1326
+ const steps = ["Set up your database: npx stash-forge setup"];
1248
1327
  if (state.clientFilePath) {
1249
1328
  steps.push(`Edit your encryption schema: ${state.clientFilePath}`);
1250
1329
  }
@@ -1257,6 +1336,18 @@ function createSupabaseProvider() {
1257
1336
  };
1258
1337
  }
1259
1338
 
1339
+ // src/bin/commands/init/steps/authenticate.ts
1340
+ var authenticateStep = {
1341
+ id: "authenticate",
1342
+ name: "Authenticate with CipherStash",
1343
+ async run(state, _provider) {
1344
+ const region = await selectRegion();
1345
+ await login(region);
1346
+ await bindDevice();
1347
+ return { ...state, authenticated: true };
1348
+ }
1349
+ };
1350
+
1260
1351
  // src/bin/commands/init/steps/build-schema.ts
1261
1352
  import { existsSync as existsSync2, mkdirSync, writeFileSync } from "node:fs";
1262
1353
  import { dirname, resolve as resolve2 } from "node:path";
@@ -1658,6 +1749,7 @@ var PROVIDER_MAP = {
1658
1749
  supabase: createSupabaseProvider
1659
1750
  };
1660
1751
  var STEPS = [
1752
+ authenticateStep,
1661
1753
  selectConnectionStep,
1662
1754
  buildSchemaStep,
1663
1755
  installForgeStep,
@@ -1768,8 +1860,18 @@ var matchIndexOptsSchema = z2.object({
1768
1860
  m: z2.number().default(2048).optional(),
1769
1861
  include_original: z2.boolean().default(false).optional()
1770
1862
  });
1863
+ var arrayIndexModeSchema = z2.union([
1864
+ z2.literal("all"),
1865
+ z2.literal("none"),
1866
+ z2.object({
1867
+ item: z2.boolean().optional(),
1868
+ wildcard: z2.boolean().optional(),
1869
+ position: z2.boolean().optional()
1870
+ })
1871
+ ]);
1771
1872
  var steVecIndexOptsSchema = z2.object({
1772
- prefix: z2.string()
1873
+ prefix: z2.string(),
1874
+ array_index_mode: arrayIndexModeSchema.optional()
1773
1875
  });
1774
1876
  var indexesSchema = z2.object({
1775
1877
  ore: oreIndexOptsSchema.optional(),
@@ -1966,7 +2068,7 @@ var EncryptedColumn = class {
1966
2068
  */
1967
2069
  searchableJson() {
1968
2070
  this.castAsValue = "json";
1969
- this.indexesValue.ste_vec = { prefix: "enabled" };
2071
+ this.indexesValue.ste_vec = { prefix: "enabled", array_index_mode: "all" };
1970
2072
  return this;
1971
2073
  }
1972
2074
  build() {
@@ -2014,6 +2116,7 @@ var EncryptedTable = class {
2014
2116
  indexes: {
2015
2117
  ...builtColumn.indexes,
2016
2118
  ste_vec: {
2119
+ ...builtColumn.indexes.ste_vec,
2017
2120
  prefix: `${this.tableName}/${colName}`
2018
2121
  }
2019
2122
  }
@@ -2075,69 +2178,6 @@ var EncryptionErrorTypes = {
2075
2178
  CtsTokenError: "CtsTokenError"
2076
2179
  };
2077
2180
 
2078
- // src/utils/config/index.ts
2079
- import fs from "node:fs";
2080
- import path from "node:path";
2081
- function getWorkspaceCrn(tomlString) {
2082
- let currentSection = "";
2083
- let workspaceCrn;
2084
- const lines = tomlString.split(/\r?\n/);
2085
- for (const line of lines) {
2086
- const trimmedLine = line.trim();
2087
- if (!trimmedLine || trimmedLine.startsWith("#")) {
2088
- continue;
2089
- }
2090
- const sectionMatch = trimmedLine.match(/^\[([^\]]+)\]$/);
2091
- if (sectionMatch) {
2092
- currentSection = sectionMatch[1];
2093
- continue;
2094
- }
2095
- const kvMatch = trimmedLine.match(/^(\w+)\s*=\s*"([^"]+)"$/);
2096
- if (kvMatch) {
2097
- const [_3, key, value] = kvMatch;
2098
- if (currentSection === "auth" && key === "workspace_crn") {
2099
- workspaceCrn = value;
2100
- break;
2101
- }
2102
- }
2103
- }
2104
- return workspaceCrn;
2105
- }
2106
- function extractWorkspaceIdFromCrn(crn) {
2107
- const match = crn.match(/crn:[^:]+:([^:]+)$/);
2108
- if (!match) {
2109
- throw new Error("Invalid CRN format");
2110
- }
2111
- return match[1];
2112
- }
2113
- function loadWorkSpaceId(suppliedCrn) {
2114
- const configPath = path.join(process.cwd(), "cipherstash.toml");
2115
- if (suppliedCrn) {
2116
- return extractWorkspaceIdFromCrn(suppliedCrn);
2117
- }
2118
- if (!fs.existsSync(configPath) && !process.env.CS_WORKSPACE_CRN) {
2119
- throw new Error(
2120
- "You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable."
2121
- );
2122
- }
2123
- if (process.env.CS_WORKSPACE_CRN) {
2124
- return extractWorkspaceIdFromCrn(process.env.CS_WORKSPACE_CRN);
2125
- }
2126
- if (!fs.existsSync(configPath)) {
2127
- throw new Error(
2128
- "You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable."
2129
- );
2130
- }
2131
- const tomlString = fs.readFileSync(configPath, "utf8");
2132
- const workspaceCrn = getWorkspaceCrn(tomlString);
2133
- if (!workspaceCrn) {
2134
- throw new Error(
2135
- "You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable."
2136
- );
2137
- }
2138
- return extractWorkspaceIdFromCrn(workspaceCrn);
2139
- }
2140
-
2141
2181
  // src/utils/logger/index.ts
2142
2182
  import { createRequestLogger, initLogger } from "evlog";
2143
2183
  var validLevels = ["debug", "info", "error"];
@@ -2631,11 +2671,11 @@ import {
2631
2671
  decryptBulk,
2632
2672
  encryptBulk
2633
2673
  } from "@cipherstash/protect-ffi";
2634
- function setNestedValue(obj, path2, value) {
2674
+ function setNestedValue(obj, path, value) {
2635
2675
  const FORBIDDEN_KEYS = ["__proto__", "prototype", "constructor"];
2636
2676
  let current = obj;
2637
- for (let i = 0; i < path2.length - 1; i++) {
2638
- const part = path2[i];
2677
+ for (let i = 0; i < path.length - 1; i++) {
2678
+ const part = path[i];
2639
2679
  if (FORBIDDEN_KEYS.includes(part)) {
2640
2680
  throw new Error(`[encryption]: Forbidden key "${part}" in field path`);
2641
2681
  }
@@ -2644,7 +2684,7 @@ function setNestedValue(obj, path2, value) {
2644
2684
  }
2645
2685
  current = current[part];
2646
2686
  }
2647
- const lastKey = path2[path2.length - 1];
2687
+ const lastKey = path[path.length - 1];
2648
2688
  if (FORBIDDEN_KEYS.includes(lastKey)) {
2649
2689
  throw new Error(`[encryption]: Forbidden key "${lastKey}" in field path`);
2650
2690
  }
@@ -2721,7 +2761,7 @@ function prepareFieldsForEncryption(model, table) {
2721
2761
  continue;
2722
2762
  }
2723
2763
  if (typeof value === "object" && !isEncryptedPayload(value) && !columnPaths2.includes(fullKey)) {
2724
- if (columnPaths2.some((path2) => path2.startsWith(fullKey))) {
2764
+ if (columnPaths2.some((path) => path.startsWith(fullKey))) {
2725
2765
  processNestedFields(
2726
2766
  value,
2727
2767
  fullKey,
@@ -2899,7 +2939,7 @@ function prepareBulkModelsForOperation(models, table) {
2899
2939
  continue;
2900
2940
  }
2901
2941
  if (typeof value === "object" && !isEncryptedPayload(value) && !columnPaths.includes(fullKey)) {
2902
- if (columnPaths.some((path2) => path2.startsWith(fullKey))) {
2942
+ if (columnPaths.some((path) => path.startsWith(fullKey))) {
2903
2943
  processNestedFields(
2904
2944
  value,
2905
2945
  fullKey,
@@ -4074,11 +4114,6 @@ var noClientError = () => new Error(
4074
4114
  var EncryptionClient = class {
4075
4115
  client;
4076
4116
  encryptConfig;
4077
- workspaceId;
4078
- constructor(workspaceCrn) {
4079
- const workspaceId = loadWorkSpaceId(workspaceCrn);
4080
- this.workspaceId = workspaceId;
4081
- }
4082
4117
  /**
4083
4118
  * Initializes the EncryptionClient with the provided configuration.
4084
4119
  * @internal
@@ -4100,10 +4135,10 @@ var EncryptionClient = class {
4100
4135
  this.client = await newClient({
4101
4136
  encryptConfig: validated,
4102
4137
  clientOpts: {
4103
- workspaceCrn: config3.workspaceCrn ?? process.env.CS_WORKSPACE_CRN,
4104
- accessKey: config3.accessKey ?? process.env.CS_CLIENT_ACCESS_KEY,
4105
- clientId: config3.clientId ?? process.env.CS_CLIENT_ID,
4106
- clientKey: config3.clientKey ?? process.env.CS_CLIENT_KEY,
4138
+ workspaceCrn: config3.workspaceCrn,
4139
+ accessKey: config3.accessKey,
4140
+ clientId: config3.clientId,
4141
+ clientKey: config3.clientKey,
4107
4142
  keyset: toFfiKeysetIdentifier(config3.keyset)
4108
4143
  }
4109
4144
  });
@@ -4488,12 +4523,6 @@ var EncryptionClient = class {
4488
4523
  bulkDecrypt(encryptedPayloads) {
4489
4524
  return new BulkDecryptOperation(this.client, encryptedPayloads);
4490
4525
  }
4491
- /** e.g., debugging or environment info */
4492
- clientInfo() {
4493
- return {
4494
- workspaceId: this.workspaceId
4495
- };
4496
- }
4497
4526
  /**
4498
4527
  * Get the encrypt config object.
4499
4528
  *
@@ -4515,7 +4544,7 @@ var Encryption = async (config3) => {
4515
4544
  "[encryption]: Invalid UUID provided for keyset id. Must be a valid UUID."
4516
4545
  );
4517
4546
  }
4518
- const client = new EncryptionClient(clientConfig?.workspaceCrn);
4547
+ const client = new EncryptionClient();
4519
4548
  const encryptConfig = buildEncryptConfig(...schemas);
4520
4549
  const result = await client.init({
4521
4550
  encryptConfig,
@@ -4527,6 +4556,15 @@ var Encryption = async (config3) => {
4527
4556
  return result.data;
4528
4557
  };
4529
4558
 
4559
+ // src/utils/config/index.ts
4560
+ function extractWorkspaceIdFromCrn(crn) {
4561
+ const match = crn.match(/crn:[^:]+:([^:]+)$/);
4562
+ if (!match) {
4563
+ throw new Error("Invalid CRN format");
4564
+ }
4565
+ return match[1];
4566
+ }
4567
+
4530
4568
  // src/secrets/index.ts
4531
4569
  var Secrets = class {
4532
4570
  encryptionClient = null;
@@ -4589,14 +4627,14 @@ var Secrets = class {
4589
4627
  * For GET requests, `params` are appended as URL query parameters.
4590
4628
  * For POST requests, `body` is sent as JSON in the request body.
4591
4629
  */
4592
- async apiRequest(method, path2, options) {
4630
+ async apiRequest(method, path, options) {
4593
4631
  try {
4594
- let url = `${this.apiBaseUrl}${path2}`;
4632
+ let url = `${this.apiBaseUrl}${path}`;
4595
4633
  if (options?.params) {
4596
4634
  const searchParams = new URLSearchParams(options.params);
4597
4635
  url = `${url}?${searchParams.toString()}`;
4598
4636
  }
4599
- logger.debug(`Secrets API request: ${method} ${path2}`);
4637
+ logger.debug(`Secrets API request: ${method} ${path}`);
4600
4638
  const headers = {
4601
4639
  "Content-Type": "application/json",
4602
4640
  Authorization: this.getAuthHeader()
@@ -4615,7 +4653,7 @@ var Secrets = class {
4615
4653
  } catch {
4616
4654
  errorMessage = errorText || errorMessage;
4617
4655
  }
4618
- logger.error(`Secrets API error on ${method} ${path2}: ${errorMessage}`);
4656
+ logger.error(`Secrets API error on ${method} ${path}: ${errorMessage}`);
4619
4657
  return {
4620
4658
  failure: {
4621
4659
  type: "ApiError",
@@ -4623,12 +4661,12 @@ var Secrets = class {
4623
4661
  }
4624
4662
  };
4625
4663
  }
4626
- logger.debug(`Secrets API request successful: ${method} ${path2}`);
4664
+ logger.debug(`Secrets API request successful: ${method} ${path}`);
4627
4665
  const data = await response.json();
4628
4666
  return { data };
4629
4667
  } catch (error) {
4630
4668
  const message = error instanceof Error ? error.message : "Unknown network error occurred";
4631
- logger.error(`Secrets network error on ${method} ${path2}: ${message}`);
4669
+ logger.error(`Secrets network error on ${method} ${path}: ${message}`);
4632
4670
  return {
4633
4671
  failure: {
4634
4672
  type: "NetworkError",
@@ -5104,7 +5142,7 @@ function requireFlag(flags, name) {
5104
5142
  }
5105
5143
  return val;
5106
5144
  }
5107
- var HELP = `
5145
+ var HELP2 = `
5108
5146
  ${style.title("Usage:")} stash secrets <command> [options]
5109
5147
 
5110
5148
  ${style.title("Commands:")}
@@ -5131,7 +5169,7 @@ async function secretsCommand(args) {
5131
5169
  const subcommand = args[0];
5132
5170
  const rest = args.slice(1);
5133
5171
  if (!subcommand || subcommand === "--help" || subcommand === "-h") {
5134
- console.log(HELP);
5172
+ console.log(HELP2);
5135
5173
  return;
5136
5174
  }
5137
5175
  const flags = parseFlags(rest);
@@ -5181,7 +5219,7 @@ async function secretsCommand(args) {
5181
5219
  }
5182
5220
  default:
5183
5221
  console.error(style.error(`Unknown secrets command: ${subcommand}`));
5184
- console.log(HELP);
5222
+ console.log(HELP2);
5185
5223
  process.exit(1);
5186
5224
  }
5187
5225
  }
@@ -5192,13 +5230,14 @@ var __dirname = dirname2(fileURLToPath(import.meta.url));
5192
5230
  var pkg = JSON.parse(
5193
5231
  readFileSync(join(__dirname, "../../package.json"), "utf-8")
5194
5232
  );
5195
- var HELP2 = `
5233
+ var HELP3 = `
5196
5234
  CipherStash Stack CLI v${pkg.version}
5197
5235
 
5198
5236
  Usage: stash <command> [options]
5199
5237
 
5200
5238
  Commands:
5201
5239
  init Initialize CipherStash for your project
5240
+ auth Authenticate with CipherStash
5202
5241
  secrets Manage encrypted secrets
5203
5242
 
5204
5243
  Options:
@@ -5211,6 +5250,7 @@ Init Flags:
5211
5250
  Examples:
5212
5251
  stash init
5213
5252
  stash init --supabase
5253
+ stash auth login
5214
5254
  stash secrets set -n DATABASE_URL -V "postgres://..." -e production
5215
5255
  stash secrets get -n DATABASE_URL -e production
5216
5256
  stash secrets get-many -n DATABASE_URL,API_KEY -e production
@@ -5235,7 +5275,7 @@ function parseArgs(argv) {
5235
5275
  async function main() {
5236
5276
  const { command, rest, booleanFlags } = parseArgs(process.argv);
5237
5277
  if (!command || command === "--help" || command === "-h") {
5238
- console.log(HELP2);
5278
+ console.log(HELP3);
5239
5279
  return;
5240
5280
  }
5241
5281
  if (command === "--version" || command === "-v") {
@@ -5246,13 +5286,16 @@ async function main() {
5246
5286
  case "init":
5247
5287
  await initCommand(booleanFlags);
5248
5288
  break;
5289
+ case "auth":
5290
+ await authCommand(rest);
5291
+ break;
5249
5292
  case "secrets":
5250
5293
  await secretsCommand(rest);
5251
5294
  break;
5252
5295
  default:
5253
5296
  console.error(`Unknown command: ${command}
5254
5297
  `);
5255
- console.log(HELP2);
5298
+ console.log(HELP3);
5256
5299
  process.exit(1);
5257
5300
  }
5258
5301
  }