@zincapp/znvault-cli 2.16.4 → 2.17.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 (299) hide show
  1. package/dist/commands/agent.d.ts.map +1 -1
  2. package/dist/commands/agent.js +6 -0
  3. package/dist/commands/agent.js.map +1 -1
  4. package/dist/commands/apikey/conditions.d.ts +6 -0
  5. package/dist/commands/apikey/conditions.d.ts.map +1 -0
  6. package/dist/commands/apikey/conditions.js +57 -0
  7. package/dist/commands/apikey/conditions.js.map +1 -0
  8. package/dist/commands/apikey/create.d.ts +6 -0
  9. package/dist/commands/apikey/create.d.ts.map +1 -0
  10. package/dist/commands/apikey/create.js +106 -0
  11. package/dist/commands/apikey/create.js.map +1 -0
  12. package/dist/commands/apikey/delete.d.ts +6 -0
  13. package/dist/commands/apikey/delete.d.ts.map +1 -0
  14. package/dist/commands/apikey/delete.js +29 -0
  15. package/dist/commands/apikey/delete.js.map +1 -0
  16. package/dist/commands/apikey/enable-disable.d.ts +6 -0
  17. package/dist/commands/apikey/enable-disable.d.ts.map +1 -0
  18. package/dist/commands/apikey/enable-disable.js +44 -0
  19. package/dist/commands/apikey/enable-disable.js.map +1 -0
  20. package/dist/commands/apikey/helpers.d.ts +23 -0
  21. package/dist/commands/apikey/helpers.d.ts.map +1 -0
  22. package/dist/commands/apikey/helpers.js +135 -0
  23. package/dist/commands/apikey/helpers.js.map +1 -0
  24. package/dist/commands/apikey/index.d.ts +10 -0
  25. package/dist/commands/apikey/index.d.ts.map +1 -0
  26. package/dist/commands/apikey/index.js +33 -0
  27. package/dist/commands/apikey/index.js.map +1 -0
  28. package/dist/commands/apikey/list.d.ts +6 -0
  29. package/dist/commands/apikey/list.d.ts.map +1 -0
  30. package/dist/commands/apikey/list.js +74 -0
  31. package/dist/commands/apikey/list.js.map +1 -0
  32. package/dist/commands/apikey/managed/bind.d.ts +6 -0
  33. package/dist/commands/apikey/managed/bind.d.ts.map +1 -0
  34. package/dist/commands/apikey/managed/bind.js +52 -0
  35. package/dist/commands/apikey/managed/bind.js.map +1 -0
  36. package/dist/commands/apikey/managed/conditions.d.ts +6 -0
  37. package/dist/commands/apikey/managed/conditions.d.ts.map +1 -0
  38. package/dist/commands/apikey/managed/conditions.js +62 -0
  39. package/dist/commands/apikey/managed/conditions.js.map +1 -0
  40. package/dist/commands/apikey/managed/config.d.ts +6 -0
  41. package/dist/commands/apikey/managed/config.d.ts.map +1 -0
  42. package/dist/commands/apikey/managed/config.js +52 -0
  43. package/dist/commands/apikey/managed/config.js.map +1 -0
  44. package/dist/commands/apikey/managed/create.d.ts +6 -0
  45. package/dist/commands/apikey/managed/create.d.ts.map +1 -0
  46. package/dist/commands/apikey/managed/create.js +82 -0
  47. package/dist/commands/apikey/managed/create.js.map +1 -0
  48. package/dist/commands/apikey/managed/delete.d.ts +6 -0
  49. package/dist/commands/apikey/managed/delete.d.ts.map +1 -0
  50. package/dist/commands/apikey/managed/delete.js +29 -0
  51. package/dist/commands/apikey/managed/delete.js.map +1 -0
  52. package/dist/commands/apikey/managed/get.d.ts +6 -0
  53. package/dist/commands/apikey/managed/get.d.ts.map +1 -0
  54. package/dist/commands/apikey/managed/get.js +31 -0
  55. package/dist/commands/apikey/managed/get.js.map +1 -0
  56. package/dist/commands/apikey/managed/helpers.d.ts +5 -0
  57. package/dist/commands/apikey/managed/helpers.d.ts.map +1 -0
  58. package/dist/commands/apikey/managed/helpers.js +70 -0
  59. package/dist/commands/apikey/managed/helpers.js.map +1 -0
  60. package/dist/commands/apikey/managed/index.d.ts +7 -0
  61. package/dist/commands/apikey/managed/index.d.ts.map +1 -0
  62. package/dist/commands/apikey/managed/index.js +27 -0
  63. package/dist/commands/apikey/managed/index.js.map +1 -0
  64. package/dist/commands/apikey/managed/list.d.ts +6 -0
  65. package/dist/commands/apikey/managed/list.d.ts.map +1 -0
  66. package/dist/commands/apikey/managed/list.js +58 -0
  67. package/dist/commands/apikey/managed/list.js.map +1 -0
  68. package/dist/commands/apikey/managed/permissions.d.ts +6 -0
  69. package/dist/commands/apikey/managed/permissions.d.ts.map +1 -0
  70. package/dist/commands/apikey/managed/permissions.js +73 -0
  71. package/dist/commands/apikey/managed/permissions.js.map +1 -0
  72. package/dist/commands/apikey/managed/rotate.d.ts +6 -0
  73. package/dist/commands/apikey/managed/rotate.d.ts.map +1 -0
  74. package/dist/commands/apikey/managed/rotate.js +29 -0
  75. package/dist/commands/apikey/managed/rotate.js.map +1 -0
  76. package/dist/commands/apikey/managed/types.d.ts +62 -0
  77. package/dist/commands/apikey/managed/types.d.ts.map +1 -0
  78. package/dist/commands/apikey/managed/types.js +3 -0
  79. package/dist/commands/apikey/managed/types.js.map +1 -0
  80. package/dist/commands/apikey/permissions.d.ts +6 -0
  81. package/dist/commands/apikey/permissions.d.ts.map +1 -0
  82. package/dist/commands/apikey/permissions.js +70 -0
  83. package/dist/commands/apikey/permissions.js.map +1 -0
  84. package/dist/commands/apikey/policies.d.ts +6 -0
  85. package/dist/commands/apikey/policies.d.ts.map +1 -0
  86. package/dist/commands/apikey/policies.js +82 -0
  87. package/dist/commands/apikey/policies.js.map +1 -0
  88. package/dist/commands/apikey/rotate.d.ts +6 -0
  89. package/dist/commands/apikey/rotate.d.ts.map +1 -0
  90. package/dist/commands/apikey/rotate.js +42 -0
  91. package/dist/commands/apikey/rotate.js.map +1 -0
  92. package/dist/commands/apikey/self.d.ts +6 -0
  93. package/dist/commands/apikey/self.d.ts.map +1 -0
  94. package/dist/commands/apikey/self.js +96 -0
  95. package/dist/commands/apikey/self.js.map +1 -0
  96. package/dist/commands/apikey/show.d.ts +6 -0
  97. package/dist/commands/apikey/show.d.ts.map +1 -0
  98. package/dist/commands/apikey/show.js +79 -0
  99. package/dist/commands/apikey/show.js.map +1 -0
  100. package/dist/commands/apikey/types.d.ts +83 -0
  101. package/dist/commands/apikey/types.d.ts.map +1 -0
  102. package/dist/commands/apikey/types.js +3 -0
  103. package/dist/commands/apikey/types.js.map +1 -0
  104. package/dist/commands/apikey.d.ts +8 -2
  105. package/dist/commands/apikey.d.ts.map +1 -1
  106. package/dist/commands/apikey.js +9 -1296
  107. package/dist/commands/apikey.js.map +1 -1
  108. package/dist/commands/device.d.ts.map +1 -1
  109. package/dist/commands/device.js +8 -5
  110. package/dist/commands/device.js.map +1 -1
  111. package/dist/commands/plugin.d.ts.map +1 -1
  112. package/dist/commands/plugin.js +29 -7
  113. package/dist/commands/plugin.js.map +1 -1
  114. package/dist/commands/secret/copy.d.ts +6 -0
  115. package/dist/commands/secret/copy.d.ts.map +1 -0
  116. package/dist/commands/secret/copy.js +43 -0
  117. package/dist/commands/secret/copy.js.map +1 -0
  118. package/dist/commands/secret/create.d.ts +6 -0
  119. package/dist/commands/secret/create.d.ts.map +1 -0
  120. package/dist/commands/secret/create.js +297 -0
  121. package/dist/commands/secret/create.js.map +1 -0
  122. package/dist/commands/secret/decrypt.d.ts +6 -0
  123. package/dist/commands/secret/decrypt.d.ts.map +1 -0
  124. package/dist/commands/secret/decrypt.js +104 -0
  125. package/dist/commands/secret/decrypt.js.map +1 -0
  126. package/dist/commands/secret/delete.d.ts +6 -0
  127. package/dist/commands/secret/delete.d.ts.map +1 -0
  128. package/dist/commands/secret/delete.js +60 -0
  129. package/dist/commands/secret/delete.js.map +1 -0
  130. package/dist/commands/secret/get.d.ts +6 -0
  131. package/dist/commands/secret/get.d.ts.map +1 -0
  132. package/dist/commands/secret/get.js +60 -0
  133. package/dist/commands/secret/get.js.map +1 -0
  134. package/dist/commands/secret/helpers.d.ts +11 -0
  135. package/dist/commands/secret/helpers.d.ts.map +1 -0
  136. package/dist/commands/secret/helpers.js +59 -0
  137. package/dist/commands/secret/helpers.js.map +1 -0
  138. package/dist/commands/secret/history.d.ts +6 -0
  139. package/dist/commands/secret/history.d.ts.map +1 -0
  140. package/dist/commands/secret/history.js +52 -0
  141. package/dist/commands/secret/history.js.map +1 -0
  142. package/dist/commands/secret/index.d.ts +12 -0
  143. package/dist/commands/secret/index.d.ts.map +1 -0
  144. package/dist/commands/secret/index.js +49 -0
  145. package/dist/commands/secret/index.js.map +1 -0
  146. package/dist/commands/secret/list.d.ts +6 -0
  147. package/dist/commands/secret/list.d.ts.map +1 -0
  148. package/dist/commands/secret/list.js +72 -0
  149. package/dist/commands/secret/list.js.map +1 -0
  150. package/dist/commands/secret/pem-analysis.d.ts +32 -0
  151. package/dist/commands/secret/pem-analysis.d.ts.map +1 -0
  152. package/dist/commands/secret/pem-analysis.js +190 -0
  153. package/dist/commands/secret/pem-analysis.js.map +1 -0
  154. package/dist/commands/secret/resolve.d.ts +17 -0
  155. package/dist/commands/secret/resolve.d.ts.map +1 -0
  156. package/dist/commands/secret/resolve.js +36 -0
  157. package/dist/commands/secret/resolve.js.map +1 -0
  158. package/dist/commands/secret/rotate.d.ts +6 -0
  159. package/dist/commands/secret/rotate.d.ts.map +1 -0
  160. package/dist/commands/secret/rotate.js +72 -0
  161. package/dist/commands/secret/rotate.js.map +1 -0
  162. package/dist/commands/secret/types.d.ts +123 -0
  163. package/dist/commands/secret/types.d.ts.map +1 -0
  164. package/dist/commands/secret/types.js +3 -0
  165. package/dist/commands/secret/types.js.map +1 -0
  166. package/dist/commands/secret/update.d.ts +6 -0
  167. package/dist/commands/secret/update.d.ts.map +1 -0
  168. package/dist/commands/secret/update.js +124 -0
  169. package/dist/commands/secret/update.js.map +1 -0
  170. package/dist/commands/secret.d.ts +8 -2
  171. package/dist/commands/secret.d.ts.map +1 -1
  172. package/dist/commands/secret.js +6 -1131
  173. package/dist/commands/secret.js.map +1 -1
  174. package/dist/index.js +48 -4
  175. package/dist/index.js.map +1 -1
  176. package/dist/lib/client/apikeys.d.ts +34 -0
  177. package/dist/lib/client/apikeys.d.ts.map +1 -0
  178. package/dist/lib/client/apikeys.js +113 -0
  179. package/dist/lib/client/apikeys.js.map +1 -0
  180. package/dist/lib/client/audit.d.ts +21 -0
  181. package/dist/lib/client/audit.d.ts.map +1 -0
  182. package/dist/lib/client/audit.js +40 -0
  183. package/dist/lib/client/audit.js.map +1 -0
  184. package/dist/lib/client/health.d.ts +30 -0
  185. package/dist/lib/client/health.d.ts.map +1 -0
  186. package/dist/lib/client/health.js +55 -0
  187. package/dist/lib/client/health.js.map +1 -0
  188. package/dist/lib/client/http.d.ts +50 -0
  189. package/dist/lib/client/http.d.ts.map +1 -0
  190. package/dist/lib/client/http.js +333 -0
  191. package/dist/lib/client/http.js.map +1 -0
  192. package/dist/lib/client/index.d.ts +156 -0
  193. package/dist/lib/client/index.d.ts.map +1 -0
  194. package/dist/lib/client/index.js +172 -0
  195. package/dist/lib/client/index.js.map +1 -0
  196. package/dist/lib/client/lockdown.d.ts +23 -0
  197. package/dist/lib/client/lockdown.d.ts.map +1 -0
  198. package/dist/lib/client/lockdown.js +48 -0
  199. package/dist/lib/client/lockdown.js.map +1 -0
  200. package/dist/lib/client/managed-keys.d.ts +18 -0
  201. package/dist/lib/client/managed-keys.d.ts.map +1 -0
  202. package/dist/lib/client/managed-keys.js +190 -0
  203. package/dist/lib/client/managed-keys.js.map +1 -0
  204. package/dist/lib/client/policies.d.ts +35 -0
  205. package/dist/lib/client/policies.d.ts.map +1 -0
  206. package/dist/lib/client/policies.js +131 -0
  207. package/dist/lib/client/policies.js.map +1 -0
  208. package/dist/lib/client/tenants.d.ts +29 -0
  209. package/dist/lib/client/tenants.d.ts.map +1 -0
  210. package/dist/lib/client/tenants.js +56 -0
  211. package/dist/lib/client/tenants.js.map +1 -0
  212. package/dist/lib/client/types.d.ts +45 -0
  213. package/dist/lib/client/types.d.ts.map +1 -0
  214. package/dist/lib/client/types.js +3 -0
  215. package/dist/lib/client/types.js.map +1 -0
  216. package/dist/lib/client/users.d.ts +44 -0
  217. package/dist/lib/client/users.d.ts.map +1 -0
  218. package/dist/lib/client/users.js +116 -0
  219. package/dist/lib/client/users.js.map +1 -0
  220. package/dist/lib/client.d.ts +11 -246
  221. package/dist/lib/client.d.ts.map +1 -1
  222. package/dist/lib/client.js +10 -996
  223. package/dist/lib/client.js.map +1 -1
  224. package/dist/lib/command-error-handler.d.ts +99 -0
  225. package/dist/lib/command-error-handler.d.ts.map +1 -0
  226. package/dist/lib/command-error-handler.js +108 -0
  227. package/dist/lib/command-error-handler.js.map +1 -0
  228. package/dist/lib/config/apikey.d.ts +29 -0
  229. package/dist/lib/config/apikey.d.ts.map +1 -0
  230. package/dist/lib/config/apikey.js +58 -0
  231. package/dist/lib/config/apikey.js.map +1 -0
  232. package/dist/lib/config/cache.d.ts +47 -0
  233. package/dist/lib/config/cache.d.ts.map +1 -0
  234. package/dist/lib/config/cache.js +84 -0
  235. package/dist/lib/config/cache.js.map +1 -0
  236. package/dist/lib/config/credentials.d.ts +33 -0
  237. package/dist/lib/config/credentials.d.ts.map +1 -0
  238. package/dist/lib/config/credentials.js +70 -0
  239. package/dist/lib/config/credentials.js.map +1 -0
  240. package/dist/lib/config/getters.d.ts +28 -0
  241. package/dist/lib/config/getters.d.ts.map +1 -0
  242. package/dist/lib/config/getters.js +65 -0
  243. package/dist/lib/config/getters.js.map +1 -0
  244. package/dist/lib/config/index.d.ts +17 -0
  245. package/dist/lib/config/index.d.ts.map +1 -0
  246. package/dist/lib/config/index.js +21 -0
  247. package/dist/lib/config/index.js.map +1 -0
  248. package/dist/lib/config/migration.d.ts +10 -0
  249. package/dist/lib/config/migration.d.ts.map +1 -0
  250. package/dist/lib/config/migration.js +59 -0
  251. package/dist/lib/config/migration.js.map +1 -0
  252. package/dist/lib/config/plugins.d.ts +25 -0
  253. package/dist/lib/config/plugins.d.ts.map +1 -0
  254. package/dist/lib/config/plugins.js +58 -0
  255. package/dist/lib/config/plugins.js.map +1 -0
  256. package/dist/lib/config/profile.d.ts +42 -0
  257. package/dist/lib/config/profile.d.ts.map +1 -0
  258. package/dist/lib/config/profile.js +154 -0
  259. package/dist/lib/config/profile.js.map +1 -0
  260. package/dist/lib/config/store.d.ts +23 -0
  261. package/dist/lib/config/store.d.ts.map +1 -0
  262. package/dist/lib/config/store.js +41 -0
  263. package/dist/lib/config/store.js.map +1 -0
  264. package/dist/lib/config/types.d.ts +44 -0
  265. package/dist/lib/config/types.d.ts.map +1 -0
  266. package/dist/lib/config/types.js +8 -0
  267. package/dist/lib/config/types.js.map +1 -0
  268. package/dist/lib/config/validation.d.ts +38 -0
  269. package/dist/lib/config/validation.d.ts.map +1 -0
  270. package/dist/lib/config/validation.js +146 -0
  271. package/dist/lib/config/validation.js.map +1 -0
  272. package/dist/lib/config.d.ts +5 -158
  273. package/dist/lib/config.d.ts.map +1 -1
  274. package/dist/lib/config.js +26 -424
  275. package/dist/lib/config.js.map +1 -1
  276. package/dist/lib/constants.d.ts +65 -0
  277. package/dist/lib/constants.d.ts.map +1 -0
  278. package/dist/lib/constants.js +90 -0
  279. package/dist/lib/constants.js.map +1 -0
  280. package/dist/lib/db.d.ts +4 -0
  281. package/dist/lib/db.d.ts.map +1 -1
  282. package/dist/lib/db.js +77 -46
  283. package/dist/lib/db.js.map +1 -1
  284. package/dist/lib/format-helpers.d.ts +63 -0
  285. package/dist/lib/format-helpers.d.ts.map +1 -0
  286. package/dist/lib/format-helpers.js +219 -0
  287. package/dist/lib/format-helpers.js.map +1 -0
  288. package/dist/lib/visual.d.ts +22 -0
  289. package/dist/lib/visual.d.ts.map +1 -1
  290. package/dist/lib/visual.js +83 -0
  291. package/dist/lib/visual.js.map +1 -1
  292. package/dist/services/signature-verifier.d.ts.map +1 -1
  293. package/dist/services/signature-verifier.js +11 -4
  294. package/dist/services/signature-verifier.js.map +1 -1
  295. package/dist/services/update-installer.d.ts +7 -0
  296. package/dist/services/update-installer.d.ts.map +1 -1
  297. package/dist/services/update-installer.js +58 -19
  298. package/dist/services/update-installer.js.map +1 -1
  299. package/package.json +1 -1
