@certen.io/cli 0.4.0 → 0.7.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.
- package/CHANGELOG.md +440 -0
- package/README.md +232 -160
- package/dist/chains.d.ts +67 -0
- package/dist/chains.js +242 -0
- package/dist/chains.js.map +1 -0
- package/dist/commands/admin.js +49 -4
- package/dist/commands/admin.js.map +1 -1
- package/dist/commands/auth.js +145 -12
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/billing.d.ts +2 -0
- package/dist/commands/billing.js +782 -0
- package/dist/commands/billing.js.map +1 -0
- package/dist/commands/call.d.ts +2 -0
- package/dist/commands/call.js +168 -0
- package/dist/commands/call.js.map +1 -0
- package/dist/commands/chains.d.ts +2 -0
- package/dist/commands/chains.js +123 -0
- package/dist/commands/chains.js.map +1 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +171 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/errors-catalogue.d.ts +2 -0
- package/dist/commands/errors-catalogue.js +83 -0
- package/dist/commands/errors-catalogue.js.map +1 -0
- package/dist/commands/identity.js +269 -19
- package/dist/commands/identity.js.map +1 -1
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +307 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/keys.js +2 -0
- package/dist/commands/keys.js.map +1 -1
- package/dist/commands/oauth-clients.d.ts +2 -0
- package/dist/commands/oauth-clients.js +131 -0
- package/dist/commands/oauth-clients.js.map +1 -0
- package/dist/commands/orgs.d.ts +2 -0
- package/dist/commands/orgs.js +117 -0
- package/dist/commands/orgs.js.map +1 -0
- package/dist/commands/pending.js +13 -1
- package/dist/commands/pending.js.map +1 -1
- package/dist/commands/portfolio.js +34 -1
- package/dist/commands/portfolio.js.map +1 -1
- package/dist/commands/proof.d.ts +2 -0
- package/dist/commands/proof.js +400 -0
- package/dist/commands/proof.js.map +1 -0
- package/dist/commands/signup.d.ts +14 -0
- package/dist/commands/signup.js +271 -0
- package/dist/commands/signup.js.map +1 -0
- package/dist/commands/transaction.js +144 -21
- package/dist/commands/transaction.js.map +1 -1
- package/dist/commands/webhooks.d.ts +2 -0
- package/dist/commands/webhooks.js +200 -0
- package/dist/commands/webhooks.js.map +1 -0
- package/dist/commands/whoami.d.ts +16 -0
- package/dist/commands/whoami.js +98 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/config.d.ts +56 -0
- package/dist/config.js +51 -0
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +14 -1
- package/dist/errors.js +15 -1
- package/dist/errors.js.map +1 -1
- package/dist/funding-guard.d.ts +40 -0
- package/dist/funding-guard.js +120 -0
- package/dist/funding-guard.js.map +1 -0
- package/dist/help-root.d.ts +2 -0
- package/dist/help-root.js +94 -0
- package/dist/help-root.js.map +1 -0
- package/dist/index.js +40 -8
- package/dist/index.js.map +1 -1
- package/dist/output.d.ts +23 -0
- package/dist/output.js +90 -0
- package/dist/output.js.map +1 -1
- package/dist/passphrase.d.ts +18 -0
- package/dist/passphrase.js +35 -3
- package/dist/passphrase.js.map +1 -1
- package/dist/payment-uri.d.ts +76 -0
- package/dist/payment-uri.js +135 -0
- package/dist/payment-uri.js.map +1 -0
- package/dist/signer.js +6 -3
- package/dist/signer.js.map +1 -1
- package/dist/solidity-args.d.ts +31 -0
- package/dist/solidity-args.js +111 -0
- package/dist/solidity-args.js.map +1 -0
- package/dist/wait.d.ts +79 -0
- package/dist/wait.js +168 -0
- package/dist/wait.js.map +1 -0
- 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":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,kEAAkE;AAClE,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,kBAAkB;IAClB,cAAc;IACd,kBAAkB;CACV,CAAC;AAIX;;;;;;GAMG;AACH,MAAM,gBAAgB,GAAG,wBAAwB,CAAC;AAElD,uGAAuG;AACvG,MAAM,OAAO,GAAmC;IAC9C,GAAG,EAAE,kBAAkB;IACvB,QAAQ,EAAE,kBAAkB;IAC5B,OAAO,EAAE,kBAAkB;IAC3B,aAAa,EAAE,kBAAkB;IACjC,OAAO,EAAE,kBAAkB;IAC3B,IAAI,EAAE,cAAc;IACpB,WAAW,EAAE,cAAc;IAC3B,GAAG,EAAE,kBAAkB;IACvB,QAAQ,EAAE,kBAAkB;IAC5B,aAAa,EAAE,kBAAkB;IACjC,UAAU,EAAE,kBAAkB;CAC/B,CAAC;AAEF,MAAM,UAAU,eAAe;IAC7B,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,eAAe,GAAmC;IACtD,QAAQ,EAAE,kBAAkB;IAC5B,KAAK,EAAE,cAAc;IACrB,MAAM,EAAE,kBAAkB;CAC3B,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACnC,KAAK,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;QAChE,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;IACD,MAAM,MAAM,GAAG,cAAc,EAAE,EAAE,OAAO,CAAC;IACzC,IAAI,MAAM,EAAE,CAAC;QACX,KAAK,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACvD,IAAI,MAAM,KAAK,IAAI;gBAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAAyC;IACtE,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACrD,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IACjC,IAAI,gBAAgB,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC;IACtC,IAAI,eAAe,CAAC,GAAG,CAAC;QAAE,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,cAAc,EAAE,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;IACnD,OAAO,SAAS,IAAI,GAAG,CAAC;AAC1B,CAAC;AAED,mGAAmG;AAEnG;;;;;;GAMG;AACH,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;AAC7D,MAAM,YAAY,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAUzC,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,IAAI,CAAC;QACzC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAe,CAAC;QAC3E,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAC5C,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,sFAAsF;QACtF,0EAA0E;QAC1E,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAwB;IACxD,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC;IAC9D,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,GAAG,YAAY,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAsD;IACpF,IAAI,CAAC;QACH,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;gBAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC;QACvG,CAAC;QACD,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC;YACvC,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACpC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7B,OAAO;SACR,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,+EAA+E;IACjF,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;AAE3C,kGAAkG;AAClG,SAAS,YAAY,CAAC,KAAa;IACjC,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAQ,gBAAsC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACjE,CAAC;AAED,SAAS,WAAW,CAAC,CAAS,EAAE,CAAS;IACvC,4FAA4F;IAC5F,8DAA8D;IAC9D,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3C,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACrE,CAAC;QACD,IAAI,GAAG,GAAG,CAAC;IACb,CAAC;IACD,OAAO,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC1C,IAAI,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;IAE5C,IAAI,IAAwB,CAAC;IAC7B,IAAI,YAAY,GAAG,QAAQ,CAAC;IAC5B,KAAK,MAAM,KAAK,IAAI,gBAAgB,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACrC,IAAI,CAAC,GAAG,YAAY,EAAE,CAAC;YACrB,YAAY,GAAG,CAAC,CAAC;YACjB,IAAI,GAAG,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,kGAAkG;IAClG,OAAO,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,IAAI,GAAG,SAAS;IACzD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,gBAAgB,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAE1C,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IAExD,4FAA4F;IAC5F,0FAA0F;IAC1F,wCAAwC;IACxC,IAAI,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,UAAU,CAClB,uBAAuB,KAAK,2BAA2B,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;cACpF,OAAO,gBAAgB,sBAAsB,EAC/C,mBAAmB,CACpB,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG;QACZ,IAAI,KAAK,6BAA6B;QACtC,UAAU,CAAC,CAAC,CAAC,iBAAiB,UAAU,GAAG,CAAC,CAAC,CAAC,EAAE;QAChD,eAAe,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;QAC7C,SAAS,gBAAgB,sCAAsC;KAChE,CAAC;IACF,MAAM,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,mBAAmB,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,IAAI,GAAG,UAAU;IAC3D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAChF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,UAAU,CAClB,GAAG,IAAI,oCAAoC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EACzE,mBAAmB,CACpB,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;AAChD,CAAC"}
|
package/dist/commands/admin.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CertenClient } from '@certen.io/sdk';
|
|
2
|
-
import { getApiKey, getApiUrl } from '../config.js';
|
|
3
|
-
import {
|
|
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
|
|
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:
|
|
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":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"admin.js","sourceRoot":"","sources":["../../src/commands/admin.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,29 +1,158 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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('
|
|
8
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
23
|
-
|
|
24
|
-
writeConfig(
|
|
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
|
|
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":"AACA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
|