@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,660 @@
1
+ // Path: znvault-cli/src/commands/policy.ts
2
+ import ora from 'ora';
3
+ import * as fs from 'fs';
4
+ import { client } from '../lib/client.js';
5
+ import { promptConfirm } from '../lib/prompts.js';
6
+ import * as output from '../lib/output.js';
7
+ export function registerPolicyCommands(program) {
8
+ const policy = program
9
+ .command('policy')
10
+ .description('ABAC policy management commands');
11
+ // ============ List Policies ============
12
+ policy
13
+ .command('list')
14
+ .description('List ABAC policies')
15
+ .option('--tenant <id>', 'Filter by tenant ID (superadmin only)')
16
+ .option('--enabled', 'Show only enabled policies')
17
+ .option('--disabled', 'Show only disabled policies')
18
+ .option('--effect <effect>', 'Filter by effect (allow|deny)')
19
+ .option('--search <term>', 'Search by name or description')
20
+ .option('--json', 'Output as JSON')
21
+ .action(async (options) => {
22
+ const spinner = ora('Fetching policies...').start();
23
+ try {
24
+ const result = await client.listPolicies({
25
+ tenantId: options.tenant,
26
+ enabled: options.enabled ? true : options.disabled ? false : undefined,
27
+ effect: options.effect,
28
+ search: options.search,
29
+ });
30
+ spinner.stop();
31
+ if (options.json) {
32
+ output.json(result.data);
33
+ return;
34
+ }
35
+ if (result.data.length === 0) {
36
+ output.info('No policies found');
37
+ return;
38
+ }
39
+ output.table(['ID', 'Name', 'Effect', 'Priority', 'Actions', 'Status', 'Tenant'], result.data.map(p => [
40
+ p.id.substring(0, 8),
41
+ p.name.length > 25 ? p.name.substring(0, 22) + '...' : p.name,
42
+ p.effect.toUpperCase(),
43
+ p.priority.toString(),
44
+ p.actions.length > 2 ? `${p.actions.slice(0, 2).join(', ')}...` : p.actions.join(', '),
45
+ p.isActive ? 'Enabled' : 'Disabled',
46
+ p.tenantId || '-',
47
+ ]));
48
+ output.info(`Total: ${result.total} policy(s)`);
49
+ }
50
+ catch (err) {
51
+ spinner.fail('Failed to list policies');
52
+ output.error(err instanceof Error ? err.message : String(err));
53
+ process.exit(1);
54
+ }
55
+ });
56
+ // ============ Get Policy ============
57
+ policy
58
+ .command('get <id>')
59
+ .description('Get policy details')
60
+ .option('--json', 'Output as JSON')
61
+ .action(async (id, options) => {
62
+ const spinner = ora('Fetching policy...').start();
63
+ try {
64
+ const result = await client.getPolicy(id);
65
+ spinner.stop();
66
+ if (options.json) {
67
+ output.json(result);
68
+ return;
69
+ }
70
+ output.section('Policy Details');
71
+ output.keyValue({
72
+ 'ID': result.id,
73
+ 'Name': result.name,
74
+ 'Description': result.description || '-',
75
+ 'Effect': result.effect.toUpperCase(),
76
+ 'Priority': result.priority.toString(),
77
+ 'Status': result.isActive ? 'Enabled' : 'Disabled',
78
+ 'Tenant': result.tenantId || 'Global',
79
+ 'Created': output.formatDate(result.createdAt),
80
+ 'Updated': output.formatDate(result.updatedAt),
81
+ });
82
+ console.log();
83
+ output.section('Actions');
84
+ for (const action of result.actions) {
85
+ console.log(` • ${action}`);
86
+ }
87
+ if (result.resources && result.resources.length > 0) {
88
+ console.log();
89
+ output.section('Resources');
90
+ for (const resource of result.resources) {
91
+ const parts = [`type: ${resource.type}`];
92
+ if (resource.id)
93
+ parts.push(`id: ${resource.id}`);
94
+ if (resource.tenantId)
95
+ parts.push(`tenant: ${resource.tenantId}`);
96
+ if (resource.tags)
97
+ parts.push(`tags: ${JSON.stringify(resource.tags)}`);
98
+ console.log(` • ${parts.join(', ')}`);
99
+ }
100
+ }
101
+ if (result.conditions && result.conditions.length > 0) {
102
+ console.log();
103
+ output.section('Conditions');
104
+ for (const condition of result.conditions) {
105
+ const op = condition.operator ? ` ${condition.operator}` : '';
106
+ console.log(` • ${condition.type}${op}: ${JSON.stringify(condition.value)}`);
107
+ }
108
+ }
109
+ console.log();
110
+ }
111
+ catch (err) {
112
+ spinner.fail('Failed to get policy');
113
+ output.error(err instanceof Error ? err.message : String(err));
114
+ process.exit(1);
115
+ }
116
+ });
117
+ // ============ Create Policy ============
118
+ policy
119
+ .command('create')
120
+ .description('Create a new ABAC policy')
121
+ .requiredOption('--name <name>', 'Policy name')
122
+ .requiredOption('--effect <effect>', 'Policy effect (allow|deny)')
123
+ .requiredOption('--actions <actions>', 'Comma-separated list of actions (e.g., secret:read:value,secret:update)')
124
+ .option('--description <desc>', 'Policy description')
125
+ .option('--priority <num>', 'Priority (higher = evaluated first)', '0')
126
+ .option('--tenant <id>', 'Tenant ID (omit for global policy)')
127
+ .option('--resources <json>', 'Resources JSON array')
128
+ .option('--conditions <json>', 'Conditions JSON array')
129
+ .option('--from-file <path>', 'Load policy definition from JSON file')
130
+ .option('--json', 'Output as JSON')
131
+ .action(async (options) => {
132
+ try {
133
+ let policyData;
134
+ if (options.fromFile) {
135
+ // Load from file
136
+ const content = fs.readFileSync(options.fromFile, 'utf-8');
137
+ policyData = JSON.parse(content);
138
+ }
139
+ else {
140
+ // Build from options
141
+ policyData = {
142
+ name: options.name,
143
+ description: options.description,
144
+ effect: options.effect,
145
+ actions: options.actions.split(',').map((a) => a.trim()),
146
+ priority: parseInt(options.priority, 10),
147
+ tenantId: options.tenant,
148
+ };
149
+ if (options.resources) {
150
+ policyData.resources = JSON.parse(options.resources);
151
+ }
152
+ if (options.conditions) {
153
+ policyData.conditions = JSON.parse(options.conditions);
154
+ }
155
+ }
156
+ const spinner = ora('Creating policy...').start();
157
+ const result = await client.createPolicy(policyData);
158
+ spinner.succeed('Policy created successfully');
159
+ if (options.json) {
160
+ output.json(result);
161
+ }
162
+ else {
163
+ output.keyValue({
164
+ 'ID': result.id,
165
+ 'Name': result.name,
166
+ 'Effect': result.effect.toUpperCase(),
167
+ 'Priority': result.priority.toString(),
168
+ 'Status': result.isActive ? 'Enabled' : 'Disabled',
169
+ });
170
+ }
171
+ }
172
+ catch (err) {
173
+ output.error(err instanceof Error ? err.message : String(err));
174
+ process.exit(1);
175
+ }
176
+ });
177
+ // ============ Update Policy ============
178
+ policy
179
+ .command('update <id>')
180
+ .description('Update an ABAC policy')
181
+ .option('--name <name>', 'New policy name')
182
+ .option('--description <desc>', 'New description')
183
+ .option('--effect <effect>', 'New effect (allow|deny)')
184
+ .option('--actions <actions>', 'New comma-separated list of actions')
185
+ .option('--priority <num>', 'New priority')
186
+ .option('--resources <json>', 'New resources JSON array')
187
+ .option('--conditions <json>', 'New conditions JSON array')
188
+ .option('--from-file <path>', 'Load updates from JSON file')
189
+ .option('--json', 'Output as JSON')
190
+ .action(async (id, options) => {
191
+ try {
192
+ let updates;
193
+ if (options.fromFile) {
194
+ const content = fs.readFileSync(options.fromFile, 'utf-8');
195
+ updates = JSON.parse(content);
196
+ }
197
+ else {
198
+ updates = {};
199
+ if (options.name)
200
+ updates.name = options.name;
201
+ if (options.description)
202
+ updates.description = options.description;
203
+ if (options.effect)
204
+ updates.effect = options.effect;
205
+ if (options.actions)
206
+ updates.actions = options.actions.split(',').map((a) => a.trim());
207
+ if (options.priority)
208
+ updates.priority = parseInt(options.priority, 10);
209
+ if (options.resources)
210
+ updates.resources = JSON.parse(options.resources);
211
+ if (options.conditions)
212
+ updates.conditions = JSON.parse(options.conditions);
213
+ }
214
+ if (Object.keys(updates).length === 0) {
215
+ output.error('No updates specified');
216
+ process.exit(1);
217
+ }
218
+ const spinner = ora('Updating policy...').start();
219
+ const result = await client.updatePolicy(id, updates);
220
+ spinner.succeed('Policy updated successfully');
221
+ if (options.json) {
222
+ output.json(result);
223
+ }
224
+ else {
225
+ output.keyValue({
226
+ 'ID': result.id,
227
+ 'Name': result.name,
228
+ 'Effect': result.effect.toUpperCase(),
229
+ 'Updated': output.formatDate(result.updatedAt),
230
+ });
231
+ }
232
+ }
233
+ catch (err) {
234
+ output.error(err instanceof Error ? err.message : String(err));
235
+ process.exit(1);
236
+ }
237
+ });
238
+ // ============ Delete Policy ============
239
+ policy
240
+ .command('delete <id>')
241
+ .description('Delete an ABAC policy')
242
+ .option('-y, --yes', 'Skip confirmation')
243
+ .action(async (id, options) => {
244
+ try {
245
+ if (!options.yes) {
246
+ const confirmed = await promptConfirm(`Are you sure you want to delete policy '${id}'? This cannot be undone.`);
247
+ if (!confirmed) {
248
+ output.info('Delete cancelled');
249
+ return;
250
+ }
251
+ }
252
+ const spinner = ora('Deleting policy...').start();
253
+ await client.deletePolicy(id);
254
+ spinner.succeed(`Policy '${id}' deleted successfully`);
255
+ }
256
+ catch (err) {
257
+ output.error(err instanceof Error ? err.message : String(err));
258
+ process.exit(1);
259
+ }
260
+ });
261
+ // ============ Enable Policy ============
262
+ policy
263
+ .command('enable <id>')
264
+ .description('Enable an ABAC policy')
265
+ .action(async (id) => {
266
+ const spinner = ora('Enabling policy...').start();
267
+ try {
268
+ const result = await client.togglePolicy(id, true);
269
+ spinner.succeed('Policy enabled successfully');
270
+ output.keyValue({
271
+ 'ID': result.id,
272
+ 'Name': result.name,
273
+ 'Status': 'Enabled',
274
+ });
275
+ }
276
+ catch (err) {
277
+ spinner.fail('Failed to enable policy');
278
+ output.error(err instanceof Error ? err.message : String(err));
279
+ process.exit(1);
280
+ }
281
+ });
282
+ // ============ Disable Policy ============
283
+ policy
284
+ .command('disable <id>')
285
+ .description('Disable an ABAC policy')
286
+ .action(async (id) => {
287
+ const spinner = ora('Disabling policy...').start();
288
+ try {
289
+ const result = await client.togglePolicy(id, false);
290
+ spinner.succeed('Policy disabled successfully');
291
+ output.keyValue({
292
+ 'ID': result.id,
293
+ 'Name': result.name,
294
+ 'Status': 'Disabled',
295
+ });
296
+ }
297
+ catch (err) {
298
+ spinner.fail('Failed to disable policy');
299
+ output.error(err instanceof Error ? err.message : String(err));
300
+ process.exit(1);
301
+ }
302
+ });
303
+ // ============ Validate Policy ============
304
+ policy
305
+ .command('validate')
306
+ .description('Validate a policy definition without creating it')
307
+ .requiredOption('--name <name>', 'Policy name')
308
+ .requiredOption('--effect <effect>', 'Policy effect (allow|deny)')
309
+ .requiredOption('--actions <actions>', 'Comma-separated list of actions')
310
+ .option('--description <desc>', 'Policy description')
311
+ .option('--priority <num>', 'Priority', '0')
312
+ .option('--resources <json>', 'Resources JSON array')
313
+ .option('--conditions <json>', 'Conditions JSON array')
314
+ .option('--from-file <path>', 'Load policy from JSON file')
315
+ .action(async (options) => {
316
+ try {
317
+ let policyData;
318
+ if (options.fromFile) {
319
+ const content = fs.readFileSync(options.fromFile, 'utf-8');
320
+ policyData = JSON.parse(content);
321
+ }
322
+ else {
323
+ policyData = {
324
+ name: options.name,
325
+ description: options.description,
326
+ effect: options.effect,
327
+ actions: options.actions.split(',').map((a) => a.trim()),
328
+ priority: parseInt(options.priority, 10),
329
+ };
330
+ if (options.resources) {
331
+ policyData.resources = JSON.parse(options.resources);
332
+ }
333
+ if (options.conditions) {
334
+ policyData.conditions = JSON.parse(options.conditions);
335
+ }
336
+ }
337
+ const spinner = ora('Validating policy...').start();
338
+ const result = await client.validatePolicy(policyData);
339
+ if (result.valid) {
340
+ spinner.succeed('Policy is valid');
341
+ }
342
+ else {
343
+ spinner.fail('Policy validation failed');
344
+ if (result.errors) {
345
+ for (const error of result.errors) {
346
+ output.error(` • ${error}`);
347
+ }
348
+ }
349
+ process.exit(1);
350
+ }
351
+ }
352
+ catch (err) {
353
+ output.error(err instanceof Error ? err.message : String(err));
354
+ process.exit(1);
355
+ }
356
+ });
357
+ // ============ Show Policy Attachments ============
358
+ policy
359
+ .command('attachments <id>')
360
+ .description('Show users and roles attached to a policy')
361
+ .option('--json', 'Output as JSON')
362
+ .action(async (id, options) => {
363
+ const spinner = ora('Fetching attachments...').start();
364
+ try {
365
+ const result = await client.getPolicyAttachments(id);
366
+ spinner.stop();
367
+ if (options.json) {
368
+ output.json(result);
369
+ return;
370
+ }
371
+ if (result.users.length === 0 && result.roles.length === 0) {
372
+ output.info('No attachments found for this policy');
373
+ return;
374
+ }
375
+ if (result.users.length > 0) {
376
+ output.section('Attached Users');
377
+ output.table(['User ID', 'Username', 'Attached At'], result.users.map(a => [
378
+ a.userId?.substring(0, 8) || '-',
379
+ a.username || '-',
380
+ output.formatDate(a.attachedAt),
381
+ ]));
382
+ }
383
+ if (result.roles.length > 0) {
384
+ console.log();
385
+ output.section('Attached Roles');
386
+ output.table(['Role ID', 'Role Name', 'Attached At'], result.roles.map(a => [
387
+ a.roleId?.substring(0, 8) || '-',
388
+ a.roleName || '-',
389
+ output.formatDate(a.attachedAt),
390
+ ]));
391
+ }
392
+ }
393
+ catch (err) {
394
+ spinner.fail('Failed to get attachments');
395
+ output.error(err instanceof Error ? err.message : String(err));
396
+ process.exit(1);
397
+ }
398
+ });
399
+ // ============ Attach Policy to User ============
400
+ policy
401
+ .command('attach-user <policyId> <userId>')
402
+ .description('Attach a policy to a user')
403
+ .action(async (policyId, userId) => {
404
+ const spinner = ora('Attaching policy to user...').start();
405
+ try {
406
+ await client.attachPolicyToUser(policyId, userId);
407
+ spinner.succeed('Policy attached to user successfully');
408
+ }
409
+ catch (err) {
410
+ spinner.fail('Failed to attach policy');
411
+ output.error(err instanceof Error ? err.message : String(err));
412
+ process.exit(1);
413
+ }
414
+ });
415
+ // ============ Attach Policy to Role ============
416
+ policy
417
+ .command('attach-role <policyId> <roleId>')
418
+ .description('Attach a policy to a role')
419
+ .action(async (policyId, roleId) => {
420
+ const spinner = ora('Attaching policy to role...').start();
421
+ try {
422
+ await client.attachPolicyToRole(policyId, roleId);
423
+ spinner.succeed('Policy attached to role successfully');
424
+ }
425
+ catch (err) {
426
+ spinner.fail('Failed to attach policy');
427
+ output.error(err instanceof Error ? err.message : String(err));
428
+ process.exit(1);
429
+ }
430
+ });
431
+ // ============ Detach Policy from User ============
432
+ policy
433
+ .command('detach-user <policyId> <userId>')
434
+ .description('Detach a policy from a user')
435
+ .action(async (policyId, userId) => {
436
+ const spinner = ora('Detaching policy from user...').start();
437
+ try {
438
+ await client.detachPolicyFromUser(policyId, userId);
439
+ spinner.succeed('Policy detached from user successfully');
440
+ }
441
+ catch (err) {
442
+ spinner.fail('Failed to detach policy');
443
+ output.error(err instanceof Error ? err.message : String(err));
444
+ process.exit(1);
445
+ }
446
+ });
447
+ // ============ Detach Policy from Role ============
448
+ policy
449
+ .command('detach-role <policyId> <roleId>')
450
+ .description('Detach a policy from a role')
451
+ .action(async (policyId, roleId) => {
452
+ const spinner = ora('Detaching policy from role...').start();
453
+ try {
454
+ await client.detachPolicyFromRole(policyId, roleId);
455
+ spinner.succeed('Policy detached from role successfully');
456
+ }
457
+ catch (err) {
458
+ spinner.fail('Failed to detach policy');
459
+ output.error(err instanceof Error ? err.message : String(err));
460
+ process.exit(1);
461
+ }
462
+ });
463
+ // ============ List User's Policies ============
464
+ policy
465
+ .command('user-policies <userId>')
466
+ .description('List policies attached to a user (directly or via roles)')
467
+ .option('--json', 'Output as JSON')
468
+ .action(async (userId, options) => {
469
+ const spinner = ora('Fetching user policies...').start();
470
+ try {
471
+ const policies = await client.getUserPolicies(userId);
472
+ spinner.stop();
473
+ if (options.json) {
474
+ output.json(policies);
475
+ return;
476
+ }
477
+ if (policies.length === 0) {
478
+ output.info('No policies attached to this user');
479
+ return;
480
+ }
481
+ output.table(['ID', 'Name', 'Effect', 'Priority', 'Status'], policies.map(p => [
482
+ p.id.substring(0, 8),
483
+ p.name,
484
+ p.effect.toUpperCase(),
485
+ p.priority.toString(),
486
+ p.isActive ? 'Enabled' : 'Disabled',
487
+ ]));
488
+ output.info(`Total: ${policies.length} policy(s)`);
489
+ }
490
+ catch (err) {
491
+ spinner.fail('Failed to get user policies');
492
+ output.error(err instanceof Error ? err.message : String(err));
493
+ process.exit(1);
494
+ }
495
+ });
496
+ // ============ List Role's Policies ============
497
+ policy
498
+ .command('role-policies <roleId>')
499
+ .description('List policies attached to a role')
500
+ .option('--json', 'Output as JSON')
501
+ .action(async (roleId, options) => {
502
+ const spinner = ora('Fetching role policies...').start();
503
+ try {
504
+ const policies = await client.getRolePolicies(roleId);
505
+ spinner.stop();
506
+ if (options.json) {
507
+ output.json(policies);
508
+ return;
509
+ }
510
+ if (policies.length === 0) {
511
+ output.info('No policies attached to this role');
512
+ return;
513
+ }
514
+ output.table(['ID', 'Name', 'Effect', 'Priority', 'Status'], policies.map(p => [
515
+ p.id.substring(0, 8),
516
+ p.name,
517
+ p.effect.toUpperCase(),
518
+ p.priority.toString(),
519
+ p.isActive ? 'Enabled' : 'Disabled',
520
+ ]));
521
+ output.info(`Total: ${policies.length} policy(s)`);
522
+ }
523
+ catch (err) {
524
+ spinner.fail('Failed to get role policies');
525
+ output.error(err instanceof Error ? err.message : String(err));
526
+ process.exit(1);
527
+ }
528
+ });
529
+ // ============ Test Policy Evaluation ============
530
+ policy
531
+ .command('test')
532
+ .description('Test ABAC policy evaluation for a user and action')
533
+ .requiredOption('--user <userId>', 'User ID to test')
534
+ .requiredOption('--action <action>', 'Action to test (e.g., secret:read:value)')
535
+ .option('--resource-type <type>', 'Resource type (secret|kms_key|certificate|...)')
536
+ .option('--resource-id <id>', 'Resource ID')
537
+ .option('--resource-tenant <id>', 'Resource tenant ID')
538
+ .option('--ip <ip>', 'Simulated client IP address')
539
+ .option('--mfa', 'Simulate MFA verified')
540
+ .option('--json', 'Output as JSON')
541
+ .action(async (options) => {
542
+ const spinner = ora('Testing policy evaluation...').start();
543
+ try {
544
+ const request = {
545
+ userId: options.user,
546
+ action: options.action,
547
+ resource: options.resourceType ? {
548
+ type: options.resourceType,
549
+ id: options.resourceId,
550
+ tenantId: options.resourceTenant,
551
+ } : undefined,
552
+ requestContext: (options.ip || options.mfa) ? {
553
+ ip: options.ip,
554
+ mfaVerified: options.mfa || false,
555
+ } : undefined,
556
+ };
557
+ const result = await client.testPolicy(request);
558
+ spinner.stop();
559
+ if (options.json) {
560
+ output.json(result);
561
+ return;
562
+ }
563
+ // Display result with color
564
+ const statusIcon = result.allowed ? '✓' : '✗';
565
+ const statusText = result.allowed ? 'ALLOWED' : 'DENIED';
566
+ console.log();
567
+ console.log(` ${statusIcon} Access: ${statusText}`);
568
+ console.log(` Effect: ${result.effect.toUpperCase()}`);
569
+ console.log(` Reason: ${result.reason}`);
570
+ console.log();
571
+ output.keyValue({
572
+ 'Policies Evaluated': result.evaluatedPolicies.toString(),
573
+ 'Policies Matched': result.matchedPolicies.length.toString(),
574
+ 'Evaluation Time': `${result.evaluationTimeMs}ms`,
575
+ });
576
+ if (result.matchedPolicies.length > 0) {
577
+ console.log();
578
+ output.section('Matched Policies');
579
+ output.table(['Name', 'Effect', 'Priority'], result.matchedPolicies.map(p => [
580
+ p.name,
581
+ p.effect.toUpperCase(),
582
+ p.priority.toString(),
583
+ ]));
584
+ }
585
+ }
586
+ catch (err) {
587
+ spinner.fail('Failed to test policy');
588
+ output.error(err instanceof Error ? err.message : String(err));
589
+ process.exit(1);
590
+ }
591
+ });
592
+ // ============ Export Policy ============
593
+ policy
594
+ .command('export <id>')
595
+ .description('Export a policy as JSON')
596
+ .option('-o, --output <path>', 'Output file path')
597
+ .action(async (id, options) => {
598
+ const spinner = ora('Exporting policy...').start();
599
+ try {
600
+ const result = await client.getPolicy(id);
601
+ spinner.stop();
602
+ const exportData = {
603
+ name: result.name,
604
+ description: result.description,
605
+ effect: result.effect,
606
+ actions: result.actions,
607
+ resources: result.resources,
608
+ conditions: result.conditions,
609
+ priority: result.priority,
610
+ };
611
+ const jsonString = JSON.stringify(exportData, null, 2);
612
+ if (options.output) {
613
+ fs.writeFileSync(options.output, jsonString);
614
+ output.success(`Policy exported to ${options.output}`);
615
+ }
616
+ else {
617
+ console.log(jsonString);
618
+ }
619
+ }
620
+ catch (err) {
621
+ spinner.fail('Failed to export policy');
622
+ output.error(err instanceof Error ? err.message : String(err));
623
+ process.exit(1);
624
+ }
625
+ });
626
+ // ============ Import Policy ============
627
+ policy
628
+ .command('import <path>')
629
+ .description('Import a policy from JSON file')
630
+ .option('--tenant <id>', 'Override tenant ID')
631
+ .option('--json', 'Output as JSON')
632
+ .action(async (path, options) => {
633
+ try {
634
+ const content = fs.readFileSync(path, 'utf-8');
635
+ const policyData = JSON.parse(content);
636
+ if (options.tenant) {
637
+ policyData.tenantId = options.tenant;
638
+ }
639
+ const spinner = ora('Importing policy...').start();
640
+ const result = await client.createPolicy(policyData);
641
+ spinner.succeed('Policy imported successfully');
642
+ if (options.json) {
643
+ output.json(result);
644
+ }
645
+ else {
646
+ output.keyValue({
647
+ 'ID': result.id,
648
+ 'Name': result.name,
649
+ 'Effect': result.effect.toUpperCase(),
650
+ 'Status': result.isActive ? 'Enabled' : 'Disabled',
651
+ });
652
+ }
653
+ }
654
+ catch (err) {
655
+ output.error(err instanceof Error ? err.message : String(err));
656
+ process.exit(1);
657
+ }
658
+ });
659
+ }
660
+ //# sourceMappingURL=policy.js.map