@zincapp/znvault-cli 4.7.1 → 4.9.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 (47) hide show
  1. package/dist/commands/apikey/conditions.d.ts.map +1 -1
  2. package/dist/commands/apikey/conditions.js +1 -0
  3. package/dist/commands/apikey/conditions.js.map +1 -1
  4. package/dist/commands/apikey/create.d.ts.map +1 -1
  5. package/dist/commands/apikey/create.js +1 -0
  6. package/dist/commands/apikey/create.js.map +1 -1
  7. package/dist/commands/apikey/helpers.d.ts +1 -0
  8. package/dist/commands/apikey/helpers.d.ts.map +1 -1
  9. package/dist/commands/apikey/helpers.js +8 -0
  10. package/dist/commands/apikey/helpers.js.map +1 -1
  11. package/dist/commands/apikey/types.d.ts +3 -0
  12. package/dist/commands/apikey/types.d.ts.map +1 -1
  13. package/dist/commands/dynamic-secrets/types.d.ts +3 -0
  14. package/dist/commands/dynamic-secrets/types.d.ts.map +1 -1
  15. package/dist/commands/mysql/alias.d.ts +26 -0
  16. package/dist/commands/mysql/alias.d.ts.map +1 -0
  17. package/dist/commands/mysql/alias.js +56 -0
  18. package/dist/commands/mysql/alias.js.map +1 -0
  19. package/dist/commands/mysql/broker.d.ts +38 -0
  20. package/dist/commands/mysql/broker.d.ts.map +1 -0
  21. package/dist/commands/mysql/broker.js +171 -0
  22. package/dist/commands/mysql/broker.js.map +1 -0
  23. package/dist/commands/mysql/index.d.ts +33 -0
  24. package/dist/commands/mysql/index.d.ts.map +1 -0
  25. package/dist/commands/mysql/index.js +207 -0
  26. package/dist/commands/mysql/index.js.map +1 -0
  27. package/dist/commands/mysql/mycnf.d.ts +40 -0
  28. package/dist/commands/mysql/mycnf.d.ts.map +1 -0
  29. package/dist/commands/mysql/mycnf.js +136 -0
  30. package/dist/commands/mysql/mycnf.js.map +1 -0
  31. package/dist/commands/mysql/resolve.d.ts +18 -0
  32. package/dist/commands/mysql/resolve.d.ts.map +1 -0
  33. package/dist/commands/mysql/resolve.js +108 -0
  34. package/dist/commands/mysql/resolve.js.map +1 -0
  35. package/dist/commands/mysql/run.d.ts +156 -0
  36. package/dist/commands/mysql/run.d.ts.map +1 -0
  37. package/dist/commands/mysql/run.js +300 -0
  38. package/dist/commands/mysql/run.js.map +1 -0
  39. package/dist/commands/mysql/types.d.ts +29 -0
  40. package/dist/commands/mysql/types.d.ts.map +1 -0
  41. package/dist/commands/mysql/types.js +7 -0
  42. package/dist/commands/mysql/types.js.map +1 -0
  43. package/dist/index.js +2 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/lib/config/types.d.ts +4 -0
  46. package/dist/lib/config/types.d.ts.map +1 -1
  47. package/package.json +1 -1
