@cipherstash/stack 0.11.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.
- package/CHANGELOG.md +6 -0
- package/README.md +38 -18
- package/dist/bin/stash.js +111 -14
- package/dist/bin/stash.js.map +1 -1
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
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
|
-
//
|
|
61
|
+
// Define a schema
|
|
52
62
|
const users = encryptedTable("users", {
|
|
53
63
|
email: encryptedColumn("email").equality().freeTextSearch(),
|
|
54
64
|
})
|
|
55
65
|
|
|
56
|
-
//
|
|
66
|
+
// Create a client
|
|
57
67
|
const client = await Encryption({ schemas: [users] })
|
|
58
68
|
|
|
59
|
-
//
|
|
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
|
-
//
|
|
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.
|
|
444
|
-
2.
|
|
445
|
-
3.
|
|
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`,
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
@@ -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,
|
|
@@ -5050,7 +5142,7 @@ function requireFlag(flags, name) {
|
|
|
5050
5142
|
}
|
|
5051
5143
|
return val;
|
|
5052
5144
|
}
|
|
5053
|
-
var
|
|
5145
|
+
var HELP2 = `
|
|
5054
5146
|
${style.title("Usage:")} stash secrets <command> [options]
|
|
5055
5147
|
|
|
5056
5148
|
${style.title("Commands:")}
|
|
@@ -5077,7 +5169,7 @@ async function secretsCommand(args) {
|
|
|
5077
5169
|
const subcommand = args[0];
|
|
5078
5170
|
const rest = args.slice(1);
|
|
5079
5171
|
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
5080
|
-
console.log(
|
|
5172
|
+
console.log(HELP2);
|
|
5081
5173
|
return;
|
|
5082
5174
|
}
|
|
5083
5175
|
const flags = parseFlags(rest);
|
|
@@ -5127,7 +5219,7 @@ async function secretsCommand(args) {
|
|
|
5127
5219
|
}
|
|
5128
5220
|
default:
|
|
5129
5221
|
console.error(style.error(`Unknown secrets command: ${subcommand}`));
|
|
5130
|
-
console.log(
|
|
5222
|
+
console.log(HELP2);
|
|
5131
5223
|
process.exit(1);
|
|
5132
5224
|
}
|
|
5133
5225
|
}
|
|
@@ -5138,13 +5230,14 @@ var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
|
5138
5230
|
var pkg = JSON.parse(
|
|
5139
5231
|
readFileSync(join(__dirname, "../../package.json"), "utf-8")
|
|
5140
5232
|
);
|
|
5141
|
-
var
|
|
5233
|
+
var HELP3 = `
|
|
5142
5234
|
CipherStash Stack CLI v${pkg.version}
|
|
5143
5235
|
|
|
5144
5236
|
Usage: stash <command> [options]
|
|
5145
5237
|
|
|
5146
5238
|
Commands:
|
|
5147
5239
|
init Initialize CipherStash for your project
|
|
5240
|
+
auth Authenticate with CipherStash
|
|
5148
5241
|
secrets Manage encrypted secrets
|
|
5149
5242
|
|
|
5150
5243
|
Options:
|
|
@@ -5157,6 +5250,7 @@ Init Flags:
|
|
|
5157
5250
|
Examples:
|
|
5158
5251
|
stash init
|
|
5159
5252
|
stash init --supabase
|
|
5253
|
+
stash auth login
|
|
5160
5254
|
stash secrets set -n DATABASE_URL -V "postgres://..." -e production
|
|
5161
5255
|
stash secrets get -n DATABASE_URL -e production
|
|
5162
5256
|
stash secrets get-many -n DATABASE_URL,API_KEY -e production
|
|
@@ -5181,7 +5275,7 @@ function parseArgs(argv) {
|
|
|
5181
5275
|
async function main() {
|
|
5182
5276
|
const { command, rest, booleanFlags } = parseArgs(process.argv);
|
|
5183
5277
|
if (!command || command === "--help" || command === "-h") {
|
|
5184
|
-
console.log(
|
|
5278
|
+
console.log(HELP3);
|
|
5185
5279
|
return;
|
|
5186
5280
|
}
|
|
5187
5281
|
if (command === "--version" || command === "-v") {
|
|
@@ -5192,13 +5286,16 @@ async function main() {
|
|
|
5192
5286
|
case "init":
|
|
5193
5287
|
await initCommand(booleanFlags);
|
|
5194
5288
|
break;
|
|
5289
|
+
case "auth":
|
|
5290
|
+
await authCommand(rest);
|
|
5291
|
+
break;
|
|
5195
5292
|
case "secrets":
|
|
5196
5293
|
await secretsCommand(rest);
|
|
5197
5294
|
break;
|
|
5198
5295
|
default:
|
|
5199
5296
|
console.error(`Unknown command: ${command}
|
|
5200
5297
|
`);
|
|
5201
|
-
console.log(
|
|
5298
|
+
console.log(HELP3);
|
|
5202
5299
|
process.exit(1);
|
|
5203
5300
|
}
|
|
5204
5301
|
}
|