@certen.io/cli 0.5.0 → 0.7.1

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 (87) hide show
  1. package/CHANGELOG.md +431 -0
  2. package/README.md +232 -160
  3. package/dist/chains.d.ts +67 -0
  4. package/dist/chains.js +242 -0
  5. package/dist/chains.js.map +1 -0
  6. package/dist/commands/admin.js +49 -4
  7. package/dist/commands/admin.js.map +1 -1
  8. package/dist/commands/auth.js +145 -12
  9. package/dist/commands/auth.js.map +1 -1
  10. package/dist/commands/billing.js +648 -40
  11. package/dist/commands/billing.js.map +1 -1
  12. package/dist/commands/call.d.ts +2 -0
  13. package/dist/commands/call.js +168 -0
  14. package/dist/commands/call.js.map +1 -0
  15. package/dist/commands/chains.d.ts +2 -0
  16. package/dist/commands/chains.js +123 -0
  17. package/dist/commands/chains.js.map +1 -0
  18. package/dist/commands/doctor.d.ts +2 -0
  19. package/dist/commands/doctor.js +171 -0
  20. package/dist/commands/doctor.js.map +1 -0
  21. package/dist/commands/errors-catalogue.d.ts +2 -0
  22. package/dist/commands/errors-catalogue.js +83 -0
  23. package/dist/commands/errors-catalogue.js.map +1 -0
  24. package/dist/commands/identity.js +269 -19
  25. package/dist/commands/identity.js.map +1 -1
  26. package/dist/commands/init.d.ts +2 -0
  27. package/dist/commands/init.js +307 -0
  28. package/dist/commands/init.js.map +1 -0
  29. package/dist/commands/keys.js +2 -0
  30. package/dist/commands/keys.js.map +1 -1
  31. package/dist/commands/oauth-clients.d.ts +2 -0
  32. package/dist/commands/oauth-clients.js +131 -0
  33. package/dist/commands/oauth-clients.js.map +1 -0
  34. package/dist/commands/orgs.d.ts +2 -0
  35. package/dist/commands/orgs.js +117 -0
  36. package/dist/commands/orgs.js.map +1 -0
  37. package/dist/commands/pending.d.ts +19 -0
  38. package/dist/commands/pending.js +117 -15
  39. package/dist/commands/pending.js.map +1 -1
  40. package/dist/commands/portfolio.js +34 -1
  41. package/dist/commands/portfolio.js.map +1 -1
  42. package/dist/commands/proof.d.ts +2 -0
  43. package/dist/commands/proof.js +400 -0
  44. package/dist/commands/proof.js.map +1 -0
  45. package/dist/commands/signup.d.ts +14 -0
  46. package/dist/commands/signup.js +271 -0
  47. package/dist/commands/signup.js.map +1 -0
  48. package/dist/commands/transaction.js +139 -21
  49. package/dist/commands/transaction.js.map +1 -1
  50. package/dist/commands/webhooks.d.ts +2 -0
  51. package/dist/commands/webhooks.js +200 -0
  52. package/dist/commands/webhooks.js.map +1 -0
  53. package/dist/commands/whoami.d.ts +16 -0
  54. package/dist/commands/whoami.js +98 -0
  55. package/dist/commands/whoami.js.map +1 -0
  56. package/dist/config.d.ts +56 -0
  57. package/dist/config.js +51 -0
  58. package/dist/config.js.map +1 -1
  59. package/dist/errors.d.ts +14 -1
  60. package/dist/errors.js +15 -1
  61. package/dist/errors.js.map +1 -1
  62. package/dist/funding-guard.d.ts +40 -0
  63. package/dist/funding-guard.js +120 -0
  64. package/dist/funding-guard.js.map +1 -0
  65. package/dist/help-root.d.ts +2 -0
  66. package/dist/help-root.js +94 -0
  67. package/dist/help-root.js.map +1 -0
  68. package/dist/index.js +27 -0
  69. package/dist/index.js.map +1 -1
  70. package/dist/output.d.ts +23 -0
  71. package/dist/output.js +46 -0
  72. package/dist/output.js.map +1 -1
  73. package/dist/passphrase.d.ts +18 -0
  74. package/dist/passphrase.js +35 -3
  75. package/dist/passphrase.js.map +1 -1
  76. package/dist/payment-uri.d.ts +76 -0
  77. package/dist/payment-uri.js +135 -0
  78. package/dist/payment-uri.js.map +1 -0
  79. package/dist/signer.js +6 -3
  80. package/dist/signer.js.map +1 -1
  81. package/dist/solidity-args.d.ts +31 -0
  82. package/dist/solidity-args.js +111 -0
  83. package/dist/solidity-args.js.map +1 -0
  84. package/dist/wait.d.ts +79 -0
  85. package/dist/wait.js +168 -0
  86. package/dist/wait.js.map +1 -0
  87. package/package.json +5 -4