@@ -0,0 +1,207 @@
1
+ // src/commands/mysql/index.ts
2
+ import * as output from '../../lib/output.js';
3
+ import { resolveTarget } from './resolve.js';
4
+ import { runBrokered } from './broker.js';
5
+ import { assertMysqlOnPath, runMysql } from './run.js';
6
+ import { addAlias, listAliases, removeAlias } from './alias.js';
7
+ /**
8
+ * Collect helper for Commander repeatable options (--file <path> ... --file <path>).
9
+ * Each invocation appends the new value to the accumulator array.
10
+ */
11
+ function collect(value, previous) {
12
+ return [...previous, value];
13
+ }
14
+ /**
15
+ * Parse and validate the `--ttl <seconds>` option (M-2).
16
+ *
17
+ * `parseInt('abc', 10)` is `NaN`, and `NaN ?? 600` stays `NaN` — which would be
18
+ * sent to the server as the requested TTL and produce a confusing bad request.
19
+ * Validate locally and fail fast with a clear message BEFORE any lease is
20
+ * generated.
21
+ *
22
+ * @param raw The raw `--ttl` option value, or undefined when not provided.
23
+ * @returns The parsed positive integer, or undefined when `--ttl` is absent
24
+ * (so the server/role default applies).
25
+ * @throws If `--ttl` is provided but is not a positive integer.
26
+ */
27
+ export function parseTtlSeconds(raw) {
28
+ if (raw === undefined)
29
+ return undefined;
30
+ const ttl = Number(raw);
31
+ if (!Number.isInteger(ttl) || ttl <= 0) {
32
+ throw new Error(`Invalid --ttl '${raw}': must be a positive integer number of seconds.`);
33
+ }
34
+ return ttl;
35
+ }
36
+ /**
37
+ * Register the `mysql` command group and all subcommands on `program`.
38
+ */
39
+ export function registerMysqlCommands(program) {
40
+ const mysql = program
41
+ .command('mysql')
42
+ // enablePositionalOptions() lets the variadic [mysqlArgs...] coexist cleanly
43
+ // with named options — keeps '--' separator forwarding well-defined.
44
+ .enablePositionalOptions()
45
+ .description('Connect to MySQL databases via short-lived dynamic-secret credentials')
46
+ .addHelpText('after', `
47
+ Examples:
48
+ # Interactive shell (connect mode)
49
+ znvault mysql connect my-connection
50
+
51
+ # Execute SQL from a file
52
+ znvault mysql exec my-connection --file schema.sql
53
+
54
+ # Execute inline SQL
55
+ znvault mysql exec my-connection --sql "SELECT 1"
56
+
57
+ # Pipe SQL from stdin
58
+ echo "SELECT version()" | znvault mysql exec my-connection
59
+
60
+ # Use an alias (see: znvault mysql alias add)
61
+ znvault mysql connect staging-rw
62
+
63
+ # Save an alias for quick access
64
+ znvault mysql alias add staging-rw --connection staging-mysql --role app-rw
65
+
66
+ # Pass extra mysql flags after --
67
+ znvault mysql connect my-connection -- --table
68
+ `);
69
+ // ── exec ─────────────────────────────────────────────────────────────────────
70
+ //
71
+ // Using `exec <target> [mysqlArgs...]` (variadic) instead of passThroughOptions()
72
+ // so that named options (--role, --sql, etc.) can appear AFTER the positional
73
+ // <target> argument without being misread as excess positional args.
74
+ //
75
+ // With passThroughOptions(), Commander stops option-parsing at the first
76
+ // non-option token (the target), so `exec staging-mysql --role app-rw` was
77
+ // rejected as "too many arguments" — the target consumed the stop-point and
78
+ // --role/--sql were treated as excess positional args.
79
+ //
80
+ // The variadic approach: Commander continues parsing named options across the
81
+ // whole argv, and any unrecognised tokens (or tokens after `--`) land in the
82
+ // mysqlArgs array instead of causing an error. The `--` separator itself is
83
+ // NOT included in mysqlArgs — Commander strips it automatically.
84
+ mysql
85
+ .command('exec <target> [mysqlArgs...]')
86
+ .description('Execute SQL against a MySQL database via a short-lived credential')
87
+ .option('--role <name>', 'Dynamic-secrets role name or ID')
88
+ .option('--file <path>', 'SQL file to execute (repeatable; concatenated in order)', collect, [])
89
+ .option('--sql <sql>', 'Inline SQL to execute')
90
+ .option('--ttl <seconds>', `Requested lease TTL in seconds (default: 600; capped by role maxTtl)`)
91
+ .option('--database <db>', 'Database/schema to select (overrides the credential default)')
92
+ .action(async (target, mysqlArgs, opts) => {
93
+ // mysqlArgs contains any extra tokens (e.g. post-`--` flags like --batch).
94
+ // Commander excludes the `--` separator itself from this array.
95
+ const passthrough = mysqlArgs;
96
+ try {
97
+ // Fail fast: check mysql binary before generating any lease.
98
+ assertMysqlOnPath();
99
+ // Validate --ttl locally before generating any lease (M-2).
100
+ const ttlSeconds = parseTtlSeconds(opts.ttl);
101
+ const { roleId } = await resolveTarget(target, opts.role);
102
+ const code = await runBrokered({
103
+ roleId,
104
+ ttlSeconds,
105
+ run: ({ credential, fd, fdPath }) => runMysql({
106
+ fd,
107
+ fdPath,
108
+ database: opts.database ?? credential.database,
109
+ mode: 'exec',
110
+ files: opts.file,
111
+ sql: opts.sql,
112
+ passthrough,
113
+ }),
114
+ });
115
+ process.exit(code);
116
+ }
117
+ catch (err) {
118
+ output.error(err instanceof Error ? err.message : String(err));
119
+ process.exit(1);
120
+ }
121
+ });
122
+ // ── connect ───────────────────────────────────────────────────────────────────
123
+ // Same variadic idiom as exec — see exec comment above.
124
+ mysql
125
+ .command('connect <target> [mysqlArgs...]')
126
+ .description('Open an interactive MySQL shell via a short-lived credential')
127
+ .option('--role <name>', 'Dynamic-secrets role name or ID')
128
+ .option('--ttl <seconds>', `Requested lease TTL in seconds (default: 600; capped by role maxTtl)`)
129
+ .option('--database <db>', 'Database/schema to select (overrides the credential default)')
130
+ .action(async (target, mysqlArgs, opts) => {
131
+ const passthrough = mysqlArgs;
132
+ try {
133
+ // Fail fast: check mysql binary before generating any lease.
134
+ assertMysqlOnPath();
135
+ // Validate --ttl locally before generating any lease (M-2).
136
+ const ttlSeconds = parseTtlSeconds(opts.ttl);
137
+ const { roleId } = await resolveTarget(target, opts.role);
138
+ const code = await runBrokered({
139
+ roleId,
140
+ ttlSeconds,
141
+ run: ({ credential, fd, fdPath }) => runMysql({
142
+ fd,
143
+ fdPath,
144
+ database: opts.database ?? credential.database,
145
+ mode: 'connect',
146
+ passthrough,
147
+ }),
148
+ });
149
+ process.exit(code);
150
+ }
151
+ catch (err) {
152
+ output.error(err instanceof Error ? err.message : String(err));
153
+ process.exit(1);
154
+ }
155
+ });
156
+ // ── alias ─────────────────────────────────────────────────────────────────────
157
+ const alias = mysql
158
+ .command('alias')
159
+ .description('Manage MySQL connection aliases for quick access');
160
+ alias
161
+ .command('add <name>')
162
+ .description('Add or overwrite a MySQL connection alias')
163
+ .requiredOption('--connection <connection>', 'Connection name or ID to bind')
164
+ .requiredOption('--role <role>', 'Role name or ID to bind')
165
+ .action((name, opts) => {
166
+ try {
167
+ addAlias(name, opts.connection, opts.role);
168
+ output.success(`Alias '${name}' saved → ${opts.connection} / ${opts.role}`);
169
+ }
170
+ catch (err) {
171
+ output.error(err instanceof Error ? err.message : String(err));
172
+ process.exit(1);
173
+ }
174
+ });
175
+ alias
176
+ .command('list')
177
+ .alias('ls')
178
+ .description('List all MySQL aliases in the active profile')
179
+ .action(() => {
180
+ try {
181
+ const aliases = listAliases();
182
+ if (aliases.length === 0) {
183
+ output.info('No aliases defined. Use: znvault mysql alias add <name> --connection <c> --role <r>');
184
+ return;
185
+ }
186
+ output.table(['Name', 'Connection', 'Role'], aliases.map(({ name, connection, role }) => [name, connection, role]));
187
+ }
188
+ catch (err) {
189
+ output.error(err instanceof Error ? err.message : String(err));
190
+ process.exit(1);
191
+ }
192
+ });
193
+ alias
194
+ .command('rm <name>')
195
+ .description('Remove a MySQL alias from the active profile')
196
+ .action((name) => {
197
+ try {
198
+ removeAlias(name);
199
+ output.success(`Alias '${name}' removed`);
200
+ }
201
+ catch (err) {
202
+ output.error(err instanceof Error ? err.message : String(err));
203
+ process.exit(1);
204
+ }
205
+ });
206
+ }
207
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/mysql/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The result of createMyCnf.
3
+ *
4
+ * - `fd`: the open file descriptor backing the (now unlinked) my.cnf. The
5
+ * child mysql inherits this fd at the SAME number and re-opens it
6
+ * via `fdPath`.
7
+ * - `fdPath`: `/dev/fd/<fd>` — pass this verbatim as the value of
8
+ * `--defaults-extra-file`. Works on macOS (/dev/fd) and Linux
9
+ * (/dev/fd → /proc/self/fd).
10
+ * - `cleanup`: idempotent, best-effort. Closes `fd` (releasing the inode so its
11
+ * bytes are reclaimed) and rmdir's the now-empty mem-fs dir.
12
+ */
13
+ export interface MyCnfHandle {
14
+ /** Open fd backing the unlinked cnf inode. Child inherits it at this number. */
15
+ fd: number;
16
+ /** `/dev/fd/<fd>` — the value to pass to --defaults-extra-file. */
17
+ fdPath: string;
18
+ /** Idempotent best-effort teardown: closeSync(fd) + rmdir(dir). */
19
+ cleanup: () => void;
20
+ }
21
+ /**
22
+ * Create the temp my.cnf, unlink its directory entry immediately, and return an
23
+ * open fd plus an idempotent cleanup().
24
+ *
25
+ * The body is fully synchronous (all fs operations are *Sync), but the function
26
+ * returns a Promise to preserve the broker's `await createMyCnf(...)` contract
27
+ * (and so it can become genuinely async later without touching callers).
28
+ * Declared non-`async` + returning Promise.resolve avoids the require-await
29
+ * lint warning while keeping the Promise return type (M-5).
30
+ *
31
+ * @throws If the exclusive create fails (e.g. EEXIST on a suffix collision,
32
+ * which is astronomically unlikely with 8 random bytes, or ENOSPC).
33
+ */
34
+ export declare function createMyCnf(opts: {
35
+ user: string;
36
+ password: string;
37
+ host: string;
38
+ port: number;
39
+ }): Promise<MyCnfHandle>;
40
+ //# sourceMappingURL=mycnf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mycnf.d.ts","sourceRoot":"","sources":["../../../src/commands/mysql/mycnf.ts"],"names":[],"mappings":"AA2CA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B,gFAAgF;IAChF,EAAE,EAAE,MAAM,CAAC;IACX,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAChC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;CAC5D,GAAG,OAAO,CAAC,WAAW,CAAC,CA8DvB"}
@@ -0,0 +1,136 @@
1
+ // src/commands/mysql/mycnf.ts
2
+ //
3
+ // Writes the short-lived 0600 my.cnf that carries the leased MySQL credentials
4
+ // (spec B2) and hands it to the child mysql as an OPEN FILE DESCRIPTOR rather
5
+ // than a path on disk.
6
+ //
7
+ // F1 (no plaintext directory entry survives the run) is achieved by the
8
+ // open → write → unlink-immediately pattern:
9
+ // 1. openSync(path, 'wx+', 0600) → exclusive create, returns fd.
10
+ // 2. writeSync(fd, body) → credentials written through the fd.
11
+ // 3. unlinkSync(path) → directory entry removed AT ONCE. The
12
+ // inode (and its plaintext bytes) stays alive ONLY because the open fd
13
+ // still references it; there is NO name in the filesystem from this point
14
+ // on, so a `kill -9` or crash leaves nothing on disk to recover.
15
+ // 4. runMysql passes `/dev/fd/<fd>` as --defaults-extra-file and inherits the
16
+ // fd into the child at the SAME number, so mysql re-opens the still-alive
17
+ // inode through /dev/fd. (On macOS /dev/fd/N re-opens the inode — which is
18
+ // why the fd MUST be readable; see the 'wx+' note below. On Linux /dev/fd
19
+ // is /proc/self/fd and resolves the same inode.)
20
+ // 5. cleanup() closes the fd → last reference gone → kernel reclaims the
21
+ // inode + its bytes. cleanup() is idempotent and best-effort.
22
+ //
23
+ // This replaces the old "spawn-then-unlink" approach, which raced: spawn()
24
+ // returns when the child is forked but BEFORE it has exec'd mysql and read the
25
+ // defaults file, so the unlink could win the race and mysql would die with
26
+ // "Failed to open required defaults file". Keeping the inode alive via an open
27
+ // fd removes the race entirely — the name is already gone before spawn, and the
28
+ // fd guarantees the bytes survive until cleanup().
29
+ import * as fs from 'node:fs';
30
+ import * as os from 'node:os';
31
+ import * as path from 'node:path';
32
+ import { randomBytes } from 'node:crypto';
33
+ function memBackedTmpBase() {
34
+ // Prefer a memory-backed fs so the plaintext never hits spinning disk (spec F1).
35
+ try {
36
+ fs.accessSync('/dev/shm', fs.constants.W_OK);
37
+ return '/dev/shm';
38
+ }
39
+ catch {
40
+ return os.tmpdir();
41
+ }
42
+ }
43
+ /**
44
+ * Create the temp my.cnf, unlink its directory entry immediately, and return an
45
+ * open fd plus an idempotent cleanup().
46
+ *
47
+ * The body is fully synchronous (all fs operations are *Sync), but the function
48
+ * returns a Promise to preserve the broker's `await createMyCnf(...)` contract
49
+ * (and so it can become genuinely async later without touching callers).
50
+ * Declared non-`async` + returning Promise.resolve avoids the require-await
51
+ * lint warning while keeping the Promise return type (M-5).
52
+ *
53
+ * @throws If the exclusive create fails (e.g. EEXIST on a suffix collision,
54
+ * which is astronomically unlikely with 8 random bytes, or ENOSPC).
55
+ */
56
+ export function createMyCnf(opts) {
57
+ const suffix = randomBytes(8).toString('hex');
58
+ const dir = path.join(memBackedTmpBase(), `znvault-my-${suffix}`);
59
+ // 0700 dir on a memory-backed fs (spec F1). Created before the file so the
60
+ // file's parent is owner-only even for the brief moment the name exists.
61
+ fs.mkdirSync(dir, { mode: 0o700 });
62
+ const file = path.join(dir, 'my.cnf');
63
+ const body = `[client]\nuser=${opts.user}\npassword=${opts.password}\nhost=${opts.host}\nport=${opts.port}\n`;
64
+ // 'wx+' = O_RDWR | O_CREAT | O_EXCL, mode 0600.
65
+ // - O_EXCL : fail if the file somehow already exists (no clobber / no
66
+ // following an attacker-planted symlink).
67
+ // - O_RDWR : the fd MUST be READABLE. On macOS, `/dev/fd/N` RE-OPENS the
68
+ // underlying inode (it is not a plain dup of the open file
69
+ // description), so a write-only ('wx') fd would make mysql fail
70
+ // with EBADF/permission when it tries to read /dev/fd/N. O_RDWR
71
+ // keeps the inode re-openable for read by the child. Verified
72
+ // against real mysql 9.4 on macOS.
73
+ let fd;
74
+ try {
75
+ fd = fs.openSync(file, 'wx+', 0o600);
76
+ }
77
+ catch (err) {
78
+ // mkdir succeeded but open failed — drop the empty dir so we don't leak it.
79
+ try {
80
+ fs.rmdirSync(dir);
81
+ }
82
+ catch { /* ignore */ }
83
+ throw err;
84
+ }
85
+ let closed = false;
86
+ try {
87
+ // CRITICAL: write at an EXPLICIT position 0 (the 5-arg overload) so the fd's
88
+ // current file OFFSET stays at 0. On macOS, `/dev/fd/N` does NOT give the
89
+ // child a fresh offset-0 description — it shares the original fd's offset.
90
+ // A plain `writeSync(fd, body)` advances the offset to EOF, so mysql reading
91
+ // /dev/fd/N would start at EOF and parse an EMPTY defaults file (verified:
92
+ // `mysql --print-defaults` shows zero args). Positioned writes do not move
93
+ // the file pointer, so the offset remains 0 and mysql reads the whole body.
94
+ const bodyBuf = Buffer.from(body, 'utf8');
95
+ fs.writeSync(fd, bodyBuf, 0, bodyBuf.length, 0);
96
+ // F1: remove the directory entry IMMEDIATELY. The inode stays alive purely
97
+ // because `fd` is still open; there is no name on disk from here on.
98
+ fs.unlinkSync(file);
99
+ }
100
+ catch (err) {
101
+ // Writing or unlinking failed — don't leak the fd or the dir.
102
+ try {
103
+ fs.closeSync(fd);
104
+ closed = true;
105
+ }
106
+ catch { /* ignore */ }
107
+ try {
108
+ fs.unlinkSync(file);
109
+ }
110
+ catch { /* ignore */ }
111
+ try {
112
+ fs.rmdirSync(dir);
113
+ }
114
+ catch { /* ignore */ }
115
+ throw err;
116
+ }
117
+ const cleanup = () => {
118
+ // Closing the last fd referencing the (already unlinked) inode releases it,
119
+ // so the kernel reclaims the plaintext bytes. Idempotent via `closed`.
120
+ if (!closed) {
121
+ try {
122
+ fs.closeSync(fd);
123
+ }
124
+ catch { /* ignore */ }
125
+ closed = true;
126
+ }
127
+ // The file name is already gone; the dir should be empty. rmdir is
128
+ // best-effort (it may already be gone if cleanup ran twice).
129
+ try {
130
+ fs.rmdirSync(dir);
131
+ }
132
+ catch { /* ignore */ }
133
+ };
134
+ return Promise.resolve({ fd, fdPath: `/dev/fd/${fd.toString()}`, cleanup });
135
+ }
136
+ //# sourceMappingURL=mycnf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mycnf.js","sourceRoot":"","sources":["../../../src/commands/mysql/mycnf.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Resolve `target` (a connection name/id OR an alias) plus an optional role
3
+ * name/id into concrete IDs.
4
+ *
5
+ * Resolution rules:
6
+ * 1. If `target` matches a saved alias, expand to { connection, role }.
7
+ * Validate both still exist; if not, throw a "dangling alias" error (F13).
8
+ * 2. Otherwise treat `target` as a connection name/id and fetch it.
9
+ * 3. Resolve the role:
10
+ * - If a role name/id is given, find it in the connection's role list.
11
+ * - If no role given and the connection has exactly one role, use it.
12
+ * - Otherwise throw an error instructing the user to pass --role.
13
+ */
14
+ export declare function resolveTarget(target: string, roleOpt?: string): Promise<{
15
+ connectionId: string;
16
+ roleId: string;
17
+ }>;
18
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../src/commands/mysql/resolve.ts"],"names":[],"mappings":"AA8DA;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA+DnD"}
@@ -0,0 +1,108 @@
1
+ // src/commands/mysql/resolve.ts
2
+ /**
3
+ * Resolve a target (connection name/id or alias) + optional role to concrete IDs.
4
+ *
5
+ * This is used by `znvault mysql exec/connect` to turn the user-supplied target
6
+ * and --role option into the { connectionId, roleId } pair needed by the broker.
7
+ */
8
+ import { client } from '../../lib/client.js';
9
+ import { getAlias } from './alias.js';
10
+ /**
11
+ * Resolve a connection name or id to a concrete connection id.
12
+ *
13
+ * Strategy (avoids a guaranteed 404 round-trip on the common name case):
14
+ * - If `target` looks like a connection id (starts with "dbc_"), try GET by id
15
+ * first; if that 404s, fall back to listing and matching by name.
16
+ * - Otherwise (target is a friendly name), list all connections and match by
17
+ * name first; if not found, try GET by id as a last resort.
18
+ * - If neither resolves, throw a clear "not found (by id or name)" error.
19
+ *
20
+ * Connection names are unique per tenant, so a name match is unambiguous.
21
+ * If somehow multiple entries share a name, the first match is used.
22
+ */
23
+ async function resolveConnectionId(target) {
24
+ const looksLikeId = target.startsWith('dbc_');
25
+ if (looksLikeId) {
26
+ // Try direct GET by id first.
27
+ try {
28
+ const conn = await client.get(`/v1/dynamic-secrets/connections/${target}`);
29
+ return conn.id;
30
+ }
31
+ catch {
32
+ // Fall through to list-by-name.
33
+ }
34
+ }
35
+ // List all connections and match by name.
36
+ const connections = await client.get('/v1/dynamic-secrets/connections');
37
+ const byName = connections.find((c) => c.name === target);
38
+ if (byName !== undefined) {
39
+ return byName.id;
40
+ }
41
+ if (!looksLikeId) {
42
+ // Not found by name; try GET by id as a last resort.
43
+ try {
44
+ const conn = await client.get(`/v1/dynamic-secrets/connections/${target}`);
45
+ return conn.id;
46
+ }
47
+ catch {
48
+ // Fall through to error.
49
+ }
50
+ }
51
+ throw new Error(`Connection '${target}' not found (by id or name). ` +
52
+ `Run 'znvault dynamic-secrets connections list' to see available connections.`);
53
+ }
54
+ /**
55
+ * Resolve `target` (a connection name/id OR an alias) plus an optional role
56
+ * name/id into concrete IDs.
57
+ *
58
+ * Resolution rules:
59
+ * 1. If `target` matches a saved alias, expand to { connection, role }.
60
+ * Validate both still exist; if not, throw a "dangling alias" error (F13).
61
+ * 2. Otherwise treat `target` as a connection name/id and fetch it.
62
+ * 3. Resolve the role:
63
+ * - If a role name/id is given, find it in the connection's role list.
64
+ * - If no role given and the connection has exactly one role, use it.
65
+ * - Otherwise throw an error instructing the user to pass --role.
66
+ */
67
+ export async function resolveTarget(target, roleOpt) {
68
+ const alias = getAlias(target);
69
+ if (alias !== undefined) {
70
+ // Alias path — validate that connection and role still exist.
71
+ const connectionTarget = alias.connection;
72
+ const roleTarget = alias.role;
73
+ let connectionId;
74
+ try {
75
+ connectionId = await resolveConnectionId(connectionTarget);
76
+ }
77
+ catch {
78
+ throw new Error(`Dangling alias '${target}': connection '${connectionTarget}' no longer exists`);
79
+ }
80
+ const roles = await client.get(`/v1/dynamic-secrets/connections/${connectionId}/roles`);
81
+ const role = roles.find((r) => r.name === roleTarget || r.id === roleTarget);
82
+ if (role === undefined) {
83
+ throw new Error(`Dangling alias '${target}': role '${roleTarget}' no longer exists on connection '${connectionTarget}'`);
84
+ }
85
+ return { connectionId, roleId: role.id };
86
+ }
87
+ // Direct connection path.
88
+ const connectionId = await resolveConnectionId(target);
89
+ const roles = await client.get(`/v1/dynamic-secrets/connections/${connectionId}/roles`);
90
+ if (roleOpt !== undefined) {
91
+ const role = roles.find((r) => r.name === roleOpt || r.id === roleOpt);
92
+ if (role === undefined) {
93
+ throw new Error(`Role '${roleOpt}' not found on connection '${target}'. ` +
94
+ `Available: ${roles.map((r) => r.name).join(', ') || '(none)'}`);
95
+ }
96
+ return { connectionId, roleId: role.id };
97
+ }
98
+ // No role given — require exactly one.
99
+ if (roles.length === 1) {
100
+ return { connectionId, roleId: roles[0].id };
101
+ }
102
+ if (roles.length === 0) {
103
+ throw new Error(`Connection '${target}' has no roles. Create one first, then pass --role <name>.`);
104
+ }
105
+ throw new Error(`Connection '${target}' has ${roles.length.toString()} roles. ` +
106
+ `Pass --role <name> to select one: ${roles.map((r) => r.name).join(', ')}`);
107
+ }
108
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/commands/mysql/resolve.ts"],"names":[],"mappings":"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"}