@zincapp/znvault-cli 2.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 (122) hide show
  1. package/README.md +310 -0
  2. package/dist/commands/agent.d.ts +3 -0
  3. package/dist/commands/agent.d.ts.map +1 -0
  4. package/dist/commands/agent.js +660 -0
  5. package/dist/commands/agent.js.map +1 -0
  6. package/dist/commands/apikey.d.ts +3 -0
  7. package/dist/commands/apikey.d.ts.map +1 -0
  8. package/dist/commands/apikey.js +767 -0
  9. package/dist/commands/apikey.js.map +1 -0
  10. package/dist/commands/audit.d.ts +3 -0
  11. package/dist/commands/audit.d.ts.map +1 -0
  12. package/dist/commands/audit.js +147 -0
  13. package/dist/commands/audit.js.map +1 -0
  14. package/dist/commands/auth.d.ts +3 -0
  15. package/dist/commands/auth.d.ts.map +1 -0
  16. package/dist/commands/auth.js +426 -0
  17. package/dist/commands/auth.js.map +1 -0
  18. package/dist/commands/cert.d.ts +3 -0
  19. package/dist/commands/cert.d.ts.map +1 -0
  20. package/dist/commands/cert.js +398 -0
  21. package/dist/commands/cert.js.map +1 -0
  22. package/dist/commands/cluster.d.ts +3 -0
  23. package/dist/commands/cluster.d.ts.map +1 -0
  24. package/dist/commands/cluster.js +228 -0
  25. package/dist/commands/cluster.js.map +1 -0
  26. package/dist/commands/emergency.d.ts +3 -0
  27. package/dist/commands/emergency.d.ts.map +1 -0
  28. package/dist/commands/emergency.js +223 -0
  29. package/dist/commands/emergency.js.map +1 -0
  30. package/dist/commands/health.d.ts +3 -0
  31. package/dist/commands/health.d.ts.map +1 -0
  32. package/dist/commands/health.js +188 -0
  33. package/dist/commands/health.js.map +1 -0
  34. package/dist/commands/lockdown.d.ts +3 -0
  35. package/dist/commands/lockdown.d.ts.map +1 -0
  36. package/dist/commands/lockdown.js +232 -0
  37. package/dist/commands/lockdown.js.map +1 -0
  38. package/dist/commands/permissions.d.ts +3 -0
  39. package/dist/commands/permissions.d.ts.map +1 -0
  40. package/dist/commands/permissions.js +168 -0
  41. package/dist/commands/permissions.js.map +1 -0
  42. package/dist/commands/policy.d.ts +3 -0
  43. package/dist/commands/policy.d.ts.map +1 -0
  44. package/dist/commands/policy.js +660 -0
  45. package/dist/commands/policy.js.map +1 -0
  46. package/dist/commands/superadmin.d.ts +3 -0
  47. package/dist/commands/superadmin.d.ts.map +1 -0
  48. package/dist/commands/superadmin.js +203 -0
  49. package/dist/commands/superadmin.js.map +1 -0
  50. package/dist/commands/tenant.d.ts +3 -0
  51. package/dist/commands/tenant.d.ts.map +1 -0
  52. package/dist/commands/tenant.js +277 -0
  53. package/dist/commands/tenant.js.map +1 -0
  54. package/dist/commands/update.d.ts +9 -0
  55. package/dist/commands/update.d.ts.map +1 -0
  56. package/dist/commands/update.js +359 -0
  57. package/dist/commands/update.js.map +1 -0
  58. package/dist/commands/user.d.ts +3 -0
  59. package/dist/commands/user.d.ts.map +1 -0
  60. package/dist/commands/user.js +363 -0
  61. package/dist/commands/user.js.map +1 -0
  62. package/dist/index.d.ts +3 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +82 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/lib/client.d.ts +246 -0
  67. package/dist/lib/client.d.ts.map +1 -0
  68. package/dist/lib/client.js +734 -0
  69. package/dist/lib/client.js.map +1 -0
  70. package/dist/lib/config.d.ts +130 -0
  71. package/dist/lib/config.d.ts.map +1 -0
  72. package/dist/lib/config.js +342 -0
  73. package/dist/lib/config.js.map +1 -0
  74. package/dist/lib/db.d.ts +111 -0
  75. package/dist/lib/db.d.ts.map +1 -0
  76. package/dist/lib/db.js +698 -0
  77. package/dist/lib/db.js.map +1 -0
  78. package/dist/lib/local.d.ts +41 -0
  79. package/dist/lib/local.d.ts.map +1 -0
  80. package/dist/lib/local.js +236 -0
  81. package/dist/lib/local.js.map +1 -0
  82. package/dist/lib/mode.d.ts +210 -0
  83. package/dist/lib/mode.d.ts.map +1 -0
  84. package/dist/lib/mode.js +389 -0
  85. package/dist/lib/mode.js.map +1 -0
  86. package/dist/lib/output.d.ts +61 -0
  87. package/dist/lib/output.d.ts.map +1 -0
  88. package/dist/lib/output.js +190 -0
  89. package/dist/lib/output.js.map +1 -0
  90. package/dist/lib/prompts.d.ts +32 -0
  91. package/dist/lib/prompts.d.ts.map +1 -0
  92. package/dist/lib/prompts.js +96 -0
  93. package/dist/lib/prompts.js.map +1 -0
  94. package/dist/services/auto-update-daemon.d.ts +48 -0
  95. package/dist/services/auto-update-daemon.d.ts.map +1 -0
  96. package/dist/services/auto-update-daemon.js +296 -0
  97. package/dist/services/auto-update-daemon.js.map +1 -0
  98. package/dist/services/signature-verifier.d.ts +38 -0
  99. package/dist/services/signature-verifier.d.ts.map +1 -0
  100. package/dist/services/signature-verifier.js +209 -0
  101. package/dist/services/signature-verifier.js.map +1 -0
  102. package/dist/services/update-checker.d.ts +39 -0
  103. package/dist/services/update-checker.d.ts.map +1 -0
  104. package/dist/services/update-checker.js +198 -0
  105. package/dist/services/update-checker.js.map +1 -0
  106. package/dist/services/update-installer.d.ts +54 -0
  107. package/dist/services/update-installer.d.ts.map +1 -0
  108. package/dist/services/update-installer.js +360 -0
  109. package/dist/services/update-installer.js.map +1 -0
  110. package/dist/types/index.d.ts +411 -0
  111. package/dist/types/index.d.ts.map +1 -0
  112. package/dist/types/index.js +2 -0
  113. package/dist/types/index.js.map +1 -0
  114. package/dist/types/update.d.ts +137 -0
  115. package/dist/types/update.d.ts.map +1 -0
  116. package/dist/types/update.js +27 -0
  117. package/dist/types/update.js.map +1 -0
  118. package/dist/utils/platform.d.ts +35 -0
  119. package/dist/utils/platform.d.ts.map +1 -0
  120. package/dist/utils/platform.js +115 -0
  121. package/dist/utils/platform.js.map +1 -0
  122. package/package.json +59 -0