@@ -1,1297 +1,10 @@
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
- // ============================================================================
8
- // Helper Functions
9
- // ============================================================================
10
- function formatDate(dateStr) {
11
- return new Date(dateStr).toLocaleString();
12
- }
13
- function getDaysUntilExpiry(expiresAt) {
14
- const expires = new Date(expiresAt);
15
- const now = new Date();
16
- return Math.ceil((expires.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
17
- }
18
- function formatSecondsToHuman(seconds) {
19
- if (seconds < 60)
20
- return `${seconds}s`;
21
- if (seconds < 3600)
22
- return `${Math.floor(seconds / 60)}m`;
23
- if (seconds < 86400)
24
- return `${Math.floor(seconds / 3600)}h`;
25
- return `${Math.floor(seconds / 86400)}d`;
26
- }
27
- function formatExpiry(expiresAt) {
28
- const days = getDaysUntilExpiry(expiresAt);
29
- if (days < 0)
30
- return `Expired ${Math.abs(days)} days ago`;
31
- if (days === 0)
32
- return 'Expires today';
33
- if (days === 1)
34
- return 'Expires tomorrow';
35
- if (days <= 7)
36
- return `Expires in ${days} days (!)`;
37
- if (days <= 30)
38
- return `Expires in ${days} days`;
39
- return `Expires in ${days} days`;
40
- }
41
- function formatPermissions(permissions) {
42
- if (permissions.length === 0)
43
- return 'None';
44
- if (permissions.length <= 3)
45
- return permissions.join(', ');
46
- return `${permissions.slice(0, 2).join(', ')} +${permissions.length - 2} more`;
47
- }
48
- function formatConditionsSummary(conditions) {
49
- if (!conditions || Object.keys(conditions).length === 0)
50
- return '-';
51
- const parts = [];
52
- if (conditions.ip)
53
- parts.push('IP');
54
- if (conditions.timeRange)
55
- parts.push('Time');
56
- if (conditions.methods)
57
- parts.push('Methods');
58
- if (conditions.resources)
59
- parts.push('Resources');
60
- if (conditions.aliases)
61
- parts.push('Aliases');
62
- if (conditions.resourceTags)
63
- parts.push('Tags');
64
- if (parts.length === 0)
65
- return '-';
66
- if (parts.length <= 2)
67
- return parts.join(', ');
68
- return `${parts.slice(0, 2).join(', ')} +${parts.length - 2}`;
69
- }
70
- function displayConditions(cond) {
71
- if (cond.ip)
72
- console.log(` - IP Allowlist: ${cond.ip.join(', ')}`);
73
- if (cond.timeRange) {
74
- const tr = cond.timeRange;
75
- console.log(` - Time Range: ${tr.start}-${tr.end} ${tr.timezone ?? 'UTC'}`);
76
- }
77
- if (cond.methods)
78
- console.log(` - Methods: ${cond.methods.join(', ')}`);
79
- if (cond.resources)
80
- console.log(` - Resources: ${JSON.stringify(cond.resources)}`);
81
- if (cond.aliases)
82
- console.log(` - Aliases: ${cond.aliases.join(', ')}`);
83
- if (cond.resourceTags)
84
- console.log(` - Tags: ${JSON.stringify(cond.resourceTags)}`);
85
- }
86
- // Managed key helper functions
87
- function formatRotationMode(mode) {
88
- switch (mode) {
89
- case 'scheduled': return 'Scheduled';
90
- case 'on-use': return 'On Use';
91
- case 'on-bind': return 'On Bind';
92
- default: return mode;
93
- }
94
- }
95
- function formatTimeUntil(dateStr) {
96
- if (!dateStr)
97
- return '-';
98
- const date = new Date(dateStr);
99
- const now = new Date();
100
- const diffMs = date.getTime() - now.getTime();
101
- if (diffMs <= 0)
102
- return 'Now';
103
- const diffMins = Math.floor(diffMs / 60000);
104
- if (diffMins < 60)
105
- return `${diffMins}m`;
106
- const diffHours = Math.floor(diffMins / 60);
107
- if (diffHours < 24)
108
- return `${diffHours}h ${diffMins % 60}m`;
109
- const diffDays = Math.floor(diffHours / 24);
110
- return `${diffDays}d ${diffHours % 24}h`;
111
- }
112
- function displayManagedKeyDetails(key) {
113
- const statusIcon = key.enabled ? '\x1b[32m●\x1b[0m Active' : '\x1b[31m○\x1b[0m Disabled';
114
- output.keyValue({
115
- 'Name': key.name,
116
- 'Key ID': key.id,
117
- 'Prefix': key.prefix,
118
- 'Status': statusIcon,
119
- 'Tenant': key.tenant_id,
120
- 'Description': key.description ?? 'None',
121
- 'Rotation Mode': formatRotationMode(key.rotation_mode),
122
- 'Rotation Interval': key.rotation_interval ?? '-',
123
- 'Grace Period': key.grace_period,
124
- 'Next Rotation': key.next_rotation_at ? `${formatDate(key.next_rotation_at)} (${formatTimeUntil(key.next_rotation_at)})` : '-',
125
- 'Last Bound': key.last_bound_at ? formatDate(key.last_bound_at) : 'Never',
126
- 'Rotation Count': key.rotation_count,
127
- 'Last Rotation': key.last_rotation ? formatDate(key.last_rotation) : 'Never',
128
- 'Expires': formatDate(key.expires_at),
129
- 'Created': formatDate(key.created_at),
130
- 'Created By': key.created_by_username ?? key.created_by ?? 'Unknown',
131
- });
132
- if (key.notify_before) {
133
- console.log(`\nNotifications: ${key.notify_before} before rotation`);
134
- }
135
- if (key.webhook_url) {
136
- console.log(`Webhook: ${key.webhook_url}`);
137
- }
138
- if (key.permissions.length > 0) {
139
- console.log('\nPermissions:');
140
- for (const perm of key.permissions) {
141
- console.log(` - ${perm}`);
142
- }
143
- }
144
- const keyConditions = key.conditions;
145
- if (keyConditions && Object.keys(keyConditions).length > 0) {
146
- console.log('\nConditions:');
147
- displayConditions(keyConditions);
148
- }
149
- }
150
- // ============================================================================
151
- // Command Registration
152
- // ============================================================================
153
- export function registerApiKeyCommands(program) {
154
- const apiKeyCmd = program
155
- .command('apikey')
156
- .alias('api-key')
157
- .description('API key management (independent, tenant-scoped)');
158
- // List API keys
159
- apiKeyCmd
160
- .command('list')
161
- .alias('ls')
162
- .description('List API keys')
163
- .option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
164
- .option('--json', 'Output as JSON')
165
- .action(async (options) => {
166
- const spinner = ora('Fetching API keys...').start();
167
- try {
168
- const result = await client.listApiKeys(options.tenant);
169
- spinner.stop();
170
- if (options.json) {
171
- output.json(result);
172
- return;
173
- }
174
- if (result.items.length === 0) {
175
- output.warn('No API keys found');
176
- return;
177
- }
178
- // Show expiring soon warning
179
- if (result.expiringSoon.length > 0) {
180
- console.log(`\n⚠️ ${result.expiringSoon.length} key(s) expiring within 7 days\n`);
181
- }
182
- const table = new Table({
183
- head: ['Name', 'Prefix', 'Type', 'Status', 'Tenant', 'Permissions', 'Expires', 'Rotations'],
184
- style: { head: ['cyan'] },
185
- });
186
- for (const key of result.items) {
187
- const daysLeft = getDaysUntilExpiry(key.expires_at);
188
- const expiryColor = daysLeft <= 7 ? '\x1b[31m' : daysLeft <= 30 ? '\x1b[33m' : '';
189
- const reset = expiryColor ? '\x1b[0m' : '';
190
- const statusIcon = key.enabled ? '\x1b[32m●\x1b[0m' : '\x1b[31m○\x1b[0m';
191
- const statusText = key.enabled ? 'Active' : 'Disabled';
192
- // Format key type (static vs managed with rotation info)
193
- let keyType = 'Static';
194
- if (key.is_managed && key.rotation_mode) {
195
- const mode = key.rotation_mode.replace('on-', '');
196
- const interval = key.rotation_interval_seconds
197
- ? formatSecondsToHuman(key.rotation_interval_seconds)
198
- : '';
199
- keyType = interval ? `${mode}/${interval}` : mode;
200
- }
201
- table.push([
202
- key.name,
203
- key.prefix,
204
- keyType,
205
- `${statusIcon} ${statusText}`,
206
- key.tenant_id,
207
- formatPermissions(key.permissions),
208
- `${expiryColor}${formatExpiry(key.expires_at)}${reset}`,
209
- key.rotation_count > 0 ? `${key.rotation_count}x` : '-',
210
- ]);
211
- }
212
- console.log(table.toString());
213
- const showingInfo = result.pagination.hasMore
214
- ? `Showing ${result.items.length} of ${result.pagination.total}`
215
- : `Total: ${result.pagination.total}`;
216
- console.log(`\n${showingInfo} API key(s)`);
217
- }
218
- catch (err) {
219
- spinner.fail('Failed to list API keys');
220
- output.error(err instanceof Error ? err.message : String(err));
221
- process.exit(1);
222
- }
223
- });
224
- // Create API key
225
- apiKeyCmd
226
- .command('create <name>')
227
- .description('Create a new API key with direct permissions')
228
- .option('-e, --expires <days>', 'Days until expiration (1-3650, default: 90)', '90')
229
- .option('-p, --permissions <perms>', 'Comma-separated permissions (required)')
230
- .option('-d, --description <desc>', 'Description')
231
- .option('--ip <ips>', 'Comma-separated IP allowlist (CIDR supported)')
232
- .option('--time-range <range>', 'Time range restriction: "HH:MM-HH:MM [TIMEZONE]"')
233
- .option('--methods <methods>', 'Comma-separated allowed HTTP methods: GET,POST,etc')
234
- .option('--resources <ids>', 'Specific resource IDs (type:id,...): secrets:id1,certificates:id2')
235
- .option('--aliases <patterns>', 'Comma-separated alias patterns (glob): prod/*,api/*')
236
- .option('--tags <tags>', 'Required resource tags: key=value,key2=value2')
237
- .option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
238
- .option('--json', 'Output as JSON')
239
- .action(async (name, options) => {
240
- // Validate permissions
241
- if (!options.permissions) {
242
- output.error('--permissions is required. Use comma-separated permission strings.');
243
- output.info('Example: --permissions "secret:read:value,secret:list:values"');
244
- process.exit(1);
245
- }
246
- const permissions = options.permissions.split(',').map((p) => p.trim());
247
- const spinner = ora('Creating API key...').start();
248
- try {
249
- // Parse options
250
- const expiresInDays = parseInt(options.expires, 10);
251
- if (isNaN(expiresInDays) || expiresInDays < 1 || expiresInDays > 3650) {
252
- spinner.fail('Invalid expiration');
253
- output.error('Expiration must be between 1 and 3650 days');
254
- process.exit(1);
255
- }
256
- let ipAllowlist;
257
- if (options.ip) {
258
- ipAllowlist = options.ip.split(',').map((ip) => ip.trim());
259
- }
260
- // Parse conditions
261
- const conditions = {};
262
- // IP condition (from --ip flag, now also stored in conditions)
263
- if (ipAllowlist) {
264
- conditions.ip = ipAllowlist;
265
- }
266
- // Time range condition
267
- if (options.timeRange) {
268
- const match = /^(\d{2}:\d{2})-(\d{2}:\d{2})(?:\s+(.+))?$/.exec(options.timeRange);
269
- if (!match) {
270
- spinner.fail('Invalid time range format');
271
- output.error('Use format: "HH:MM-HH:MM [TIMEZONE]"');
272
- output.info('Example: --time-range "09:00-17:00 America/New_York"');
273
- process.exit(1);
274
- }
275
- conditions.timeRange = {
276
- start: match[1],
277
- end: match[2],
278
- timezone: match[3] || 'UTC',
279
- };
280
- }
281
- // HTTP methods condition
282
- if (options.methods) {
283
- conditions.methods = options.methods.split(',').map((m) => m.trim().toUpperCase());
284
- }
285
- // Resource IDs condition
286
- if (options.resources) {
287
- const resources = {};
288
- for (const part of options.resources.split(',')) {
289
- const [type, id] = part.split(':');
290
- if (type && id) {
291
- resources[type] = resources[type] ?? [];
292
- resources[type].push(id);
293
- }
294
- }
295
- if (Object.keys(resources).length > 0) {
296
- conditions.resources = resources;
297
- }
298
- }
299
- // Alias patterns condition
300
- if (options.aliases) {
301
- conditions.aliases = options.aliases.split(',').map((a) => a.trim());
302
- }
303
- // Resource tags condition
304
- if (options.tags) {
305
- const tags = {};
306
- for (const part of options.tags.split(',')) {
307
- const [key, value] = part.split('=');
308
- if (key && value) {
309
- tags[key.trim()] = value.trim();
310
- }
311
- }
312
- if (Object.keys(tags).length > 0) {
313
- conditions.resourceTags = tags;
314
- }
315
- }
316
- const result = await client.createApiKey({
317
- name,
318
- description: options.description,
319
- expiresInDays,
320
- permissions,
321
- ipAllowlist,
322
- conditions: Object.keys(conditions).length > 0 ? conditions : undefined,
323
- tenantId: options.tenant,
324
- });
325
- spinner.succeed('API key created');
326
- if (options.json) {
327
- output.json(result);
328
- return;
329
- }
330
- console.log('\n⚠️ IMPORTANT: Save this key now - it will not be shown again!\n');
331
- console.log('────────────────────────────────────────────────────────────────');
332
- console.log(`API Key: ${result.key}`);
333
- console.log('────────────────────────────────────────────────────────────────\n');
334
- output.keyValue({
335
- 'Key ID': result.apiKey.id,
336
- 'Name': result.apiKey.name,
337
- 'Prefix': result.apiKey.prefix,
338
- 'Status': result.apiKey.enabled ? '\x1b[32m●\x1b[0m Active' : '\x1b[31m○\x1b[0m Disabled',
339
- 'Tenant': result.apiKey.tenant_id,
340
- 'Description': result.apiKey.description ?? 'None',
341
- 'Expires': formatDate(result.apiKey.expires_at),
342
- 'IP Allowlist': result.apiKey.ip_allowlist?.join(', ') ?? 'None',
343
- });
344
- if (result.apiKey.permissions.length > 0) {
345
- console.log('\nPermissions:');
346
- for (const perm of result.apiKey.permissions) {
347
- console.log(` - ${perm}`);
348
- }
349
- }
350
- // Display conditions if any
351
- const apiKeyConditions = result.apiKey.conditions;
352
- if (apiKeyConditions && Object.keys(apiKeyConditions).length > 0) {
353
- console.log('\nConditions:');
354
- displayConditions(apiKeyConditions);
355
- }
356
- }
357
- catch (err) {
358
- spinner.fail('Failed to create API key');
359
- output.error(err instanceof Error ? err.message : String(err));
360
- process.exit(1);
361
- }
362
- });
363
- // Show API key details
364
- apiKeyCmd
365
- .command('show <id>')
366
- .description('Show API key details')
367
- .option('-t, --tenant <id>', 'Tenant ID')
368
- .option('--json', 'Output as JSON')
369
- .action(async (id, options) => {
370
- const spinner = ora('Fetching API key...').start();
371
- try {
372
- // First try to get by ID directly
373
- let key;
374
- try {
375
- key = await client.getApiKey(id, options.tenant);
376
- }
377
- catch {
378
- // Fall back to list and search
379
- const result = await client.listApiKeys(options.tenant);
380
- key = result.items.find(k => k.id === id || k.prefix === id || k.name === id);
381
- }
382
- if (!key) {
383
- spinner.fail('API key not found');
384
- output.error(`No API key found matching: ${id}`);
385
- process.exit(1);
386
- }
387
- spinner.stop();
388
- if (options.json) {
389
- output.json(key);
390
- return;
391
- }
392
- const daysLeft = getDaysUntilExpiry(key.expires_at);
393
- const statusIcon = key.enabled ? '\x1b[32m●\x1b[0m Active' : '\x1b[31m○\x1b[0m Disabled';
394
- output.keyValue({
395
- 'Key ID': key.id,
396
- 'Name': key.name,
397
- 'Prefix': key.prefix,
398
- 'Status': statusIcon,
399
- 'Tenant': key.tenant_id,
400
- 'Description': key.description ?? 'None',
401
- 'Created By': key.created_by_username ?? key.created_by ?? 'Unknown',
402
- 'Created': formatDate(key.created_at),
403
- 'Expires': formatDate(key.expires_at),
404
- 'Days Until Expiry': daysLeft,
405
- 'Last Used': key.last_used ? formatDate(key.last_used) : 'Never',
406
- 'Rotation Count': key.rotation_count,
407
- 'Last Rotation': key.last_rotation ? formatDate(key.last_rotation) : 'Never',
408
- 'IP Allowlist': key.ip_allowlist?.join(', ') ?? 'None (any IP)',
409
- });
410
- if (key.permissions.length > 0) {
411
- console.log('\nPermissions:');
412
- for (const perm of key.permissions) {
413
- console.log(` - ${perm}`);
414
- }
415
- }
416
- // Display conditions if any
417
- const keyConditions = key.conditions;
418
- if (keyConditions && Object.keys(keyConditions).length > 0) {
419
- console.log('\nConditions:');
420
- displayConditions(keyConditions);
421
- }
422
- if (!key.enabled) {
423
- console.log('\n⚠️ This key is disabled and cannot be used for authentication.');
424
- }
425
- else if (daysLeft <= 7) {
426
- console.log('\n⚠️ This key is expiring soon! Consider rotating it.');
427
- }
428
- }
429
- catch (err) {
430
- spinner.fail('Failed to fetch API key');
431
- output.error(err instanceof Error ? err.message : String(err));
432
- process.exit(1);
433
- }
434
- });
435
- // Delete API key
436
- apiKeyCmd
437
- .command('delete <id>')
438
- .alias('rm')
439
- .description('Delete an API key')
440
- .option('-t, --tenant <id>', 'Tenant ID')
441
- .option('-f, --force', 'Skip confirmation')
442
- .action(async (id, options) => {
443
- if (!options.force) {
444
- output.warn(`This will permanently delete API key: ${id}`);
445
- output.warn('The key will stop working immediately.');
446
- }
447
- const spinner = ora('Deleting API key...').start();
448
- try {
449
- await client.deleteApiKey(id, options.tenant);
450
- spinner.succeed(`API key deleted: ${id}`);
451
- }
452
- catch (err) {
453
- spinner.fail('Failed to delete API key');
454
- output.error(err instanceof Error ? err.message : String(err));
455
- process.exit(1);
456
- }
457
- });
458
- // Rotate API key
459
- apiKeyCmd
460
- .command('rotate <id>')
461
- .description('Rotate an API key (creates new key, invalidates old)')
462
- .option('-n, --name <name>', 'New name for the rotated key')
463
- .option('-t, --tenant <id>', 'Tenant ID')
464
- .option('--json', 'Output as JSON')
465
- .action(async (id, options) => {
466
- const spinner = ora('Rotating API key...').start();
467
- try {
468
- const result = await client.rotateApiKey(id, options.name, options.tenant);
469
- spinner.succeed('API key rotated');
470
- if (options.json) {
471
- output.json(result);
472
- return;
473
- }
474
- console.log('\n⚠️ IMPORTANT: Save this new key now - it will not be shown again!');
475
- console.log('The old key has been invalidated.\n');
476
- console.log('────────────────────────────────────────────────────────────────');
477
- console.log(`New API Key: ${result.key}`);
478
- console.log('────────────────────────────────────────────────────────────────\n');
479
- output.keyValue({
480
- 'New Key ID': result.apiKey.id,
481
- 'Name': result.apiKey.name,
482
- 'Prefix': result.apiKey.prefix,
483
- 'Expires': formatDate(result.apiKey.expires_at),
484
- 'Rotation Count': result.apiKey.rotation_count,
485
- });
486
- }
487
- catch (err) {
488
- spinner.fail('Failed to rotate API key');
489
- output.error(err instanceof Error ? err.message : String(err));
490
- process.exit(1);
491
- }
492
- });
493
- // Enable API key
494
- apiKeyCmd
495
- .command('enable <id>')
496
- .description('Enable an API key (allow authentication)')
497
- .option('-t, --tenant <id>', 'Tenant ID')
498
- .action(async (id, options) => {
499
- const spinner = ora('Enabling API key...').start();
500
- try {
501
- const key = await client.setApiKeyEnabled(id, true, options.tenant);
502
- spinner.succeed(`API key enabled: ${key.name}`);
503
- console.log('\nThe key can now be used for authentication.');
504
- }
505
- catch (err) {
506
- spinner.fail('Failed to enable API key');
507
- output.error(err instanceof Error ? err.message : String(err));
508
- process.exit(1);
509
- }
510
- });
511
- // Disable API key
512
- apiKeyCmd
513
- .command('disable <id>')
514
- .description('Disable an API key (block authentication without deleting)')
515
- .option('-t, --tenant <id>', 'Tenant ID')
516
- .action(async (id, options) => {
517
- const spinner = ora('Disabling API key...').start();
518
- try {
519
- const key = await client.setApiKeyEnabled(id, false, options.tenant);
520
- spinner.succeed(`API key disabled: ${key.name}`);
521
- console.log('\nThe key is now blocked from authentication.');
522
- console.log('Use "znvault apikey enable" to re-enable it.');
523
- }
524
- catch (err) {
525
- spinner.fail('Failed to disable API key');
526
- output.error(err instanceof Error ? err.message : String(err));
527
- process.exit(1);
528
- }
529
- });
530
- // Update permissions
531
- apiKeyCmd
532
- .command('update-permissions <id>')
533
- .description('Update API key permissions')
534
- .option('-s, --set <perms>', 'Set permissions (comma-separated, replaces all)')
535
- .option('-a, --add <perms>', 'Add permissions (comma-separated)')
536
- .option('-r, --remove <perms>', 'Remove permissions (comma-separated)')
537
- .option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
538
- .option('--json', 'Output as JSON')
539
- .action(async (id, options) => {
540
- if (!options.set && !options.add && !options.remove) {
541
- output.error('At least one of --set, --add, or --remove is required');
542
- output.info('Examples:');
543
- output.info(' znvault apikey update-permissions <id> --set "secret:read,secret:list"');
544
- output.info(' znvault apikey update-permissions <id> --add "kms:encrypt"');
545
- output.info(' znvault apikey update-permissions <id> --remove "secret:delete"');
546
- process.exit(1);
547
- }
548
- const spinner = ora('Updating permissions...').start();
549
- try {
550
- let newPermissions;
551
- if (options.set) {
552
- // Replace all permissions
553
- newPermissions = options.set.split(',').map((p) => p.trim());
554
- }
555
- else {
556
- // Need to get current permissions first
557
- const currentKey = await client.getApiKey(id, options.tenant);
558
- newPermissions = [...currentKey.permissions];
559
- if (options.add) {
560
- const toAdd = options.add.split(',').map((p) => p.trim());
561
- for (const perm of toAdd) {
562
- if (!newPermissions.includes(perm)) {
563
- newPermissions.push(perm);
564
- }
565
- }
566
- }
567
- if (options.remove) {
568
- const toRemove = options.remove.split(',').map((p) => p.trim());
569
- newPermissions = newPermissions.filter((p) => !toRemove.includes(p));
570
- }
571
- }
572
- if (newPermissions.length === 0) {
573
- spinner.fail('Cannot remove all permissions');
574
- output.error('API key must have at least one permission');
575
- process.exit(1);
576
- }
577
- const key = await client.updateApiKeyPermissions(id, newPermissions, options.tenant);
578
- spinner.succeed('Permissions updated');
579
- if (options.json) {
580
- output.json(key);
581
- return;
582
- }
583
- console.log('\nUpdated permissions:');
584
- for (const perm of key.permissions) {
585
- console.log(` - ${perm}`);
586
- }
587
- }
588
- catch (err) {
589
- spinner.fail('Failed to update permissions');
590
- output.error(err instanceof Error ? err.message : String(err));
591
- process.exit(1);
592
- }
593
- });
594
- // Update conditions
595
- apiKeyCmd
596
- .command('update-conditions <id>')
597
- .description('Update API key inline ABAC conditions')
598
- .option('--ip <ips>', 'Comma-separated IP allowlist (CIDR supported)')
599
- .option('--time-range <range>', 'Time range: "HH:MM-HH:MM [TIMEZONE]" or "clear"')
600
- .option('--methods <methods>', 'Comma-separated HTTP methods or "clear"')
601
- .option('--resources <ids>', 'Resource IDs (type:id,...) or "clear"')
602
- .option('--aliases <patterns>', 'Alias patterns (glob) or "clear"')
603
- .option('--tags <tags>', 'Resource tags (key=value,...) or "clear"')
604
- .option('--clear-all', 'Remove all conditions')
605
- .option('-t, --tenant <id>', 'Tenant ID')
606
- .option('--json', 'Output as JSON')
607
- .action(async (id, options) => {
608
- const conditions = {};
609
- // Handle --clear-all
610
- if (!options.clearAll) {
611
- // Parse individual conditions
612
- if (options.ip && options.ip !== 'clear') {
613
- conditions.ip = options.ip.split(',').map((ip) => ip.trim());
614
- }
615
- if (options.timeRange) {
616
- if (options.timeRange !== 'clear') {
617
- const match = /^(\d{2}:\d{2})-(\d{2}:\d{2})(?:\s+(.+))?$/.exec(options.timeRange);
618
- if (!match) {
619
- output.error('Invalid time range format. Use: "HH:MM-HH:MM [TIMEZONE]"');
620
- process.exit(1);
621
- }
622
- conditions.timeRange = {
623
- start: match[1],
624
- end: match[2],
625
- timezone: match[3] || 'UTC',
626
- };
627
- }
628
- }
629
- if (options.methods && options.methods !== 'clear') {
630
- conditions.methods = options.methods.split(',').map((m) => m.trim().toUpperCase());
631
- }
632
- if (options.resources && options.resources !== 'clear') {
633
- const resources = {};
634
- for (const part of options.resources.split(',')) {
635
- const [type, resId] = part.split(':');
636
- if (type && resId) {
637
- resources[type] = resources[type] ?? [];
638
- resources[type].push(resId);
639
- }
640
- }
641
- if (Object.keys(resources).length > 0) {
642
- conditions.resources = resources;
643
- }
644
- }
645
- if (options.aliases && options.aliases !== 'clear') {
646
- conditions.aliases = options.aliases.split(',').map((a) => a.trim());
647
- }
648
- if (options.tags && options.tags !== 'clear') {
649
- const tags = {};
650
- for (const part of options.tags.split(',')) {
651
- const [key, value] = part.split('=');
652
- if (key && value) {
653
- tags[key.trim()] = value.trim();
654
- }
655
- }
656
- if (Object.keys(tags).length > 0) {
657
- conditions.resourceTags = tags;
658
- }
659
- }
660
- }
661
- const spinner = ora('Updating conditions...').start();
662
- try {
663
- const key = await client.updateApiKeyConditions(id, conditions, options.tenant);
664
- spinner.succeed('Conditions updated');
665
- if (options.json) {
666
- output.json(key);
667
- return;
668
- }
669
- const keyConditions = key.conditions;
670
- if (keyConditions && Object.keys(keyConditions).length > 0) {
671
- console.log('\nUpdated conditions:');
672
- displayConditions(keyConditions);
673
- }
674
- else {
675
- console.log('\nAll conditions cleared.');
676
- }
677
- }
678
- catch (err) {
679
- spinner.fail('Failed to update conditions');
680
- output.error(err instanceof Error ? err.message : String(err));
681
- process.exit(1);
682
- }
683
- });
684
- // List policies attached to an API key
685
- apiKeyCmd
686
- .command('list-policies <id>')
687
- .description('List ABAC policies attached to an API key')
688
- .option('-t, --tenant <id>', 'Tenant ID')
689
- .option('--json', 'Output as JSON')
690
- .action(async (id, options) => {
691
- const spinner = ora('Fetching policies...').start();
692
- try {
693
- const result = await client.getApiKeyPolicies(id, options.tenant);
694
- spinner.stop();
695
- if (options.json) {
696
- output.json(result);
697
- return;
698
- }
699
- if (result.policies.length === 0) {
700
- output.info('No ABAC policies attached to this API key');
701
- return;
702
- }
703
- const table = new Table({
704
- head: ['Policy ID', 'Policy Name', 'Attached At'],
705
- style: { head: ['cyan'] },
706
- });
707
- for (const policy of result.policies) {
708
- table.push([
709
- policy.policyId,
710
- policy.policyName,
711
- formatDate(policy.attachedAt),
712
- ]);
713
- }
714
- console.log(table.toString());
715
- console.log(`\nTotal: ${result.policies.length} policy/policies`);
716
- }
717
- catch (err) {
718
- spinner.fail('Failed to fetch policies');
719
- output.error(err instanceof Error ? err.message : String(err));
720
- process.exit(1);
721
- }
722
- });
723
- // Attach policy to API key
724
- apiKeyCmd
725
- .command('attach-policy <keyId> <policyId>')
726
- .description('Attach an ABAC policy to an API key')
727
- .option('-t, --tenant <id>', 'Tenant ID')
728
- .action(async (keyId, policyId, options) => {
729
- const spinner = ora('Attaching policy...').start();
730
- try {
731
- await client.attachApiKeyPolicy(keyId, policyId, options.tenant);
732
- spinner.succeed(`Policy ${policyId} attached to API key ${keyId}`);
733
- }
734
- catch (err) {
735
- spinner.fail('Failed to attach policy');
736
- output.error(err instanceof Error ? err.message : String(err));
737
- process.exit(1);
738
- }
739
- });
740
- // Detach policy from API key
741
- apiKeyCmd
742
- .command('detach-policy <keyId> <policyId>')
743
- .description('Detach an ABAC policy from an API key')
744
- .option('-t, --tenant <id>', 'Tenant ID')
745
- .action(async (keyId, policyId, options) => {
746
- const spinner = ora('Detaching policy...').start();
747
- try {
748
- await client.detachApiKeyPolicy(keyId, policyId, options.tenant);
749
- spinner.succeed(`Policy ${policyId} detached from API key ${keyId}`);
750
- }
751
- catch (err) {
752
- spinner.fail('Failed to detach policy');
753
- output.error(err instanceof Error ? err.message : String(err));
754
- process.exit(1);
755
- }
756
- });
757
- // Self-info (when using API key auth)
758
- apiKeyCmd
759
- .command('self')
760
- .description('Show info about the currently used API key')
761
- .option('--json', 'Output as JSON')
762
- .action(async (options) => {
763
- const spinner = ora('Fetching API key info...').start();
764
- try {
765
- const result = await client.getApiKeySelf();
766
- spinner.stop();
767
- if (options.json) {
768
- output.json(result);
769
- return;
770
- }
771
- const statusIcon = result.apiKey.enabled ? '\x1b[32m●\x1b[0m Active' : '\x1b[31m○\x1b[0m Disabled';
772
- output.keyValue({
773
- 'Key ID': result.apiKey.id,
774
- 'Name': result.apiKey.name,
775
- 'Prefix': result.apiKey.prefix,
776
- 'Status': statusIcon,
777
- 'Tenant': result.apiKey.tenant_id,
778
- 'Description': result.apiKey.description ?? 'None',
779
- 'Expires': formatDate(result.apiKey.expires_at),
780
- 'Days Until Expiry': result.expiresInDays,
781
- 'Expiring Soon': result.isExpiringSoon ? 'Yes (!)' : 'No',
782
- 'Last Used': result.apiKey.last_used ? formatDate(result.apiKey.last_used) : 'Never',
783
- 'Rotation Count': result.apiKey.rotation_count,
784
- 'Last Rotation': result.apiKey.last_rotation ? formatDate(result.apiKey.last_rotation) : 'Never',
785
- });
786
- if (result.apiKey.permissions.length > 0) {
787
- console.log('\nPermissions:');
788
- for (const perm of result.apiKey.permissions) {
789
- console.log(` - ${perm}`);
790
- }
791
- }
792
- // Display conditions if any
793
- const apiKeyConditions = result.apiKey.conditions;
794
- if (apiKeyConditions && Object.keys(apiKeyConditions).length > 0) {
795
- console.log('\nConditions:');
796
- displayConditions(apiKeyConditions);
797
- }
798
- if (result.isExpiringSoon) {
799
- console.log('\n⚠️ This key is expiring soon! Run "znvault apikey self-rotate" to rotate it.');
800
- }
801
- }
802
- catch (err) {
803
- spinner.fail('Failed to fetch API key info');
804
- output.error(err instanceof Error ? err.message : String(err));
805
- console.log('\nNote: This command only works when authenticated via API key (X-API-Key header).');
806
- process.exit(1);
807
- }
808
- });
809
- // Self-rotate (when using API key auth)
810
- apiKeyCmd
811
- .command('self-rotate')
812
- .description('Rotate the currently used API key')
813
- .option('-n, --name <name>', 'New name for the rotated key')
814
- .option('--json', 'Output as JSON')
815
- .action(async (options) => {
816
- const spinner = ora('Rotating API key...').start();
817
- try {
818
- const result = await client.rotateApiKeySelf(options.name);
819
- spinner.succeed('API key rotated');
820
- if (options.json) {
821
- output.json(result);
822
- return;
823
- }
824
- console.log('\n⚠️ IMPORTANT: Save this new key now - it will not be shown again!');
825
- console.log('The old key has been invalidated.\n');
826
- console.log('────────────────────────────────────────────────────────────────');
827
- console.log(`New API Key: ${result.key}`);
828
- console.log('────────────────────────────────────────────────────────────────\n');
829
- output.keyValue({
830
- 'New Key ID': result.apiKey.id,
831
- 'Name': result.apiKey.name,
832
- 'Prefix': result.apiKey.prefix,
833
- 'Expires': formatDate(result.apiKey.expires_at),
834
- 'Days Until Expiry': result.expiresInDays,
835
- 'Rotation Count': result.apiKey.rotation_count,
836
- });
837
- }
838
- catch (err) {
839
- spinner.fail('Failed to rotate API key');
840
- output.error(err instanceof Error ? err.message : String(err));
841
- console.log('\nNote: This command only works when authenticated via API key (X-API-Key header).');
842
- process.exit(1);
843
- }
844
- });
845
- // ============================================================================
846
- // Managed API Key Commands
847
- // ============================================================================
848
- const managedCmd = apiKeyCmd
849
- .command('managed')
850
- .description('Managed API key operations (auto-rotating keys)');
851
- // List managed keys
852
- managedCmd
853
- .command('list')
854
- .alias('ls')
855
- .description('List all managed API keys')
856
- .option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
857
- .option('--json', 'Output as JSON')
858
- .action(async (options) => {
859
- const spinner = ora('Fetching managed API keys...').start();
860
- try {
861
- const result = await client.listManagedApiKeys(options.tenant);
862
- spinner.stop();
863
- if (options.json) {
864
- output.json(result);
865
- return;
866
- }
867
- if (result.items.length === 0) {
868
- output.warn('No managed API keys found');
869
- return;
870
- }
871
- const table = new Table({
872
- head: ['Name', 'Mode', 'Interval', 'Grace', 'Next Rotation', 'Status', 'Tenant', 'Rotations'],
873
- style: { head: ['cyan'] },
874
- });
875
- for (const key of result.items) {
876
- const statusIcon = key.enabled ? '\x1b[32m●\x1b[0m' : '\x1b[31m○\x1b[0m';
877
- const nextRotation = key.next_rotation_at ? formatTimeUntil(key.next_rotation_at) : '-';
878
- table.push([
879
- key.name,
880
- formatRotationMode(key.rotation_mode),
881
- key.rotation_interval ?? '-',
882
- key.grace_period,
883
- nextRotation,
884
- `${statusIcon} ${key.enabled ? 'Active' : 'Disabled'}`,
885
- key.tenant_id,
886
- key.rotation_count > 0 ? `${key.rotation_count}x` : '-',
887
- ]);
888
- }
889
- console.log(table.toString());
890
- const showingInfo = result.pagination.hasMore
891
- ? `Showing ${result.items.length} of ${result.pagination.total}`
892
- : `Total: ${result.pagination.total}`;
893
- console.log(`\n${showingInfo} managed API key(s)`);
894
- }
895
- catch (err) {
896
- spinner.fail('Failed to list managed API keys');
897
- output.error(err instanceof Error ? err.message : String(err));
898
- process.exit(1);
899
- }
900
- });
901
- // Create managed key
902
- managedCmd
903
- .command('create <name>')
904
- .description('Create a new managed API key with auto-rotation')
905
- .option('-e, --expires <days>', 'Days until expiration (1-3650, default: 365)', '365')
906
- .option('-p, --permissions <perms>', 'Comma-separated permissions (required)')
907
- .option('-d, --description <desc>', 'Description')
908
- .option('-m, --rotation-mode <mode>', 'Rotation mode: scheduled, on-use, on-bind (required)')
909
- .option('-i, --rotation-interval <interval>', 'Rotation interval (e.g., 24h, 7d) - required for scheduled mode')
910
- .option('-g, --grace-period <period>', 'Grace period (e.g., 5m, 1h)', '5m')
911
- .option('--notify-before <duration>', 'Notify before rotation (e.g., 1h)')
912
- .option('--webhook-url <url>', 'Webhook URL for rotation notifications')
913
- .option('--ip <ips>', 'Comma-separated IP allowlist (CIDR supported)')
914
- .option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
915
- .option('--json', 'Output as JSON')
916
- .action(async (name, options) => {
917
- // Validate required options
918
- if (!options.permissions) {
919
- output.error('--permissions is required. Use comma-separated permission strings.');
920
- process.exit(1);
921
- }
922
- if (!options.rotationMode) {
923
- output.error('--rotation-mode is required. Use: scheduled, on-use, or on-bind');
924
- process.exit(1);
925
- }
926
- const rotationMode = options.rotationMode;
927
- if (!['scheduled', 'on-use', 'on-bind'].includes(rotationMode)) {
928
- output.error('Invalid rotation mode. Use: scheduled, on-use, or on-bind');
929
- process.exit(1);
930
- }
931
- if (rotationMode === 'scheduled' && !options.rotationInterval) {
932
- output.error('--rotation-interval is required for scheduled rotation mode');
933
- output.info('Example: --rotation-interval 24h');
934
- process.exit(1);
935
- }
936
- const permissions = options.permissions.split(',').map((p) => p.trim());
937
- const spinner = ora('Creating managed API key...').start();
938
- try {
939
- const expiresInDays = parseInt(options.expires, 10);
940
- if (isNaN(expiresInDays) || expiresInDays < 1 || expiresInDays > 3650) {
941
- spinner.fail('Invalid expiration');
942
- output.error('Expiration must be between 1 and 3650 days');
943
- process.exit(1);
944
- }
945
- const result = await client.createManagedApiKey({
946
- name,
947
- description: options.description,
948
- expiresInDays,
949
- permissions,
950
- tenantId: options.tenant,
951
- ipAllowlist: options.ip?.split(',').map((ip) => ip.trim()),
952
- managed: {
953
- rotationMode,
954
- rotationInterval: options.rotationInterval,
955
- gracePeriod: options.gracePeriod,
956
- notifyBefore: options.notifyBefore,
957
- webhookUrl: options.webhookUrl,
958
- },
959
- });
960
- spinner.succeed('Managed API key created');
961
- if (options.json) {
962
- output.json(result);
963
- return;
964
- }
965
- console.log('\n✓ Managed API key created successfully');
966
- console.log('\nNote: Managed keys do not show the key value at creation.');
967
- console.log('Use "znvault apikey managed bind <name>" to get the current key value.\n');
968
- displayManagedKeyDetails(result.apiKey);
969
- }
970
- catch (err) {
971
- spinner.fail('Failed to create managed API key');
972
- output.error(err instanceof Error ? err.message : String(err));
973
- process.exit(1);
974
- }
975
- });
976
- // Get managed key details
977
- managedCmd
978
- .command('get <name>')
979
- .alias('show')
980
- .description('Show managed API key details')
981
- .option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
982
- .option('--json', 'Output as JSON')
983
- .action(async (name, options) => {
984
- const spinner = ora('Fetching managed API key...').start();
985
- try {
986
- const key = await client.getManagedApiKey(name, options.tenant);
987
- spinner.stop();
988
- if (options.json) {
989
- output.json(key);
990
- return;
991
- }
992
- displayManagedKeyDetails(key);
993
- }
994
- catch (err) {
995
- spinner.fail('Failed to fetch managed API key');
996
- output.error(err instanceof Error ? err.message : String(err));
997
- process.exit(1);
998
- }
999
- });
1000
- // Bind to managed key (get current key value)
1001
- managedCmd
1002
- .command('bind <name>')
1003
- .description('Bind to a managed key and get the current key value')
1004
- .option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
1005
- .option('--json', 'Output as JSON')
1006
- .action(async (name, options) => {
1007
- const spinner = ora('Binding to managed API key...').start();
1008
- try {
1009
- const result = await client.bindManagedApiKey(name, options.tenant);
1010
- spinner.succeed('Bound to managed API key');
1011
- if (options.json) {
1012
- output.json(result);
1013
- return;
1014
- }
1015
- console.log('\n────────────────────────────────────────────────────────────────');
1016
- console.log(`API Key: ${result.key}`);
1017
- console.log('────────────────────────────────────────────────────────────────\n');
1018
- output.keyValue({
1019
- 'Name': result.name,
1020
- 'Key ID': result.id,
1021
- 'Prefix': result.prefix,
1022
- 'Rotation Mode': formatRotationMode(result.rotationMode),
1023
- 'Next Rotation': result.nextRotationAt ? `${formatDate(result.nextRotationAt)} (${formatTimeUntil(result.nextRotationAt)})` : '-',
1024
- 'Grace Period': result.gracePeriod,
1025
- 'Grace Expires': result.graceExpiresAt ? formatDate(result.graceExpiresAt) : '-',
1026
- 'Key Expires': formatDate(result.expiresAt),
1027
- });
1028
- if (result.permissions.length > 0) {
1029
- console.log('\nPermissions:');
1030
- for (const perm of result.permissions) {
1031
- console.log(` - ${perm}`);
1032
- }
1033
- }
1034
- if (result._notice) {
1035
- console.log(`\n⚠️ ${result._notice}`);
1036
- }
1037
- }
1038
- catch (err) {
1039
- spinner.fail('Failed to bind to managed API key');
1040
- output.error(err instanceof Error ? err.message : String(err));
1041
- process.exit(1);
1042
- }
1043
- });
1044
- // Force rotate managed key
1045
- managedCmd
1046
- .command('rotate <name>')
1047
- .description('Force immediate rotation of a managed key')
1048
- .option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
1049
- .action(async (name, options) => {
1050
- const spinner = ora('Rotating managed API key...').start();
1051
- try {
1052
- const result = await client.rotateManagedApiKey(name, options.tenant);
1053
- spinner.succeed('Managed API key rotated');
1054
- console.log(`\n${result.message}`);
1055
- if (result.nextRotationAt) {
1056
- console.log(`Next scheduled rotation: ${formatDate(result.nextRotationAt)}`);
1057
- }
1058
- console.log('\nUse "znvault apikey managed bind <name>" to get the new key value.');
1059
- }
1060
- catch (err) {
1061
- spinner.fail('Failed to rotate managed API key');
1062
- output.error(err instanceof Error ? err.message : String(err));
1063
- process.exit(1);
1064
- }
1065
- });
1066
- // Update managed key config
1067
- managedCmd
1068
- .command('config <name>')
1069
- .description('Update managed key rotation configuration')
1070
- .option('-i, --rotation-interval <interval>', 'Rotation interval (e.g., 24h, 7d)')
1071
- .option('-g, --grace-period <period>', 'Grace period (e.g., 5m, 1h)')
1072
- .option('--notify-before <duration>', 'Notify before rotation (e.g., 1h)')
1073
- .option('--webhook-url <url>', 'Webhook URL for rotation notifications')
1074
- .option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
1075
- .option('--json', 'Output as JSON')
1076
- .action(async (name, options) => {
1077
- // Check if at least one config option is provided
1078
- if (!options.rotationInterval && !options.gracePeriod && !options.notifyBefore && !options.webhookUrl) {
1079
- output.error('At least one configuration option is required');
1080
- output.info('Options: --rotation-interval, --grace-period, --notify-before, --webhook-url');
1081
- process.exit(1);
1082
- }
1083
- const spinner = ora('Updating managed API key config...').start();
1084
- try {
1085
- const key = await client.updateManagedApiKeyConfig(name, {
1086
- rotationInterval: options.rotationInterval,
1087
- gracePeriod: options.gracePeriod,
1088
- notifyBefore: options.notifyBefore,
1089
- webhookUrl: options.webhookUrl,
1090
- }, options.tenant);
1091
- spinner.succeed('Configuration updated');
1092
- if (options.json) {
1093
- output.json(key);
1094
- return;
1095
- }
1096
- console.log('\nUpdated configuration:');
1097
- output.keyValue({
1098
- 'Rotation Interval': key.rotation_interval ?? '-',
1099
- 'Grace Period': key.grace_period,
1100
- 'Notify Before': key.notify_before ?? '-',
1101
- 'Webhook URL': key.webhook_url ?? '-',
1102
- 'Next Rotation': key.next_rotation_at ? formatDate(key.next_rotation_at) : '-',
1103
- });
1104
- }
1105
- catch (err) {
1106
- spinner.fail('Failed to update configuration');
1107
- output.error(err instanceof Error ? err.message : String(err));
1108
- process.exit(1);
1109
- }
1110
- });
1111
- // Delete managed key
1112
- managedCmd
1113
- .command('delete <name>')
1114
- .alias('rm')
1115
- .description('Delete a managed API key')
1116
- .option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
1117
- .option('-f, --force', 'Skip confirmation')
1118
- .action(async (name, options) => {
1119
- if (!options.force) {
1120
- output.warn(`This will permanently delete managed API key: ${name}`);
1121
- output.warn('All bound applications will lose access immediately.');
1122
- }
1123
- const spinner = ora('Deleting managed API key...').start();
1124
- try {
1125
- await client.deleteManagedApiKey(name, options.tenant);
1126
- spinner.succeed(`Managed API key deleted: ${name}`);
1127
- }
1128
- catch (err) {
1129
- spinner.fail('Failed to delete managed API key');
1130
- output.error(err instanceof Error ? err.message : String(err));
1131
- process.exit(1);
1132
- }
1133
- });
1134
- // Update managed key permissions
1135
- managedCmd
1136
- .command('permissions <name>')
1137
- .description('Update managed API key permissions')
1138
- .option('-s, --set <perms>', 'Set permissions (comma-separated, replaces all)')
1139
- .option('-a, --add <perms>', 'Add permissions (comma-separated)')
1140
- .option('-r, --remove <perms>', 'Remove permissions (comma-separated)')
1141
- .option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
1142
- .option('--json', 'Output as JSON')
1143
- .action(async (name, options) => {
1144
- if (!options.set && !options.add && !options.remove) {
1145
- output.error('At least one of --set, --add, or --remove is required');
1146
- output.info('Examples:');
1147
- output.info(' znvault apikey managed permissions my-key --set "secret:read,secret:list"');
1148
- output.info(' znvault apikey managed permissions my-key --add "kms:encrypt"');
1149
- output.info(' znvault apikey managed permissions my-key --remove "secret:delete"');
1150
- process.exit(1);
1151
- }
1152
- const spinner = ora('Updating managed API key permissions...').start();
1153
- try {
1154
- // First, get the managed key to find its ID and current permissions
1155
- const managedKey = await client.getManagedApiKey(name, options.tenant);
1156
- let newPermissions;
1157
- if (options.set) {
1158
- // Replace all permissions
1159
- newPermissions = options.set.split(',').map((p) => p.trim());
1160
- }
1161
- else {
1162
- // Start with current permissions
1163
- newPermissions = [...managedKey.permissions];
1164
- if (options.add) {
1165
- const toAdd = options.add.split(',').map((p) => p.trim());
1166
- for (const perm of toAdd) {
1167
- if (!newPermissions.includes(perm)) {
1168
- newPermissions.push(perm);
1169
- }
1170
- }
1171
- }
1172
- if (options.remove) {
1173
- const toRemove = options.remove.split(',').map((p) => p.trim());
1174
- newPermissions = newPermissions.filter((p) => !toRemove.includes(p));
1175
- }
1176
- }
1177
- if (newPermissions.length === 0) {
1178
- spinner.fail('Cannot remove all permissions');
1179
- output.error('API key must have at least one permission');
1180
- process.exit(1);
1181
- }
1182
- // Update using the key ID
1183
- const updatedKey = await client.updateApiKeyPermissions(managedKey.id, newPermissions, options.tenant);
1184
- spinner.succeed('Permissions updated');
1185
- if (options.json) {
1186
- output.json(updatedKey);
1187
- return;
1188
- }
1189
- console.log(`\nManaged key: ${name}`);
1190
- console.log('Updated permissions:');
1191
- for (const perm of updatedKey.permissions) {
1192
- console.log(` - ${perm}`);
1193
- }
1194
- }
1195
- catch (err) {
1196
- spinner.fail('Failed to update permissions');
1197
- output.error(err instanceof Error ? err.message : String(err));
1198
- process.exit(1);
1199
- }
1200
- });
1201
- // Update managed key conditions
1202
- managedCmd
1203
- .command('conditions <name>')
1204
- .description('Update managed API key ABAC conditions')
1205
- .option('--ip <ips>', 'Comma-separated IP allowlist (CIDR supported)')
1206
- .option('--time-range <range>', 'Time range: "HH:MM-HH:MM [TIMEZONE]" or "clear"')
1207
- .option('--methods <methods>', 'Comma-separated HTTP methods or "clear"')
1208
- .option('--resources <ids>', 'Resource IDs (type:id,...) or "clear"')
1209
- .option('--aliases <patterns>', 'Alias patterns (glob) or "clear"')
1210
- .option('--tags <tags>', 'Resource tags (key=value,...) or "clear"')
1211
- .option('--clear-all', 'Remove all conditions')
1212
- .option('-t, --tenant <id>', 'Tenant ID (superadmin only)')
1213
- .option('--json', 'Output as JSON')
1214
- .action(async (name, options) => {
1215
- const conditions = {};
1216
- // Handle --clear-all
1217
- if (!options.clearAll) {
1218
- // Parse individual conditions
1219
- if (options.ip && options.ip !== 'clear') {
1220
- conditions.ip = options.ip.split(',').map((ip) => ip.trim());
1221
- }
1222
- if (options.timeRange) {
1223
- if (options.timeRange !== 'clear') {
1224
- const match = /^(\d{2}:\d{2})-(\d{2}:\d{2})(?:\s+(.+))?$/.exec(options.timeRange);
1225
- if (!match) {
1226
- output.error('Invalid time range format. Use: "HH:MM-HH:MM [TIMEZONE]"');
1227
- process.exit(1);
1228
- }
1229
- conditions.timeRange = {
1230
- start: match[1],
1231
- end: match[2],
1232
- timezone: match[3] || 'UTC',
1233
- };
1234
- }
1235
- }
1236
- if (options.methods && options.methods !== 'clear') {
1237
- conditions.methods = options.methods.split(',').map((m) => m.trim().toUpperCase());
1238
- }
1239
- if (options.resources && options.resources !== 'clear') {
1240
- const resources = {};
1241
- for (const part of options.resources.split(',')) {
1242
- const [type, resId] = part.split(':');
1243
- if (type && resId) {
1244
- resources[type] = resources[type] ?? [];
1245
- resources[type].push(resId);
1246
- }
1247
- }
1248
- if (Object.keys(resources).length > 0) {
1249
- conditions.resources = resources;
1250
- }
1251
- }
1252
- if (options.aliases && options.aliases !== 'clear') {
1253
- conditions.aliases = options.aliases.split(',').map((a) => a.trim());
1254
- }
1255
- if (options.tags && options.tags !== 'clear') {
1256
- const tags = {};
1257
- for (const part of options.tags.split(',')) {
1258
- const [key, value] = part.split('=');
1259
- if (key && value) {
1260
- tags[key.trim()] = value.trim();
1261
- }
1262
- }
1263
- if (Object.keys(tags).length > 0) {
1264
- conditions.resourceTags = tags;
1265
- }
1266
- }
1267
- }
1268
- const spinner = ora('Updating managed API key conditions...').start();
1269
- try {
1270
- // First, get the managed key to find its ID
1271
- const managedKey = await client.getManagedApiKey(name, options.tenant);
1272
- // Update using the key ID
1273
- const key = await client.updateApiKeyConditions(managedKey.id, conditions, options.tenant);
1274
- spinner.succeed('Conditions updated');
1275
- if (options.json) {
1276
- output.json(key);
1277
- return;
1278
- }
1279
- console.log(`\nManaged key: ${name}`);
1280
- console.log('Updated conditions:');
1281
- if (key.conditions && Object.keys(key.conditions).length > 0) {
1282
- for (const [condKey, condValue] of Object.entries(key.conditions)) {
1283
- console.log(` ${condKey}: ${JSON.stringify(condValue)}`);
1284
- }
1285
- }
1286
- else {
1287
- console.log(' (no conditions)');
1288
- }
1289
- }
1290
- catch (err) {
1291
- spinner.fail('Failed to update conditions');
1292
- output.error(err instanceof Error ? err.message : String(err));
1293
- process.exit(1);
1294
- }
1295
- });
1296
- }
1
+ // Path: src/commands/apikey.ts
2
+ /**
3
+ * API key commands - backward compatibility re-export
4
+ *
5
+ * This file maintains backward compatibility by re-exporting from the
6
+ * modularized apikey/ directory structure.
7
+ */
8
+ export { registerApiKeyCommands } from './apikey/index.js';
9
+ export * from './apikey/types.js';
1297
10
  //# sourceMappingURL=apikey.js.map