package/dist/chains.js ADDED
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Chain vocabulary.
3
+ *
4
+ * Every command that takes a chain validates it HERE, before the network call. A typo used to
5
+ * travel all the way to the gateway and come back as a rejection with no visible connection to
6
+ * the flag that caused it — and in `fund`'s case it came back only after a real payment intent
7
+ * had already been opened against the wrong chain.
8
+ *
9
+ * The suggestion matters as much as the rejection. `--chain base` is not a typo of nothing: `base`
10
+ * is a real mainnet, and silently mapping it to `base-sepolia` would be the CLI guessing about
11
+ * where money goes. So aliases SUGGEST and never substitute.
12
+ *
13
+ * This list is a constant today. Phase 2 replaces it with a cached read of `GET /v1/chains` and
14
+ * keeps this as the offline fallback — which is why the export is a function, not the array.
15
+ */
16
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
17
+ import { join } from 'node:path';
18
+ import { homedir } from 'node:os';
19
+ import { UsageError } from './errors.js';
20
+ /** The chains this product targets. Deliberately testnet-only. */
21
+ export const SUPPORTED_CHAINS = [
22
+ 'ethereum-sepolia',
23
+ 'base-sepolia',
24
+ 'arbitrum-sepolia',
25
+ ];
26
+ /**
27
+ * Escape hatch for a chain the gateway serves but this build does not list.
28
+ *
29
+ * The gateway is deployed on more chains than these three. Someone deliberately working outside
30
+ * the supported set should not have to patch the CLI to do it — but they should have to say so,
31
+ * so it can never happen by typo.
32
+ */
33
+ const OVERRIDE_ENV_VAR = 'CERTEN_ALLOW_ANY_CHAIN';
34
+ /** Near-misses worth naming explicitly. A mainnet name is the dangerous case, not the harmless one. */
35
+ const ALIASES = {
36
+ eth: 'ethereum-sepolia',
37
+ ethereum: 'ethereum-sepolia',
38
+ sepolia: 'ethereum-sepolia',
39
+ 'eth-sepolia': 'ethereum-sepolia',
40
+ mainnet: 'ethereum-sepolia',
41
+ base: 'base-sepolia',
42
+ basesepolia: 'base-sepolia',
43
+ arb: 'arbitrum-sepolia',
44
+ arbitrum: 'arbitrum-sepolia',
45
+ 'arb-sepolia': 'arbitrum-sepolia',
46
+ arbsepolia: 'arbitrum-sepolia',
47
+ };
48
+ export function supportedChains() {
49
+ return SUPPORTED_CHAINS;
50
+ }
51
+ /**
52
+ * Numeric EVM chain id → registry slug, for the chains this CLI targets.
53
+ *
54
+ * **This is not a convenience.** `GET /v1/portfolio` used to return `chain_id` as a slug for some
55
+ * chain accounts and as a numeric EVM id for others — both spellings in the same response, on the
56
+ * same organization. Anything comparing `chain_id` to a slug therefore silently missed every
57
+ * numeric entry, which is exactly how the unfunded-account guard came to skip the chain it was
58
+ * written to protect.
59
+ *
60
+ * The gateway now canonicalizes on write and has backfilled the old rows, so a current gateway
61
+ * sends slugs only. Kept regardless: the CLI ships on its own cadence and is frequently newer than
62
+ * the gateway it talks to, and the failure this prevents is silent.
63
+ *
64
+ * The cache below extends this at runtime; the constant is what makes it work offline and on a
65
+ * first run.
66
+ */
67
+ const NUMERIC_TO_SLUG = {
68
+ 11155111: 'ethereum-sepolia',
69
+ 84532: 'base-sepolia',
70
+ 421614: 'arbitrum-sepolia',
71
+ };
72
+ /**
73
+ * Registry slug → numeric EVM chain id.
74
+ *
75
+ * `execute.contractCall` passes `chainId` straight through to the intent leg. Leaving it undefined
76
+ * makes the caller supply a number they already told us by naming the chain, so it is derived —
77
+ * from the cached registry when there is one, and from this table otherwise.
78
+ */
79
+ export function chainIdFor(chain) {
80
+ const slug = normalizeChain(chain);
81
+ for (const [numeric, mapped] of Object.entries(NUMERIC_TO_SLUG)) {
82
+ if (mapped === slug)
83
+ return Number(numeric);
84
+ }
85
+ const cached = readChainCache()?.numeric;
86
+ if (cached) {
87
+ for (const [numeric, mapped] of Object.entries(cached)) {
88
+ if (mapped === slug)
89
+ return Number(numeric);
90
+ }
91
+ }
92
+ return undefined;
93
+ }
94
+ /**
95
+ * Resolve whatever the gateway called a chain into one canonical name.
96
+ *
97
+ * Anything unrecognised is returned unchanged: a value we cannot map is still the best label we
98
+ * have for it, and inventing one would be worse than showing what arrived.
99
+ */
100
+ export function normalizeChain(value) {
101
+ if (value === null || value === undefined)
102
+ return '';
103
+ const raw = String(value).trim();
104
+ if (isSupportedChain(raw))
105
+ return raw;
106
+ if (NUMERIC_TO_SLUG[raw])
107
+ return NUMERIC_TO_SLUG[raw];
108
+ const fromCache = readChainCache()?.numeric?.[raw];
109
+ return fromCache ?? raw;
110
+ }
111
+ // ── the registry cache ──────────────────────────────────────────────────────────────────────────
112
+ /**
113
+ * `GET /v1/chains` is public and its answer is static for hours at a time, so it is cached rather
114
+ * than fetched on every validation. The cache does NOT widen the supported set — that is a product
115
+ * decision, not a gateway fact — it exists so a refusal can tell the truth about WHY a real chain
116
+ * is being refused: "the gateway serves optimism-sepolia, but this CLI targets these three" reads
117
+ * very differently from "optimism-sepolia is not a chain", and only one of them is accurate.
118
+ */
119
+ const CACHE_FILE = join(homedir(), '.certen', 'chains.json');
120
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
121
+ export function readChainCache() {
122
+ try {
123
+ if (!existsSync(CACHE_FILE))
124
+ return null;
125
+ const parsed = JSON.parse(readFileSync(CACHE_FILE, 'utf-8'));
126
+ if (!Array.isArray(parsed.ids))
127
+ return null;
128
+ return parsed;
129
+ }
130
+ catch {
131
+ // A corrupt cache is not an error worth surfacing — it just means we fall back to the
132
+ // constant, which is what would have happened had the file never existed.
133
+ return null;
134
+ }
135
+ }
136
+ export function chainCacheIsFresh(cache) {
137
+ if (!cache)
138
+ return false;
139
+ const age = Date.now() - new Date(cache.fetched_at).getTime();
140
+ return Number.isFinite(age) && age >= 0 && age < CACHE_TTL_MS;
141
+ }
142
+ export function writeChainCache(entries) {
143
+ try {
144
+ const numeric = {};
145
+ for (const entry of entries) {
146
+ if (entry.chainId !== null && entry.chainId !== undefined)
147
+ numeric[String(entry.chainId)] = entry.id;
148
+ }
149
+ mkdirSync(join(homedir(), '.certen'), { recursive: true });
150
+ writeFileSync(CACHE_FILE, JSON.stringify({
151
+ fetched_at: new Date().toISOString(),
152
+ ids: entries.map((e) => e.id),
153
+ numeric,
154
+ }, null, 2));
155
+ }
156
+ catch {
157
+ // Best effort. Failing to cache must never fail the command that triggered it.
158
+ }
159
+ }
160
+ export const CHAIN_CACHE_FILE = CACHE_FILE;
161
+ /** Does the gateway serve this chain, as far as the cache knows? Absent cache means "no idea". */
162
+ function gatewayKnows(chain) {
163
+ const cache = readChainCache();
164
+ return cache ? cache.ids.includes(chain) : false;
165
+ }
166
+ export function isSupportedChain(value) {
167
+ return SUPPORTED_CHAINS.includes(value);
168
+ }
169
+ function levenshtein(a, b) {
170
+ // Single-row DP: the strings here are chain names, so allocation matters less than clarity,
171
+ // but there is no reason to hold a full matrix for it either.
172
+ let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
173
+ for (let i = 1; i <= a.length; i += 1) {
174
+ const row = [i];
175
+ for (let j = 1; j <= b.length; j += 1) {
176
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
177
+ row[j] = Math.min(row[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
178
+ }
179
+ prev = row;
180
+ }
181
+ return prev[b.length];
182
+ }
183
+ /** The closest supported chain to `value`, or undefined if nothing is close enough to suggest. */
184
+ export function nearestChain(value) {
185
+ const needle = value.trim().toLowerCase();
186
+ if (ALIASES[needle])
187
+ return ALIASES[needle];
188
+ let best;
189
+ let bestDistance = Infinity;
190
+ for (const chain of SUPPORTED_CHAINS) {
191
+ const d = levenshtein(needle, chain);
192
+ if (d < bestDistance) {
193
+ bestDistance = d;
194
+ best = chain;
195
+ }
196
+ }
197
+ // 3 is wide enough to catch "base-sepolai" and narrow enough not to propose a chain for "solana".
198
+ return bestDistance <= 3 ? best : undefined;
199
+ }
200
+ /**
201
+ * Validate one chain name, throwing a UsageError that names the alternatives.
202
+ *
203
+ * `flag` is the option the value arrived on, so the message points at what the caller typed rather
204
+ * than at an abstract notion of "chain".
205
+ */
206
+ export function assertChain(value, flag = '--chain') {
207
+ const chain = value.trim();
208
+ if (isSupportedChain(chain))
209
+ return chain;
210
+ if (process.env[OVERRIDE_ENV_VAR] === '1')
211
+ return chain;
212
+ // A chain the gateway really serves gets a different sentence from one that does not exist.
213
+ // Telling someone `optimism-sepolia` "is not a chain" would be false, and would send them
214
+ // looking for a typo they did not make.
215
+ if (gatewayKnows(chain)) {
216
+ throw new UsageError(`The gateway serves "${chain}", but this CLI targets ${SUPPORTED_CHAINS.join(', ')}. `
217
+ + `Set ${OVERRIDE_ENV_VAR}=1 to use it anyway.`, 'UNSUPPORTED_CHAIN');
218
+ }
219
+ const suggestion = nearestChain(chain);
220
+ const lines = [
221
+ `"${chain}" is not a supported chain.`,
222
+ suggestion ? ` Did you mean ${suggestion}?` : '',
223
+ ` Supported: ${SUPPORTED_CHAINS.join(', ')}.`,
224
+ ` (Set ${OVERRIDE_ENV_VAR}=1 to use a chain outside this set.)`,
225
+ ];
226
+ throw new UsageError(lines.join(''), 'UNSUPPORTED_CHAIN');
227
+ }
228
+ /**
229
+ * Validate a comma-separated list, as `--chains` takes.
230
+ *
231
+ * Empty entries are dropped rather than rejected — a trailing comma is a slip, not an instruction,
232
+ * and failing on it would be pedantry. An empty list after that is an error, because the caller
233
+ * clearly meant to name at least one.
234
+ */
235
+ export function assertChains(value, flag = '--chains') {
236
+ const parts = value.split(',').map((c) => c.trim()).filter((c) => c.length > 0);
237
+ if (parts.length === 0) {
238
+ throw new UsageError(`${flag} was given no chains. Supported: ${SUPPORTED_CHAINS.join(', ')}.`, 'UNSUPPORTED_CHAIN');
239
+ }
240
+ return parts.map((c) => assertChain(c, flag));
241
+ }
242
+ //# sourceMappingURL=chains.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chains.js","sourceRoot":"","sources":["../src/chains.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { CertenClient } from '@certen.io/sdk';
2
- import { getApiKey, getApiUrl } from '../config.js';
3
- import { printOutput } from '../output.js';
2
+ import { getApiKey, getApiUrl, getOutputFormat } from '../config.js';
3
+ import { CliError, EXIT } from '../errors.js';
4
+ import { printOutput, human, hint, isJsonMode } from '../output.js';
4
5
  async function getClient() {
5
6
  return new CertenClient({ apiKey: await getApiKey(), baseUrl: getApiUrl() });
6
7
  }
@@ -16,20 +17,64 @@ export function registerAdminCommands(program) {
16
17
  const result = await client.admin.listApiKeys();
17
18
  printOutput(result);
18
19
  });
20
+ program
21
+ .command('scopes')
22
+ .description('Every permission a key can be granted, and what each covers')
23
+ .option('--all', 'Include operator scopes, which customer keys should not use')
24
+ .action(async (opts) => {
25
+ // `--permissions` below takes a comma-separated list of a vocabulary published nowhere, so
26
+ // the choice was made by guessing. Guessing wrong is asymmetric: under-grant and you get
27
+ // 403s, which are visible; over-grant - and the obvious answer to uncertainty is `*` - and a
28
+ // key that only needed to read a balance can retire identities and publish price books.
29
+ const client = await getClient();
30
+ const { scopes } = await client.admin.scopes();
31
+ const shown = opts.all ? scopes : scopes.filter((s) => s.audience === 'customer');
32
+ if (isJsonMode() || getOutputFormat() === 'json') {
33
+ printOutput({ scopes: shown });
34
+ return;
35
+ }
36
+ const w = Math.max(...shown.map((s) => s.name.length), 4);
37
+ human('');
38
+ for (const s of shown) {
39
+ human(` ${s.name.padEnd(w)} ${s.description}`);
40
+ if (s.operations.length > 0) {
41
+ human(` ${' '.repeat(w)} ${s.operations.length} operation(s)`);
42
+ }
43
+ }
44
+ human('');
45
+ if (!opts.all) {
46
+ human(' Operator scopes are hidden. See them all: certen scopes --all');
47
+ }
48
+ hint('certen admin api-keys create --name <n> --org-id <id> --permissions billing:read,proof:read');
49
+ });
19
50
  apiKeys
20
51
  .command('create')
21
52
  .description('Create a new API key')
22
53
  .requiredOption('--name <name>', 'Key name')
23
54
  .requiredOption('--org-id <id>', 'Organization ID')
24
- .option('--permissions <perms>', 'Comma-separated permissions')
55
+ .option('--permissions <perms>', 'Comma-separated scopes. List them: certen scopes')
25
56
  .option('--rate-limit <rpm>', 'Rate limit RPM', parseInt)
26
57
  .option('--expires-at <date>', 'Expiration date (ISO)')
27
58
  .action(async (opts) => {
28
59
  const client = await getClient();
60
+ // Checked against the live catalogue BEFORE the key is minted. A typo used to produce a key
61
+ // that authenticates fine and then 403s on the one call it exists to make - discovered in
62
+ // production, and fixable only by issuing a new key, since permissions are set at creation.
63
+ const requested = opts.permissions
64
+ ? opts.permissions.split(',').map((p) => p.trim()).filter(Boolean)
65
+ : undefined;
66
+ if (requested?.length) {
67
+ const { scopes } = await client.admin.scopes();
68
+ const known = new Set(scopes.map((s) => s.name));
69
+ const unknown = requested.filter((p) => !known.has(p));
70
+ if (unknown.length > 0) {
71
+ throw new CliError(`Not a permission: ${unknown.join(', ')}. See them all: certen scopes`, 'UNKNOWN_SCOPE', EXIT.USAGE);
72
+ }
73
+ }
29
74
  const result = await client.admin.createApiKey({
30
75
  name: opts.name,
31
76
  orgId: opts.orgId,
32
- permissions: opts.permissions ? opts.permissions.split(',') : undefined,
77
+ permissions: requested,
33
78
  rateLimitRpm: opts.rateLimit,
34
79
  expiresAt: opts.expiresAt,
35
80
  });
@@ -1 +1 @@
1
- {"version":3,"file":"admin.js","sourceRoot":"","sources":["../../src/commands/admin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"admin.js","sourceRoot":"","sources":["../../src/commands/admin.ts"],"names":[],"mappings":"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"}
@@ -1,29 +1,158 @@
1
- import { readConfig, writeConfig, setApiKey, clearApiKey, DEFAULT_API_URL } from '../config.js';
2
- import { printOutput, human } from '../output.js';
1
+ import { CertenClient, CertenError, revokeOAuthToken } from '@certen.io/sdk';
2
+ import { readConfig, writeConfig, setApiKey, clearApiKey, DEFAULT_API_URL, getPortalUrl, getApiUrl, } from '../config.js';
3
+ import { printOutput, human, hint, isJsonMode } from '../output.js';
4
+ import { CliError, UsageError, EXIT } from '../errors.js';
5
+ import { isInteractive, promptSecret, readSecretFromStdin } from '../passphrase.js';
6
+ async function resolveKeyInput(flagValue) {
7
+ // `-` is the conventional "read it from stdin" spelling, and it is the one that keeps the secret
8
+ // out of shell history and out of `ps` output.
9
+ if (flagValue === '-') {
10
+ const key = await readSecretFromStdin();
11
+ if (!key)
12
+ throw new UsageError('No API key on stdin.', 'NO_API_KEY');
13
+ return { key, source: 'stdin' };
14
+ }
15
+ if (flagValue)
16
+ return { key: flagValue.trim(), source: 'flag' };
17
+ if (!isInteractive()) {
18
+ throw new UsageError('No API key given and no TTY to prompt on. Pass --api-key <key>, pipe it with '
19
+ + '`--api-key -`, or set CERTEN_API_KEY.', 'NO_API_KEY');
20
+ }
21
+ const key = await promptSecret('API key (input hidden): ');
22
+ if (!key)
23
+ throw new UsageError('No API key entered.', 'NO_API_KEY');
24
+ return { key, source: 'prompt' };
25
+ }
26
+ /**
27
+ * Prove the key is accepted by the gateway before it is written anywhere.
28
+ *
29
+ * `billing.balance` is the probe because it is cheap, read-only, and mutates nothing. The status
30
+ * codes are read carefully rather than treated as pass/fail:
31
+ *
32
+ * - **401** is the only conclusive rejection — the gateway does not recognise this credential.
33
+ * - **403** means the key is REAL and simply lacks `billing:read`. Refusing it here would reject
34
+ * a perfectly good scoped key, so it is accepted with a note.
35
+ * - **Anything else** (a 5xx, a route missing on an older gateway) is inconclusive. Saying so is
36
+ * honest; claiming verification we did not achieve is not.
37
+ * - **A transport failure** is rethrown untouched, so it exits 3 and the caller knows nothing was
38
+ * written rather than assuming the key was bad.
39
+ */
40
+ async function verifyKey(apiKey, baseUrl) {
41
+ const client = new CertenClient({ apiKey, baseUrl });
42
+ try {
43
+ await client.billing.balance();
44
+ return { ok: true };
45
+ }
46
+ catch (err) {
47
+ if (err instanceof CertenError) {
48
+ if (err.status === 401)
49
+ return { ok: false };
50
+ if (err.status === 403) {
51
+ return { ok: true, note: 'Key accepted, but it has no billing:read scope — `certen balance` will not work with it.' };
52
+ }
53
+ // status 0 is the SDK's "never reached the gateway". That is an unreachable-gateway
54
+ // condition, not a bad key, and it must exit 3 rather than be reported as a rejection.
55
+ if (err.status === 0)
56
+ throw err;
57
+ return { ok: true, note: `Could not fully verify the key (gateway answered ${err.status}). Saved anyway.` };
58
+ }
59
+ throw err;
60
+ }
61
+ }
3
62
  export function registerAuthCommands(program) {
4
63
  const auth = program.command('auth').description('Authentication management');
5
64
  auth
6
65
  .command('login')
7
- .description('Save API key (OS keyring by default; --no-keyring stores in ~/.certen/config.json with 0600 perms)')
8
- .requiredOption('--api-key <key>', 'API key to save')
66
+ .description('Verify an API key and save it (OS keyring by default; --no-keyring writes ~/.certen/config.json at 0600)')
67
+ // No longer required: omitting it prompts on a TTY, which keeps the secret out of shell
68
+ // history. `--api-key -` reads it from stdin for scripts that want the same guarantee.
69
+ .option('--api-key <key>', 'API key to save. Use "-" to read it from stdin; omit to be prompted')
9
70
  .option('--api-url <url>', 'API base URL')
10
71
  .option('--no-keyring', 'Store in ~/.certen/config.json instead of the OS keyring')
72
+ .option('--no-verify', 'Skip the check that the gateway accepts this key (offline setup)')
11
73
  .action(async (opts) => {
12
74
  const useKeyring = opts.keyring !== false;
75
+ const { key, source } = await resolveKeyInput(opts.apiKey);
76
+ // The URL the key will be checked against has to be the one it will be USED against, so a
77
+ // --api-url passed in the same invocation applies to the verification too.
78
+ const cfg = readConfig();
79
+ const baseUrl = opts.apiUrl ?? process.env.CERTEN_API_URL ?? cfg.api_url ?? DEFAULT_API_URL;
80
+ let note;
81
+ if (opts.verify !== false) {
82
+ const result = await verifyKey(key, baseUrl);
83
+ if (!result.ok) {
84
+ // Nothing is written. The old behaviour saved first and discovered this later, which
85
+ // left a broken credential on disk that every subsequent command tripped over.
86
+ throw new UsageError(`That API key was rejected by ${baseUrl}. Nothing was saved. `
87
+ + `Check it, or mint a new one at ${getPortalUrl()}`, 'INVALID_API_KEY');
88
+ }
89
+ note = result.note;
90
+ }
13
91
  try {
14
- await setApiKey(opts.apiKey, useKeyring);
92
+ await setApiKey(key, useKeyring);
15
93
  }
16
94
  catch (err) {
95
+ // Was `process.exit(1)`, which skipped the envelope entirely in --json mode.
17
96
  const msg = err instanceof Error ? err.message : String(err);
18
- console.error(`Failed to save API key: ${msg}`);
19
- process.exit(1);
97
+ throw new CliError(`Failed to save API key: ${msg}`, 'KEY_STORAGE_FAILED', EXIT.FAILED);
20
98
  }
21
99
  if (opts.apiUrl) {
22
- const cfg = readConfig();
23
- cfg.api_url = opts.apiUrl;
24
- writeConfig(cfg);
100
+ const updated = readConfig();
101
+ updated.api_url = opts.apiUrl;
102
+ writeConfig(updated);
103
+ }
104
+ printOutput({
105
+ storage: useKeyring ? 'keyring' : 'file',
106
+ key_prefix: `${key.substring(0, 12)}...`,
107
+ api_url: baseUrl,
108
+ verified: opts.verify !== false,
109
+ });
110
+ human(useKeyring
111
+ ? 'API key saved to the OS keyring.'
112
+ : 'API key saved to ~/.certen/config.json (mode 0600).');
113
+ if (note)
114
+ hint(note);
115
+ if (source === 'flag') {
116
+ hint('Tip: `--api-key -` reads the key from stdin, which keeps it out of your shell history.');
117
+ }
118
+ hint('');
119
+ hint('Next: certen keys generate --name dev');
120
+ });
121
+ auth
122
+ .command('revoke-token [token]')
123
+ .description('Revoke an OAuth2 access or refresh token (no API key needed)')
124
+ .option('--refresh', 'The token is a refresh token — revoking it kills its whole chain')
125
+ .action(async (token, opts) => {
126
+ // Reachable without a configured API key on purpose. This is the command someone runs when a
127
+ // token has leaked, and requiring the credential they are trying to contain would be exactly
128
+ // backwards. The gateway authenticates the request with the token itself.
129
+ let value = token;
130
+ if (!value) {
131
+ // Read from stdin or a prompt rather than argv: a token pasted as an argument lands in
132
+ // shell history and process listings, which is a poor place for a live credential.
133
+ value = isInteractive()
134
+ ? await promptSecret('Token to revoke: ')
135
+ : await readSecretFromStdin();
136
+ }
137
+ if (!value) {
138
+ throw new UsageError('No token given. Pass it as an argument, or pipe it on stdin.', 'MISSING_TOKEN');
139
+ }
140
+ await revokeOAuthToken(value.trim(), {
141
+ baseUrl: getApiUrl(),
142
+ tokenTypeHint: opts.refresh ? 'refresh_token' : 'access_token',
143
+ });
144
+ printOutput({ revoked: true });
145
+ if (!isJsonMode()) {
146
+ human('');
147
+ human(' That token is no longer valid.');
148
+ // Said explicitly because RFC 7009 makes success ambiguous by design, and someone
149
+ // containing an incident deserves to know what they have and have not learned.
150
+ human(' Revocation never reveals whether a token existed, so this succeeds either way —');
151
+ human(' it confirms the token is not valid now, not that it was valid before.');
152
+ if (opts.refresh) {
153
+ human(' Its descendant access tokens were revoked with it.');
154
+ }
25
155
  }
26
- human(useKeyring ? 'API key saved to OS keyring' : 'API key saved to ~/.certen/config.json (mode 0600)');
27
156
  });
28
157
  auth
29
158
  .command('logout')
@@ -34,7 +163,7 @@ export function registerAuthCommands(program) {
34
163
  });
35
164
  auth
36
165
  .command('status')
37
- .description('Show current authentication config (prefix-only — never reveals the full key)')
166
+ .description('Show LOCAL authentication config (offline — never contacts the gateway, never reveals the full key)')
38
167
  .action(() => {
39
168
  const cfg = readConfig();
40
169
  // Round-2 #43: when storage=keyring the prefix is persisted at login
@@ -63,6 +192,10 @@ export function registerAuthCommands(program) {
63
192
  api_url: cfg.api_url ?? `${DEFAULT_API_URL} (default)`,
64
193
  output: cfg.output ?? 'table (default)',
65
194
  });
195
+ // This command answers "what is configured here", not "does it work". Those are different
196
+ // questions and conflating them is how a user concludes their setup is fine while every
197
+ // request 401s. `certen doctor` (Phase 2) answers the second one.
198
+ hint('This is local config only. It does not check that the key still works.');
66
199
  });
67
200
  }
68
201
  //# sourceMappingURL=auth.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}