@@ -0,0 +1,767 @@
1
+ // Path: znvault-cli/src/commands/apikey.ts
2
+ // CLI commands for independent API key management
3
+ import ora from 'ora';
4
+ import Table from 'cli-table3';
5
+ import { client } from '../lib/client.js';
6
+ import * as output from '../lib/output.js';
7
+ function formatDate(dateStr) {
8
+ return new Date(dateStr).toLocaleString();
9
+ }
10
+ function getDaysUntilExpiry(expiresAt) {
11
+ const expires = new Date(expiresAt);
12
+ const now = new Date();
13
+ return Math.ceil((expires.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
14
+ }
15
+ function formatExpiry(expiresAt) {
16
+ const days = getDaysUntilExpiry(expiresAt);
17
+ if (days < 0)
18
+ return `Expired ${Math.abs(days)} days ago`;
19
+ if (days === 0)
20
+ return 'Expires today';
21
+ if (days === 1)
22
+ return 'Expires tomorrow';
23
+ if (days <= 7)
24
+ return `Expires in ${days} days (!)`;
25
+ if (days <= 30)
26
+ return `Expires in ${days} days`;
27
+ return `Expires in ${days} days`;
28
+ }
29
+ function formatPermissions(permissions) {
30
+ if (!permissions || permissions.length === 0)
31
+ return 'None';
32
+ if (permissions.length <= 3)
33
+ return permissions.join(', ');
34
+ return `${permissions.slice(0, 2).join(', ')} +${permissions.length - 2} more`;
35
+ }
36
+ function formatConditionsSummary(conditions) {
37
+ if (!conditions || Object.keys(conditions).length === 0)
38
+ return '-';
39
+ const parts = [];
40
+ if (conditions.ip)
41
+ parts.push('IP');
42
+ if (conditions.timeRange)
43
+ parts.push('Time');
44
+ if (conditions.methods)
45
+ parts.push('Methods');
46
+ if (conditions.resources)
47
+ parts.push('Resources');
48
+ if (conditions.aliases)
49
+ parts.push('Aliases');
50
+ if (conditions.resourceTags)
51
+ parts.push('Tags');
52
+ if (parts.length === 0)
53
+ return '-';
54
+ if (parts.length <= 2)
55
+ return parts.join(', ');
56
+ return `${parts.slice(0, 2).join(', ')} +${parts.length - 2}`;
57
+ }
58
+ export function registerApiKeyCommands(program) {
59
+ const apiKeyCmd = program
60
+ .command('apikey')
61
+ .alias('api-key')
62
+ .description('API key management (independent, tenant-scoped)');
63
+ // List API keys
64
+ apiKeyCmd
65
+ .command('list')
66
+ .alias('ls')
67
+ .description('List API keys')
68
+ .option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
69
+ .option('--json', 'Output as JSON')
70
+ .action(async (options) => {
71
+ const spinner = ora('Fetching API keys...').start();
72
+ try {
73
+ const result = await client.listApiKeys(options.tenant);
74
+ spinner.stop();
75
+ if (options.json) {
76
+ output.json(result);
77
+ return;
78
+ }
79
+ if (result.keys.length === 0) {
80
+ output.warn('No API keys found');
81
+ return;
82
+ }
83
+ // Show expiring soon warning
84
+ if (result.expiringSoon.length > 0) {
85
+ console.log(`\n⚠️ ${result.expiringSoon.length} key(s) expiring within 7 days\n`);
86
+ }
87
+ const table = new Table({
88
+ head: ['Name', 'Prefix', 'Status', 'Tenant', 'Permissions', 'Conditions', 'Expires', 'Rotations'],
89
+ style: { head: ['cyan'] },
90
+ });
91
+ for (const key of result.keys) {
92
+ const daysLeft = getDaysUntilExpiry(key.expires_at);
93
+ const expiryColor = daysLeft <= 7 ? '\x1b[31m' : daysLeft <= 30 ? '\x1b[33m' : '';
94
+ const reset = expiryColor ? '\x1b[0m' : '';
95
+ const statusIcon = key.enabled ? '\x1b[32m●\x1b[0m' : '\x1b[31m○\x1b[0m';
96
+ const statusText = key.enabled ? 'Active' : 'Disabled';
97
+ table.push([
98
+ key.name,
99
+ key.prefix,
100
+ `${statusIcon} ${statusText}`,
101
+ key.tenant_id,
102
+ formatPermissions(key.permissions),
103
+ formatConditionsSummary(key.conditions),
104
+ `${expiryColor}${formatExpiry(key.expires_at)}${reset}`,
105
+ key.rotation_count > 0 ? `${key.rotation_count}x` : '-',
106
+ ]);
107
+ }
108
+ console.log(table.toString());
109
+ console.log(`\nTotal: ${result.keys.length} API key(s)`);
110
+ }
111
+ catch (err) {
112
+ spinner.fail('Failed to list API keys');
113
+ output.error(err instanceof Error ? err.message : String(err));
114
+ process.exit(1);
115
+ }
116
+ });
117
+ // Create API key
118
+ apiKeyCmd
119
+ .command('create <name>')
120
+ .description('Create a new API key with direct permissions')
121
+ .option('-e, --expires <days>', 'Days until expiration (1-3650, default: 90)', '90')
122
+ .option('-p, --permissions <perms>', 'Comma-separated permissions (required)')
123
+ .option('-d, --description <desc>', 'Description')
124
+ .option('--ip <ips>', 'Comma-separated IP allowlist (CIDR supported)')
125
+ .option('--time-range <range>', 'Time range restriction: "HH:MM-HH:MM [TIMEZONE]"')
126
+ .option('--methods <methods>', 'Comma-separated allowed HTTP methods: GET,POST,etc')
127
+ .option('--resources <ids>', 'Specific resource IDs (type:id,...): secrets:id1,certificates:id2')
128
+ .option('--aliases <patterns>', 'Comma-separated alias patterns (glob): prod/*,api/*')
129
+ .option('--tags <tags>', 'Required resource tags: key=value,key2=value2')
130
+ .option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
131
+ .option('--json', 'Output as JSON')
132
+ .action(async (name, options) => {
133
+ // Validate permissions
134
+ if (!options.permissions) {
135
+ output.error('--permissions is required. Use comma-separated permission strings.');
136
+ output.info('Example: --permissions "secret:read:value,secret:list:values"');
137
+ process.exit(1);
138
+ }
139
+ const permissions = options.permissions.split(',').map((p) => p.trim());
140
+ const spinner = ora('Creating API key...').start();
141
+ try {
142
+ // Parse options
143
+ const expiresInDays = parseInt(options.expires, 10);
144
+ if (isNaN(expiresInDays) || expiresInDays < 1 || expiresInDays > 3650) {
145
+ spinner.fail('Invalid expiration');
146
+ output.error('Expiration must be between 1 and 3650 days');
147
+ process.exit(1);
148
+ }
149
+ let ipAllowlist;
150
+ if (options.ip) {
151
+ ipAllowlist = options.ip.split(',').map((ip) => ip.trim());
152
+ }
153
+ // Parse conditions
154
+ const conditions = {};
155
+ // IP condition (from --ip flag, now also stored in conditions)
156
+ if (ipAllowlist) {
157
+ conditions.ip = ipAllowlist;
158
+ }
159
+ // Time range condition
160
+ if (options.timeRange) {
161
+ const match = options.timeRange.match(/^(\d{2}:\d{2})-(\d{2}:\d{2})(?:\s+(.+))?$/);
162
+ if (!match) {
163
+ spinner.fail('Invalid time range format');
164
+ output.error('Use format: "HH:MM-HH:MM [TIMEZONE]"');
165
+ output.info('Example: --time-range "09:00-17:00 America/New_York"');
166
+ process.exit(1);
167
+ }
168
+ conditions.timeRange = {
169
+ start: match[1],
170
+ end: match[2],
171
+ timezone: match[3] || 'UTC',
172
+ };
173
+ }
174
+ // HTTP methods condition
175
+ if (options.methods) {
176
+ conditions.methods = options.methods.split(',').map((m) => m.trim().toUpperCase());
177
+ }
178
+ // Resource IDs condition
179
+ if (options.resources) {
180
+ const resources = {};
181
+ for (const part of options.resources.split(',')) {
182
+ const [type, id] = part.split(':');
183
+ if (type && id) {
184
+ if (!resources[type])
185
+ resources[type] = [];
186
+ resources[type].push(id);
187
+ }
188
+ }
189
+ if (Object.keys(resources).length > 0) {
190
+ conditions.resources = resources;
191
+ }
192
+ }
193
+ // Alias patterns condition
194
+ if (options.aliases) {
195
+ conditions.aliases = options.aliases.split(',').map((a) => a.trim());
196
+ }
197
+ // Resource tags condition
198
+ if (options.tags) {
199
+ const tags = {};
200
+ for (const part of options.tags.split(',')) {
201
+ const [key, value] = part.split('=');
202
+ if (key && value) {
203
+ tags[key.trim()] = value.trim();
204
+ }
205
+ }
206
+ if (Object.keys(tags).length > 0) {
207
+ conditions.resourceTags = tags;
208
+ }
209
+ }
210
+ const result = await client.createApiKey({
211
+ name,
212
+ description: options.description,
213
+ expiresInDays,
214
+ permissions,
215
+ ipAllowlist,
216
+ conditions: Object.keys(conditions).length > 0 ? conditions : undefined,
217
+ tenantId: options.tenant,
218
+ });
219
+ spinner.succeed('API key created');
220
+ if (options.json) {
221
+ output.json(result);
222
+ return;
223
+ }
224
+ console.log('\n⚠️ IMPORTANT: Save this key now - it will not be shown again!\n');
225
+ console.log('────────────────────────────────────────────────────────────────');
226
+ console.log(`API Key: ${result.key}`);
227
+ console.log('────────────────────────────────────────────────────────────────\n');
228
+ output.keyValue({
229
+ 'Key ID': result.apiKey.id,
230
+ 'Name': result.apiKey.name,
231
+ 'Prefix': result.apiKey.prefix,
232
+ 'Status': result.apiKey.enabled ? '\x1b[32m●\x1b[0m Active' : '\x1b[31m○\x1b[0m Disabled',
233
+ 'Tenant': result.apiKey.tenant_id,
234
+ 'Description': result.apiKey.description || 'None',
235
+ 'Expires': formatDate(result.apiKey.expires_at),
236
+ 'IP Allowlist': result.apiKey.ip_allowlist?.join(', ') || 'None',
237
+ });
238
+ if (result.apiKey.permissions.length > 0) {
239
+ console.log('\nPermissions:');
240
+ for (const perm of result.apiKey.permissions) {
241
+ console.log(` - ${perm}`);
242
+ }
243
+ }
244
+ // Display conditions if any
245
+ if (result.apiKey.conditions && Object.keys(result.apiKey.conditions).length > 0) {
246
+ console.log('\nConditions:');
247
+ const cond = result.apiKey.conditions;
248
+ if (cond.ip)
249
+ console.log(` - IP Allowlist: ${cond.ip.join(', ')}`);
250
+ if (cond.timeRange) {
251
+ const tr = cond.timeRange;
252
+ console.log(` - Time Range: ${tr.start}-${tr.end} ${tr.timezone || 'UTC'}`);
253
+ }
254
+ if (cond.methods)
255
+ console.log(` - Methods: ${cond.methods.join(', ')}`);
256
+ if (cond.resources)
257
+ console.log(` - Resources: ${JSON.stringify(cond.resources)}`);
258
+ if (cond.aliases)
259
+ console.log(` - Aliases: ${cond.aliases.join(', ')}`);
260
+ if (cond.resourceTags)
261
+ console.log(` - Tags: ${JSON.stringify(cond.resourceTags)}`);
262
+ }
263
+ }
264
+ catch (err) {
265
+ spinner.fail('Failed to create API key');
266
+ output.error(err instanceof Error ? err.message : String(err));
267
+ process.exit(1);
268
+ }
269
+ });
270
+ // Show API key details
271
+ apiKeyCmd
272
+ .command('show <id>')
273
+ .description('Show API key details')
274
+ .option('-t, --tenant <id>', 'Tenant ID')
275
+ .option('--json', 'Output as JSON')
276
+ .action(async (id, options) => {
277
+ const spinner = ora('Fetching API key...').start();
278
+ try {
279
+ // First try to get by ID directly
280
+ let key;
281
+ try {
282
+ key = await client.getApiKey(id, options.tenant);
283
+ }
284
+ catch {
285
+ // Fall back to list and search
286
+ const result = await client.listApiKeys(options.tenant);
287
+ key = result.keys.find(k => k.id === id || k.prefix === id || k.name === id);
288
+ }
289
+ if (!key) {
290
+ spinner.fail('API key not found');
291
+ output.error(`No API key found matching: ${id}`);
292
+ process.exit(1);
293
+ }
294
+ spinner.stop();
295
+ if (options.json) {
296
+ output.json(key);
297
+ return;
298
+ }
299
+ const daysLeft = getDaysUntilExpiry(key.expires_at);
300
+ const statusIcon = key.enabled ? '\x1b[32m●\x1b[0m Active' : '\x1b[31m○\x1b[0m Disabled';
301
+ output.keyValue({
302
+ 'Key ID': key.id,
303
+ 'Name': key.name,
304
+ 'Prefix': key.prefix,
305
+ 'Status': statusIcon,
306
+ 'Tenant': key.tenant_id,
307
+ 'Description': key.description || 'None',
308
+ 'Created By': key.created_by_username || key.created_by || 'Unknown',
309
+ 'Created': formatDate(key.created_at),
310
+ 'Expires': formatDate(key.expires_at),
311
+ 'Days Until Expiry': daysLeft,
312
+ 'Last Used': key.last_used ? formatDate(key.last_used) : 'Never',
313
+ 'Rotation Count': key.rotation_count,
314
+ 'Last Rotation': key.last_rotation ? formatDate(key.last_rotation) : 'Never',
315
+ 'IP Allowlist': key.ip_allowlist?.join(', ') || 'None (any IP)',
316
+ });
317
+ if (key.permissions.length > 0) {
318
+ console.log('\nPermissions:');
319
+ for (const perm of key.permissions) {
320
+ console.log(` - ${perm}`);
321
+ }
322
+ }
323
+ // Display conditions if any
324
+ if (key.conditions && Object.keys(key.conditions).length > 0) {
325
+ console.log('\nConditions:');
326
+ const cond = key.conditions;
327
+ if (cond.ip)
328
+ console.log(` - IP Allowlist: ${cond.ip.join(', ')}`);
329
+ if (cond.timeRange) {
330
+ const tr = cond.timeRange;
331
+ console.log(` - Time Range: ${tr.start}-${tr.end} ${tr.timezone || 'UTC'}`);
332
+ }
333
+ if (cond.methods)
334
+ console.log(` - Methods: ${cond.methods.join(', ')}`);
335
+ if (cond.resources)
336
+ console.log(` - Resources: ${JSON.stringify(cond.resources)}`);
337
+ if (cond.aliases)
338
+ console.log(` - Aliases: ${cond.aliases.join(', ')}`);
339
+ if (cond.resourceTags)
340
+ console.log(` - Tags: ${JSON.stringify(cond.resourceTags)}`);
341
+ }
342
+ if (!key.enabled) {
343
+ console.log('\n⚠️ This key is disabled and cannot be used for authentication.');
344
+ }
345
+ else if (daysLeft <= 7) {
346
+ console.log('\n⚠️ This key is expiring soon! Consider rotating it.');
347
+ }
348
+ }
349
+ catch (err) {
350
+ spinner.fail('Failed to fetch API key');
351
+ output.error(err instanceof Error ? err.message : String(err));
352
+ process.exit(1);
353
+ }
354
+ });
355
+ // Delete API key
356
+ apiKeyCmd
357
+ .command('delete <id>')
358
+ .alias('rm')
359
+ .description('Delete an API key')
360
+ .option('-t, --tenant <id>', 'Tenant ID')
361
+ .option('-f, --force', 'Skip confirmation')
362
+ .action(async (id, options) => {
363
+ if (!options.force) {
364
+ output.warn(`This will permanently delete API key: ${id}`);
365
+ output.warn('The key will stop working immediately.');
366
+ }
367
+ const spinner = ora('Deleting API key...').start();
368
+ try {
369
+ await client.deleteApiKey(id, options.tenant);
370
+ spinner.succeed(`API key deleted: ${id}`);
371
+ }
372
+ catch (err) {
373
+ spinner.fail('Failed to delete API key');
374
+ output.error(err instanceof Error ? err.message : String(err));
375
+ process.exit(1);
376
+ }
377
+ });
378
+ // Rotate API key
379
+ apiKeyCmd
380
+ .command('rotate <id>')
381
+ .description('Rotate an API key (creates new key, invalidates old)')
382
+ .option('-n, --name <name>', 'New name for the rotated key')
383
+ .option('-t, --tenant <id>', 'Tenant ID')
384
+ .option('--json', 'Output as JSON')
385
+ .action(async (id, options) => {
386
+ const spinner = ora('Rotating API key...').start();
387
+ try {
388
+ const result = await client.rotateApiKey(id, options.name, options.tenant);
389
+ spinner.succeed('API key rotated');
390
+ if (options.json) {
391
+ output.json(result);
392
+ return;
393
+ }
394
+ console.log('\n⚠️ IMPORTANT: Save this new key now - it will not be shown again!');
395
+ console.log('The old key has been invalidated.\n');
396
+ console.log('────────────────────────────────────────────────────────────────');
397
+ console.log(`New API Key: ${result.key}`);
398
+ console.log('────────────────────────────────────────────────────────────────\n');
399
+ output.keyValue({
400
+ 'New Key ID': result.apiKey.id,
401
+ 'Name': result.apiKey.name,
402
+ 'Prefix': result.apiKey.prefix,
403
+ 'Expires': formatDate(result.apiKey.expires_at),
404
+ 'Rotation Count': result.apiKey.rotation_count,
405
+ });
406
+ }
407
+ catch (err) {
408
+ spinner.fail('Failed to rotate API key');
409
+ output.error(err instanceof Error ? err.message : String(err));
410
+ process.exit(1);
411
+ }
412
+ });
413
+ // Enable API key
414
+ apiKeyCmd
415
+ .command('enable <id>')
416
+ .description('Enable an API key (allow authentication)')
417
+ .option('-t, --tenant <id>', 'Tenant ID')
418
+ .action(async (id, options) => {
419
+ const spinner = ora('Enabling API key...').start();
420
+ try {
421
+ const key = await client.setApiKeyEnabled(id, true, options.tenant);
422
+ spinner.succeed(`API key enabled: ${key.name}`);
423
+ console.log('\nThe key can now be used for authentication.');
424
+ }
425
+ catch (err) {
426
+ spinner.fail('Failed to enable API key');
427
+ output.error(err instanceof Error ? err.message : String(err));
428
+ process.exit(1);
429
+ }
430
+ });
431
+ // Disable API key
432
+ apiKeyCmd
433
+ .command('disable <id>')
434
+ .description('Disable an API key (block authentication without deleting)')
435
+ .option('-t, --tenant <id>', 'Tenant ID')
436
+ .action(async (id, options) => {
437
+ const spinner = ora('Disabling API key...').start();
438
+ try {
439
+ const key = await client.setApiKeyEnabled(id, false, options.tenant);
440
+ spinner.succeed(`API key disabled: ${key.name}`);
441
+ console.log('\nThe key is now blocked from authentication.');
442
+ console.log('Use "znvault apikey enable" to re-enable it.');
443
+ }
444
+ catch (err) {
445
+ spinner.fail('Failed to disable API key');
446
+ output.error(err instanceof Error ? err.message : String(err));
447
+ process.exit(1);
448
+ }
449
+ });
450
+ // Update permissions
451
+ apiKeyCmd
452
+ .command('update-permissions <id>')
453
+ .description('Update API key permissions')
454
+ .option('-s, --set <perms>', 'Set permissions (comma-separated, replaces all)')
455
+ .option('-t, --tenant <id>', 'Tenant ID')
456
+ .option('--json', 'Output as JSON')
457
+ .action(async (id, options) => {
458
+ if (!options.set) {
459
+ output.error('--set is required. Provide comma-separated permissions.');
460
+ process.exit(1);
461
+ }
462
+ const permissions = options.set.split(',').map((p) => p.trim());
463
+ const spinner = ora('Updating permissions...').start();
464
+ try {
465
+ const key = await client.updateApiKeyPermissions(id, permissions, options.tenant);
466
+ spinner.succeed('Permissions updated');
467
+ if (options.json) {
468
+ output.json(key);
469
+ return;
470
+ }
471
+ console.log('\nUpdated permissions:');
472
+ for (const perm of key.permissions) {
473
+ console.log(` - ${perm}`);
474
+ }
475
+ }
476
+ catch (err) {
477
+ spinner.fail('Failed to update permissions');
478
+ output.error(err instanceof Error ? err.message : String(err));
479
+ process.exit(1);
480
+ }
481
+ });
482
+ // Update conditions
483
+ apiKeyCmd
484
+ .command('update-conditions <id>')
485
+ .description('Update API key inline ABAC conditions')
486
+ .option('--ip <ips>', 'Comma-separated IP allowlist (CIDR supported)')
487
+ .option('--time-range <range>', 'Time range: "HH:MM-HH:MM [TIMEZONE]" or "clear"')
488
+ .option('--methods <methods>', 'Comma-separated HTTP methods or "clear"')
489
+ .option('--resources <ids>', 'Resource IDs (type:id,...) or "clear"')
490
+ .option('--aliases <patterns>', 'Alias patterns (glob) or "clear"')
491
+ .option('--tags <tags>', 'Resource tags (key=value,...) or "clear"')
492
+ .option('--clear-all', 'Remove all conditions')
493
+ .option('-t, --tenant <id>', 'Tenant ID')
494
+ .option('--json', 'Output as JSON')
495
+ .action(async (id, options) => {
496
+ const conditions = {};
497
+ // Handle --clear-all
498
+ if (options.clearAll) {
499
+ // Empty object clears all conditions
500
+ }
501
+ else {
502
+ // Parse individual conditions
503
+ if (options.ip && options.ip !== 'clear') {
504
+ conditions.ip = options.ip.split(',').map((ip) => ip.trim());
505
+ }
506
+ if (options.timeRange) {
507
+ if (options.timeRange === 'clear') {
508
+ // Don't include timeRange (will be removed)
509
+ }
510
+ else {
511
+ const match = options.timeRange.match(/^(\d{2}:\d{2})-(\d{2}:\d{2})(?:\s+(.+))?$/);
512
+ if (!match) {
513
+ output.error('Invalid time range format. Use: "HH:MM-HH:MM [TIMEZONE]"');
514
+ process.exit(1);
515
+ }
516
+ conditions.timeRange = {
517
+ start: match[1],
518
+ end: match[2],
519
+ timezone: match[3] || 'UTC',
520
+ };
521
+ }
522
+ }
523
+ if (options.methods && options.methods !== 'clear') {
524
+ conditions.methods = options.methods.split(',').map((m) => m.trim().toUpperCase());
525
+ }
526
+ if (options.resources && options.resources !== 'clear') {
527
+ const resources = {};
528
+ for (const part of options.resources.split(',')) {
529
+ const [type, resId] = part.split(':');
530
+ if (type && resId) {
531
+ if (!resources[type])
532
+ resources[type] = [];
533
+ resources[type].push(resId);
534
+ }
535
+ }
536
+ if (Object.keys(resources).length > 0) {
537
+ conditions.resources = resources;
538
+ }
539
+ }
540
+ if (options.aliases && options.aliases !== 'clear') {
541
+ conditions.aliases = options.aliases.split(',').map((a) => a.trim());
542
+ }
543
+ if (options.tags && options.tags !== 'clear') {
544
+ const tags = {};
545
+ for (const part of options.tags.split(',')) {
546
+ const [key, value] = part.split('=');
547
+ if (key && value) {
548
+ tags[key.trim()] = value.trim();
549
+ }
550
+ }
551
+ if (Object.keys(tags).length > 0) {
552
+ conditions.resourceTags = tags;
553
+ }
554
+ }
555
+ }
556
+ const spinner = ora('Updating conditions...').start();
557
+ try {
558
+ const key = await client.updateApiKeyConditions(id, conditions, options.tenant);
559
+ spinner.succeed('Conditions updated');
560
+ if (options.json) {
561
+ output.json(key);
562
+ return;
563
+ }
564
+ if (key.conditions && Object.keys(key.conditions).length > 0) {
565
+ console.log('\nUpdated conditions:');
566
+ const cond = key.conditions;
567
+ if (cond.ip)
568
+ console.log(` - IP Allowlist: ${cond.ip.join(', ')}`);
569
+ if (cond.timeRange) {
570
+ const tr = cond.timeRange;
571
+ console.log(` - Time Range: ${tr.start}-${tr.end} ${tr.timezone || 'UTC'}`);
572
+ }
573
+ if (cond.methods)
574
+ console.log(` - Methods: ${cond.methods.join(', ')}`);
575
+ if (cond.resources)
576
+ console.log(` - Resources: ${JSON.stringify(cond.resources)}`);
577
+ if (cond.aliases)
578
+ console.log(` - Aliases: ${cond.aliases.join(', ')}`);
579
+ if (cond.resourceTags)
580
+ console.log(` - Tags: ${JSON.stringify(cond.resourceTags)}`);
581
+ }
582
+ else {
583
+ console.log('\nAll conditions cleared.');
584
+ }
585
+ }
586
+ catch (err) {
587
+ spinner.fail('Failed to update conditions');
588
+ output.error(err instanceof Error ? err.message : String(err));
589
+ process.exit(1);
590
+ }
591
+ });
592
+ // List policies attached to an API key
593
+ apiKeyCmd
594
+ .command('list-policies <id>')
595
+ .description('List ABAC policies attached to an API key')
596
+ .option('-t, --tenant <id>', 'Tenant ID')
597
+ .option('--json', 'Output as JSON')
598
+ .action(async (id, options) => {
599
+ const spinner = ora('Fetching policies...').start();
600
+ try {
601
+ const result = await client.getApiKeyPolicies(id, options.tenant);
602
+ spinner.stop();
603
+ if (options.json) {
604
+ output.json(result);
605
+ return;
606
+ }
607
+ if (result.policies.length === 0) {
608
+ output.info('No ABAC policies attached to this API key');
609
+ return;
610
+ }
611
+ const table = new Table({
612
+ head: ['Policy ID', 'Policy Name', 'Attached At'],
613
+ style: { head: ['cyan'] },
614
+ });
615
+ for (const policy of result.policies) {
616
+ table.push([
617
+ policy.policyId,
618
+ policy.policyName,
619
+ formatDate(policy.attachedAt),
620
+ ]);
621
+ }
622
+ console.log(table.toString());
623
+ console.log(`\nTotal: ${result.policies.length} policy/policies`);
624
+ }
625
+ catch (err) {
626
+ spinner.fail('Failed to fetch policies');
627
+ output.error(err instanceof Error ? err.message : String(err));
628
+ process.exit(1);
629
+ }
630
+ });
631
+ // Attach policy to API key
632
+ apiKeyCmd
633
+ .command('attach-policy <keyId> <policyId>')
634
+ .description('Attach an ABAC policy to an API key')
635
+ .option('-t, --tenant <id>', 'Tenant ID')
636
+ .action(async (keyId, policyId, options) => {
637
+ const spinner = ora('Attaching policy...').start();
638
+ try {
639
+ await client.attachApiKeyPolicy(keyId, policyId, options.tenant);
640
+ spinner.succeed(`Policy ${policyId} attached to API key ${keyId}`);
641
+ }
642
+ catch (err) {
643
+ spinner.fail('Failed to attach policy');
644
+ output.error(err instanceof Error ? err.message : String(err));
645
+ process.exit(1);
646
+ }
647
+ });
648
+ // Detach policy from API key
649
+ apiKeyCmd
650
+ .command('detach-policy <keyId> <policyId>')
651
+ .description('Detach an ABAC policy from an API key')
652
+ .option('-t, --tenant <id>', 'Tenant ID')
653
+ .action(async (keyId, policyId, options) => {
654
+ const spinner = ora('Detaching policy...').start();
655
+ try {
656
+ await client.detachApiKeyPolicy(keyId, policyId, options.tenant);
657
+ spinner.succeed(`Policy ${policyId} detached from API key ${keyId}`);
658
+ }
659
+ catch (err) {
660
+ spinner.fail('Failed to detach policy');
661
+ output.error(err instanceof Error ? err.message : String(err));
662
+ process.exit(1);
663
+ }
664
+ });
665
+ // Self-info (when using API key auth)
666
+ apiKeyCmd
667
+ .command('self')
668
+ .description('Show info about the currently used API key')
669
+ .option('--json', 'Output as JSON')
670
+ .action(async (options) => {
671
+ const spinner = ora('Fetching API key info...').start();
672
+ try {
673
+ const result = await client.getApiKeySelf();
674
+ spinner.stop();
675
+ if (options.json) {
676
+ output.json(result);
677
+ return;
678
+ }
679
+ const statusIcon = result.apiKey.enabled ? '\x1b[32m●\x1b[0m Active' : '\x1b[31m○\x1b[0m Disabled';
680
+ output.keyValue({
681
+ 'Key ID': result.apiKey.id,
682
+ 'Name': result.apiKey.name,
683
+ 'Prefix': result.apiKey.prefix,
684
+ 'Status': statusIcon,
685
+ 'Tenant': result.apiKey.tenant_id,
686
+ 'Description': result.apiKey.description || 'None',
687
+ 'Expires': formatDate(result.apiKey.expires_at),
688
+ 'Days Until Expiry': result.expiresInDays,
689
+ 'Expiring Soon': result.isExpiringSoon ? 'Yes (!)' : 'No',
690
+ 'Last Used': result.apiKey.last_used ? formatDate(result.apiKey.last_used) : 'Never',
691
+ 'Rotation Count': result.apiKey.rotation_count,
692
+ 'Last Rotation': result.apiKey.last_rotation ? formatDate(result.apiKey.last_rotation) : 'Never',
693
+ });
694
+ if (result.apiKey.permissions.length > 0) {
695
+ console.log('\nPermissions:');
696
+ for (const perm of result.apiKey.permissions) {
697
+ console.log(` - ${perm}`);
698
+ }
699
+ }
700
+ // Display conditions if any
701
+ if (result.apiKey.conditions && Object.keys(result.apiKey.conditions).length > 0) {
702
+ console.log('\nConditions:');
703
+ const cond = result.apiKey.conditions;
704
+ if (cond.ip)
705
+ console.log(` - IP Allowlist: ${cond.ip.join(', ')}`);
706
+ if (cond.timeRange) {
707
+ const tr = cond.timeRange;
708
+ console.log(` - Time Range: ${tr.start}-${tr.end} ${tr.timezone || 'UTC'}`);
709
+ }
710
+ if (cond.methods)
711
+ console.log(` - Methods: ${cond.methods.join(', ')}`);
712
+ if (cond.resources)
713
+ console.log(` - Resources: ${JSON.stringify(cond.resources)}`);
714
+ if (cond.aliases)
715
+ console.log(` - Aliases: ${cond.aliases.join(', ')}`);
716
+ if (cond.resourceTags)
717
+ console.log(` - Tags: ${JSON.stringify(cond.resourceTags)}`);
718
+ }
719
+ if (result.isExpiringSoon) {
720
+ console.log('\n⚠️ This key is expiring soon! Run "znvault apikey self-rotate" to rotate it.');
721
+ }
722
+ }
723
+ catch (err) {
724
+ spinner.fail('Failed to fetch API key info');
725
+ output.error(err instanceof Error ? err.message : String(err));
726
+ console.log('\nNote: This command only works when authenticated via API key (X-API-Key header).');
727
+ process.exit(1);
728
+ }
729
+ });
730
+ // Self-rotate (when using API key auth)
731
+ apiKeyCmd
732
+ .command('self-rotate')
733
+ .description('Rotate the currently used API key')
734
+ .option('-n, --name <name>', 'New name for the rotated key')
735
+ .option('--json', 'Output as JSON')
736
+ .action(async (options) => {
737
+ const spinner = ora('Rotating API key...').start();
738
+ try {
739
+ const result = await client.rotateApiKeySelf(options.name);
740
+ spinner.succeed('API key rotated');
741
+ if (options.json) {
742
+ output.json(result);
743
+ return;
744
+ }
745
+ console.log('\n⚠️ IMPORTANT: Save this new key now - it will not be shown again!');
746
+ console.log('The old key has been invalidated.\n');
747
+ console.log('────────────────────────────────────────────────────────────────');
748
+ console.log(`New API Key: ${result.key}`);
749
+ console.log('────────────────────────────────────────────────────────────────\n');
750
+ output.keyValue({
751
+ 'New Key ID': result.apiKey.id,
752
+ 'Name': result.apiKey.name,
753
+ 'Prefix': result.apiKey.prefix,
754
+ 'Expires': formatDate(result.apiKey.expires_at),
755
+ 'Days Until Expiry': result.expiresInDays,
756
+ 'Rotation Count': result.apiKey.rotation_count,
757
+ });
758
+ }
759
+ catch (err) {
760
+ spinner.fail('Failed to rotate API key');
761
+ output.error(err instanceof Error ? err.message : String(err));
762
+ console.log('\nNote: This command only works when authenticated via API key (X-API-Key header).');
763
+ process.exit(1);
764
+ }
765
+ });
766
+ }
767
+ //# sourceMappingURL=apikey.js.map