@smonn/ids 0.8.0 → 0.9.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.
- package/README.md +210 -32
- package/dist/adapter-types-BY-wrYYB.mjs +27 -0
- package/dist/adapter-types-BY-wrYYB.mjs.map +1 -0
- package/dist/adapter-types-unUcmMXC.d.mts +20 -0
- package/dist/adapter-types-unUcmMXC.d.mts.map +1 -0
- package/dist/cli.mjs +342 -71
- package/dist/cli.mjs.map +1 -1
- package/dist/{codec-shell-DH-UO4UR.mjs → codec-shell-CW2sD6BU.mjs} +6 -5
- package/dist/codec-shell-CW2sD6BU.mjs.map +1 -0
- package/dist/drizzle.d.mts +33 -2
- package/dist/drizzle.d.mts.map +1 -0
- package/dist/drizzle.mjs +2 -3
- package/dist/drizzle.mjs.map +1 -1
- package/dist/express.d.mts +2 -5
- package/dist/express.d.mts.map +1 -1
- package/dist/express.mjs +3 -8
- package/dist/express.mjs.map +1 -1
- package/dist/fastify.d.mts +2 -5
- package/dist/fastify.d.mts.map +1 -1
- package/dist/fastify.mjs +3 -8
- package/dist/fastify.mjs.map +1 -1
- package/dist/hono.d.mts +2 -5
- package/dist/hono.d.mts.map +1 -1
- package/dist/hono.mjs +3 -8
- package/dist/hono.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/key-material-gOnqTNoV.mjs +137 -0
- package/dist/key-material-gOnqTNoV.mjs.map +1 -0
- package/dist/kysely.d.mts +1 -1
- package/dist/kysely.mjs +2 -3
- package/dist/kysely.mjs.map +1 -1
- package/dist/{opaque-uvjOFY_0.mjs → opaque-BpqxV8oB.mjs} +12 -48
- package/dist/opaque-BpqxV8oB.mjs.map +1 -0
- package/dist/opaque.d.mts +8 -0
- package/dist/opaque.d.mts.map +1 -1
- package/dist/opaque.mjs +1 -1
- package/dist/prisma.d.mts +4 -18
- package/dist/prisma.d.mts.map +1 -1
- package/dist/prisma.mjs +3 -5
- package/dist/prisma.mjs.map +1 -1
- package/dist/{reverse-BgFU6JHw.mjs → reverse-d5uEoIET.mjs} +5 -7
- package/dist/reverse-d5uEoIET.mjs.map +1 -0
- package/dist/reverse.d.mts.map +1 -1
- package/dist/reverse.mjs +1 -1
- package/dist/rng-CPJOx_nE.mjs +9 -0
- package/dist/rng-CPJOx_nE.mjs.map +1 -0
- package/dist/signed-BnRSC03a.mjs +207 -0
- package/dist/signed-BnRSC03a.mjs.map +1 -0
- package/dist/signed.d.mts.map +1 -1
- package/dist/signed.mjs +1 -255
- package/dist/{timestamp-B5_UCzc6.mjs → timestamp-BbZL8hwg.mjs} +5 -5
- package/dist/{timestamp-B5_UCzc6.mjs.map → timestamp-BbZL8hwg.mjs.map} +1 -1
- package/dist/{timestamp-bytes-BBY7JI33.mjs → timestamp-bytes-DoFjLjDp.mjs} +3 -2
- package/dist/timestamp-bytes-DoFjLjDp.mjs.map +1 -0
- package/dist/{wrapped-0vL72Nje.mjs → wrapped-BI9UXnAm.mjs} +33 -62
- package/dist/wrapped-BI9UXnAm.mjs.map +1 -0
- package/dist/wrapped.d.mts.map +1 -1
- package/dist/wrapped.mjs +1 -1
- package/package.json +5 -5
- package/dist/adapter-types-oHCCSgOO.d.mts +0 -12
- package/dist/adapter-types-oHCCSgOO.d.mts.map +0 -1
- package/dist/bytes-lhzKVaBV.mjs +0 -53
- package/dist/bytes-lhzKVaBV.mjs.map +0 -1
- package/dist/codec-shell-DH-UO4UR.mjs.map +0 -1
- package/dist/drizzle-CeSni5PB.d.mts +0 -44
- package/dist/drizzle-CeSni5PB.d.mts.map +0 -1
- package/dist/opaque-uvjOFY_0.mjs.map +0 -1
- package/dist/reverse-BgFU6JHw.mjs.map +0 -1
- package/dist/signed.mjs.map +0 -1
- package/dist/timestamp-bytes-BBY7JI33.mjs.map +0 -1
- package/dist/wrapped-0vL72Nje.mjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { n as isIdsError } from "./error-Cp5qYZcv.mjs";
|
|
3
|
-
import { t as createTimestampId } from "./timestamp-
|
|
4
|
-
import { i as importOpaqueKey, n as decodeOpaqueKey, r as encodeOpaqueKey, t as createOpaqueTimestampId } from "./opaque-
|
|
5
|
-
import { t as createReverseTimestampId } from "./reverse-
|
|
6
|
-
import { i as
|
|
3
|
+
import { t as createTimestampId } from "./timestamp-BbZL8hwg.mjs";
|
|
4
|
+
import { i as importOpaqueKey, n as decodeOpaqueKey, r as encodeOpaqueKey, t as createOpaqueTimestampId } from "./opaque-BpqxV8oB.mjs";
|
|
5
|
+
import { t as createReverseTimestampId } from "./reverse-d5uEoIET.mjs";
|
|
6
|
+
import { i as importSigningKey, n as decodeSigningKey, r as encodeSigningKey, t as createSignedTimestampId } from "./signed-BnRSC03a.mjs";
|
|
7
|
+
import { i as importWrappingKey, n as decodeWrappingKey, r as encodeWrappingKey, t as createWrappedKeyId } from "./wrapped-BI9UXnAm.mjs";
|
|
7
8
|
//#region src/cli/codec-options.ts
|
|
8
9
|
function codecOpts(opts) {
|
|
9
10
|
const o = { allowDuplicateBrand: true };
|
|
@@ -26,6 +27,14 @@ function formatWrappedInspectOutput(result) {
|
|
|
26
27
|
""
|
|
27
28
|
].join("\n");
|
|
28
29
|
}
|
|
30
|
+
function formatSignedInspectOutput(result) {
|
|
31
|
+
const relative = formatRelative(result.timestamp.getTime(), result.nowMs);
|
|
32
|
+
const inputLine = describeInputForm(result.input, result.canonical);
|
|
33
|
+
const lines = [`brand: ${result.brand}`, `timestamp: ${result.timestamp.toISOString()} (${relative})`];
|
|
34
|
+
lines.push(`verification: ${result.verification}`);
|
|
35
|
+
lines.push(`canonical: ${result.canonical}`, `input: ${inputLine}`, "");
|
|
36
|
+
return lines.join("\n");
|
|
37
|
+
}
|
|
29
38
|
function formatInspectOutput(result) {
|
|
30
39
|
const relative = formatRelative(result.timestamp.getTime(), result.nowMs);
|
|
31
40
|
const inputLine = describeInputForm(result.input, result.canonical);
|
|
@@ -86,19 +95,12 @@ function splitFlagToken(arg) {
|
|
|
86
95
|
inlineValue: arg.slice(eq + 1)
|
|
87
96
|
};
|
|
88
97
|
}
|
|
89
|
-
function splitFlags(args) {
|
|
98
|
+
function splitFlags(args, valueFlags) {
|
|
90
99
|
const flags = /* @__PURE__ */ new Set();
|
|
91
100
|
const values = /* @__PURE__ */ new Map();
|
|
92
101
|
const positionals = [];
|
|
93
102
|
const errors = [];
|
|
94
103
|
const seenFlags = /* @__PURE__ */ new Set();
|
|
95
|
-
const valueFlags = new Set([
|
|
96
|
-
"--count",
|
|
97
|
-
"-c",
|
|
98
|
-
"--bits",
|
|
99
|
-
"--key-format",
|
|
100
|
-
"--kind"
|
|
101
|
-
]);
|
|
102
104
|
const addFlag = (flag) => {
|
|
103
105
|
const canonical = canonicalFlag(flag);
|
|
104
106
|
if (seenFlags.has(canonical)) errors.push(`duplicate flag: ${canonical}`);
|
|
@@ -108,11 +110,6 @@ function splitFlags(args) {
|
|
|
108
110
|
for (let i = 0; i < args.length; i++) {
|
|
109
111
|
const raw = args[i];
|
|
110
112
|
const { flag, inlineValue } = splitFlagToken(raw);
|
|
111
|
-
if (flag === "--opaque" || flag === "--wrapped" || flag === "--reverse") {
|
|
112
|
-
addFlag(flag);
|
|
113
|
-
if (inlineValue !== void 0) errors.push(`flag does not take a value: ${flag}`);
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
113
|
if (valueFlags.has(flag)) {
|
|
117
114
|
if (inlineValue !== void 0) {
|
|
118
115
|
addFlag(flag);
|
|
@@ -132,6 +129,7 @@ function splitFlags(args) {
|
|
|
132
129
|
}
|
|
133
130
|
if (flag.startsWith("-")) {
|
|
134
131
|
addFlag(flag);
|
|
132
|
+
if (inlineValue !== void 0) errors.push(`flag does not take a value: ${flag}`);
|
|
135
133
|
continue;
|
|
136
134
|
}
|
|
137
135
|
positionals.push(raw);
|
|
@@ -147,10 +145,11 @@ function canonicalFlag(flag) {
|
|
|
147
145
|
if (flag === "-c") return "--count";
|
|
148
146
|
return flag;
|
|
149
147
|
}
|
|
150
|
-
const knownFlags = new Set([
|
|
148
|
+
const knownFlags = /* @__PURE__ */ new Set([
|
|
151
149
|
"--opaque",
|
|
152
150
|
"--wrapped",
|
|
153
151
|
"--reverse",
|
|
152
|
+
"--signed",
|
|
154
153
|
"--kind",
|
|
155
154
|
"--key-format",
|
|
156
155
|
"--count",
|
|
@@ -189,48 +188,83 @@ function isKindError(result) {
|
|
|
189
188
|
return result !== "u32" && result !== "i32" && result !== "u64" && result !== "i64";
|
|
190
189
|
}
|
|
191
190
|
//#endregion
|
|
192
|
-
//#region src/cli/
|
|
191
|
+
//#region src/cli/key-io.ts
|
|
193
192
|
function isKeyFormatError(result) {
|
|
194
193
|
return result !== "hex" && result !== "base64url";
|
|
195
194
|
}
|
|
196
|
-
function parseKeyFormatFlag
|
|
195
|
+
function parseKeyFormatFlag(values) {
|
|
197
196
|
const fromFlag = values.get("--key-format");
|
|
198
197
|
if (fromFlag === void 0) return void 0;
|
|
199
198
|
if (fromFlag === "") return "--key-format requires a value";
|
|
200
199
|
if (fromFlag === "hex" || fromFlag === "base64url") return fromFlag;
|
|
201
200
|
return `--key-format must be hex or base64url, got '${fromFlag}'`;
|
|
202
201
|
}
|
|
203
|
-
function
|
|
204
|
-
const fromFlag = parseKeyFormatFlag
|
|
202
|
+
function parseKeyFormatFromFlag(values) {
|
|
203
|
+
const fromFlag = parseKeyFormatFlag(values);
|
|
205
204
|
if (fromFlag === void 0) return "hex";
|
|
206
205
|
return fromFlag;
|
|
207
206
|
}
|
|
208
|
-
function
|
|
209
|
-
const fromFlag = parseKeyFormatFlag
|
|
207
|
+
function parseKeyFormat(values, opts, facet) {
|
|
208
|
+
const fromFlag = parseKeyFormatFlag(values);
|
|
210
209
|
if (fromFlag !== void 0) return fromFlag;
|
|
211
|
-
const fromEnv = (opts.env ?? process.env).
|
|
210
|
+
const fromEnv = (opts.env ?? process.env)[facet.formatEnvVar];
|
|
212
211
|
if (fromEnv === void 0 || fromEnv === "") return "hex";
|
|
213
212
|
if (fromEnv === "hex" || fromEnv === "base64url") return fromEnv;
|
|
214
|
-
return
|
|
213
|
+
return `${facet.formatEnvVar} must be hex or base64url, got '${fromEnv}'`;
|
|
215
214
|
}
|
|
216
|
-
async function
|
|
217
|
-
const raw = (opts.env ?? process.env).
|
|
218
|
-
if (raw === void 0 || raw === "") return
|
|
215
|
+
async function loadKey(opts, format, facet) {
|
|
216
|
+
const raw = (opts.env ?? process.env)[facet.envVar];
|
|
217
|
+
if (raw === void 0 || raw === "") return `missing ${facet.envVar} environment variable`;
|
|
219
218
|
try {
|
|
220
|
-
return
|
|
219
|
+
return await facet.import(facet.decode(raw, format));
|
|
221
220
|
} catch (err) {
|
|
222
221
|
return err.message;
|
|
223
222
|
}
|
|
224
223
|
}
|
|
225
224
|
//#endregion
|
|
225
|
+
//#region src/cli/opaque-key.ts
|
|
226
|
+
const opaqueFacet = {
|
|
227
|
+
envVar: "IDS_KEY",
|
|
228
|
+
formatEnvVar: "IDS_KEY_FORMAT",
|
|
229
|
+
decode: decodeOpaqueKey,
|
|
230
|
+
import: importOpaqueKey
|
|
231
|
+
};
|
|
232
|
+
function parseOpaqueKeyFormat(values, opts) {
|
|
233
|
+
return parseKeyFormat(values, opts, opaqueFacet);
|
|
234
|
+
}
|
|
235
|
+
async function loadOpaqueKey(opts, format) {
|
|
236
|
+
return loadKey(opts, format, opaqueFacet);
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/cli/signing-key.ts
|
|
240
|
+
const signingFacet = {
|
|
241
|
+
envVar: "IDS_SIGNING_KEY",
|
|
242
|
+
formatEnvVar: "IDS_SIGNING_KEY_FORMAT",
|
|
243
|
+
decode: decodeSigningKey,
|
|
244
|
+
import: importSigningKey
|
|
245
|
+
};
|
|
246
|
+
function parseSigningKeyFormat(values, opts) {
|
|
247
|
+
return parseKeyFormat(values, opts, signingFacet);
|
|
248
|
+
}
|
|
249
|
+
async function loadSigningKey(opts, format) {
|
|
250
|
+
return loadKey(opts, format, signingFacet);
|
|
251
|
+
}
|
|
252
|
+
//#endregion
|
|
226
253
|
//#region src/cli/commands/generate.ts
|
|
227
254
|
function runGenerate(args, opts) {
|
|
228
|
-
const { flags, values, positionals, errors } = splitFlags(args
|
|
229
|
-
|
|
255
|
+
const { flags, values, positionals, errors } = splitFlags(args, /* @__PURE__ */ new Set([
|
|
256
|
+
"--count",
|
|
257
|
+
"-c",
|
|
258
|
+
"--bits",
|
|
259
|
+
"--key-format",
|
|
260
|
+
"--kind"
|
|
261
|
+
]));
|
|
262
|
+
const unsupported = unsupportedFlagForCommand("generate", flags, /* @__PURE__ */ new Set([
|
|
230
263
|
"--count",
|
|
231
264
|
"-c",
|
|
232
265
|
"--opaque",
|
|
233
266
|
"--reverse",
|
|
267
|
+
"--signed",
|
|
234
268
|
"--key-format"
|
|
235
269
|
]));
|
|
236
270
|
if (unsupported !== void 0) {
|
|
@@ -254,12 +288,21 @@ function runGenerate(args, opts) {
|
|
|
254
288
|
}
|
|
255
289
|
const opaque = flags.has("--opaque");
|
|
256
290
|
const reverse = flags.has("--reverse");
|
|
291
|
+
const signed = flags.has("--signed");
|
|
257
292
|
if (reverse && opaque) {
|
|
258
293
|
opts.stderr("cannot use --reverse and --opaque together\n");
|
|
259
294
|
return Promise.resolve(1);
|
|
260
295
|
}
|
|
261
|
-
if (
|
|
262
|
-
opts.stderr("--
|
|
296
|
+
if (signed && opaque) {
|
|
297
|
+
opts.stderr("cannot use --signed and --opaque together\n");
|
|
298
|
+
return Promise.resolve(1);
|
|
299
|
+
}
|
|
300
|
+
if (signed && reverse) {
|
|
301
|
+
opts.stderr("cannot use --signed and --reverse together\n");
|
|
302
|
+
return Promise.resolve(1);
|
|
303
|
+
}
|
|
304
|
+
if (!opaque && !signed && flags.has("--key-format")) {
|
|
305
|
+
opts.stderr("--key-format requires --opaque or --signed\n");
|
|
263
306
|
return Promise.resolve(1);
|
|
264
307
|
}
|
|
265
308
|
if (opaque) {
|
|
@@ -270,6 +313,14 @@ function runGenerate(args, opts) {
|
|
|
270
313
|
}
|
|
271
314
|
return runOpaqueGenerate(brand ?? "", count, format, opts);
|
|
272
315
|
}
|
|
316
|
+
if (signed) {
|
|
317
|
+
const format = parseSigningKeyFormat(values, opts);
|
|
318
|
+
if (isKeyFormatError(format)) {
|
|
319
|
+
opts.stderr(format + "\n");
|
|
320
|
+
return Promise.resolve(1);
|
|
321
|
+
}
|
|
322
|
+
return runSignedGenerate(brand ?? "", count, format, opts);
|
|
323
|
+
}
|
|
273
324
|
if (reverse) {
|
|
274
325
|
let codec;
|
|
275
326
|
try {
|
|
@@ -310,31 +361,39 @@ async function runOpaqueGenerate(brand, count, format, opts) {
|
|
|
310
361
|
for (let i = 0; i < count; i++) opts.stdout(await codec.generate() + "\n");
|
|
311
362
|
return 0;
|
|
312
363
|
}
|
|
364
|
+
async function runSignedGenerate(brand, count, format, opts) {
|
|
365
|
+
const keyResult = await loadSigningKey(opts, format);
|
|
366
|
+
if (typeof keyResult === "string") {
|
|
367
|
+
opts.stderr(keyResult + "\n");
|
|
368
|
+
return 1;
|
|
369
|
+
}
|
|
370
|
+
let codec;
|
|
371
|
+
try {
|
|
372
|
+
codec = createSignedTimestampId(brand, {
|
|
373
|
+
keys: [keyResult],
|
|
374
|
+
allowDuplicateBrand: true,
|
|
375
|
+
...codecOpts(opts)
|
|
376
|
+
});
|
|
377
|
+
} catch (err) {
|
|
378
|
+
opts.stderr(formatCliError(err) + "\n");
|
|
379
|
+
return 1;
|
|
380
|
+
}
|
|
381
|
+
for (let i = 0; i < count; i++) opts.stdout(await codec.generate() + "\n");
|
|
382
|
+
return 0;
|
|
383
|
+
}
|
|
313
384
|
//#endregion
|
|
314
385
|
//#region src/cli/wrapping-key.ts
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
386
|
+
const wrappingFacet = {
|
|
387
|
+
envVar: "IDS_WRAPPING_KEY",
|
|
388
|
+
formatEnvVar: "IDS_WRAPPING_KEY_FORMAT",
|
|
389
|
+
decode: decodeWrappingKey,
|
|
390
|
+
import: importWrappingKey
|
|
391
|
+
};
|
|
322
392
|
function parseWrappingKeyFormat(values, opts) {
|
|
323
|
-
|
|
324
|
-
if (fromFlag !== void 0) return fromFlag;
|
|
325
|
-
const fromEnv = (opts.env ?? process.env).IDS_WRAPPING_KEY_FORMAT;
|
|
326
|
-
if (fromEnv === void 0 || fromEnv === "") return "hex";
|
|
327
|
-
if (fromEnv === "hex" || fromEnv === "base64url") return fromEnv;
|
|
328
|
-
return `IDS_WRAPPING_KEY_FORMAT must be hex or base64url, got '${fromEnv}'`;
|
|
393
|
+
return parseKeyFormat(values, opts, wrappingFacet);
|
|
329
394
|
}
|
|
330
395
|
async function loadWrappingKey(opts, format) {
|
|
331
|
-
|
|
332
|
-
if (raw === void 0 || raw === "") return "missing IDS_WRAPPING_KEY environment variable";
|
|
333
|
-
try {
|
|
334
|
-
return importWrappingKey(decodeWrappingKey(raw, format));
|
|
335
|
-
} catch (err) {
|
|
336
|
-
return err.message;
|
|
337
|
-
}
|
|
396
|
+
return loadKey(opts, format, wrappingFacet);
|
|
338
397
|
}
|
|
339
398
|
//#endregion
|
|
340
399
|
//#region src/cli/usage.ts
|
|
@@ -343,30 +402,41 @@ function usage() {
|
|
|
343
402
|
"Usage: ids <subcommand> [args]",
|
|
344
403
|
"",
|
|
345
404
|
"Subcommands:",
|
|
346
|
-
" inspect, i <id> [--opaque] [--wrapped --kind u32|i32|u64|i64] [--reverse] [--key-format hex|base64url]",
|
|
405
|
+
" inspect, i <id> [--opaque] [--wrapped --kind u32|i32|u64|i64] [--reverse] [--signed] [--key-format hex|base64url]",
|
|
347
406
|
" Decode an ID and print brand, timestamp (or lookup key), and canonical form.",
|
|
348
407
|
" --opaque reads the AES key from IDS_KEY (hex by default; IDS_KEY_FORMAT or --key-format).",
|
|
349
408
|
" --wrapped reads the wrapping key from IDS_WRAPPING_KEY (hex by default; IDS_WRAPPING_KEY_FORMAT or --key-format).",
|
|
350
409
|
" --kind is required with --wrapped: u32, i32, u64, or i64.",
|
|
351
410
|
" --reverse decodes a Reverse Timestamp ID (newest-first sort order).",
|
|
352
|
-
"
|
|
411
|
+
" --signed decodes a Signed Timestamp ID; reads signing key from IDS_SIGNING_KEY (hex by default; IDS_SIGNING_KEY_FORMAT or --key-format).",
|
|
412
|
+
" Without IDS_SIGNING_KEY, --signed prints the timestamp only (no verification). With IDS_SIGNING_KEY, prints verification: ok or failed.",
|
|
413
|
+
" generate, g <brand> [--count, -c N] [--opaque] [--reverse] [--signed] [--key-format hex|base64url]",
|
|
353
414
|
` Mint 1..${maxGenerateCount} canonical IDs for the given brand.`,
|
|
354
415
|
" --opaque reads the AES key from IDS_KEY (hex by default; IDS_KEY_FORMAT or --key-format).",
|
|
355
416
|
" --reverse mints Reverse Timestamp IDs (newest-first sort order).",
|
|
356
|
-
"
|
|
357
|
-
"
|
|
417
|
+
" --signed mints Signed Timestamp IDs; reads signing key from IDS_SIGNING_KEY (hex by default; IDS_SIGNING_KEY_FORMAT or --key-format).",
|
|
418
|
+
" keygen, k [--wrapped] [--signed] [--bits 128|192|256] [--key-format hex|base64url]",
|
|
419
|
+
" Emit a random key for importOpaqueKey, importWrappingKey, or importSigningKey (stdout only).",
|
|
358
420
|
" --wrapped emits a wrapping key for importWrappingKey instead (IDS_WRAPPING_KEY).",
|
|
421
|
+
" --signed emits a signing key for importSigningKey instead (IDS_SIGNING_KEY; hex by default; IDS_SIGNING_KEY_FORMAT or --key-format).",
|
|
359
422
|
""
|
|
360
423
|
].join("\n");
|
|
361
424
|
}
|
|
362
425
|
//#endregion
|
|
363
426
|
//#region src/cli/commands/inspect.ts
|
|
364
427
|
function runInspect(args, opts) {
|
|
365
|
-
const { flags, values, positionals, errors } = splitFlags(args
|
|
366
|
-
|
|
428
|
+
const { flags, values, positionals, errors } = splitFlags(args, /* @__PURE__ */ new Set([
|
|
429
|
+
"--count",
|
|
430
|
+
"-c",
|
|
431
|
+
"--bits",
|
|
432
|
+
"--key-format",
|
|
433
|
+
"--kind"
|
|
434
|
+
]));
|
|
435
|
+
const unsupported = unsupportedFlagForCommand("inspect", flags, /* @__PURE__ */ new Set([
|
|
367
436
|
"--opaque",
|
|
368
437
|
"--wrapped",
|
|
369
438
|
"--reverse",
|
|
439
|
+
"--signed",
|
|
370
440
|
"--kind",
|
|
371
441
|
"--key-format"
|
|
372
442
|
]));
|
|
@@ -391,6 +461,7 @@ function runInspect(args, opts) {
|
|
|
391
461
|
const opaque = flags.has("--opaque");
|
|
392
462
|
const wrapped = flags.has("--wrapped");
|
|
393
463
|
const reverse = flags.has("--reverse");
|
|
464
|
+
const signed = flags.has("--signed");
|
|
394
465
|
if (opaque && wrapped) {
|
|
395
466
|
opts.stderr("cannot use --wrapped and --opaque together\n");
|
|
396
467
|
return Promise.resolve(1);
|
|
@@ -403,8 +474,20 @@ function runInspect(args, opts) {
|
|
|
403
474
|
opts.stderr("cannot use --reverse and --wrapped together\n");
|
|
404
475
|
return Promise.resolve(1);
|
|
405
476
|
}
|
|
406
|
-
if (
|
|
407
|
-
opts.stderr("--
|
|
477
|
+
if (signed && opaque) {
|
|
478
|
+
opts.stderr("cannot use --signed and --opaque together\n");
|
|
479
|
+
return Promise.resolve(1);
|
|
480
|
+
}
|
|
481
|
+
if (signed && wrapped) {
|
|
482
|
+
opts.stderr("cannot use --signed and --wrapped together\n");
|
|
483
|
+
return Promise.resolve(1);
|
|
484
|
+
}
|
|
485
|
+
if (signed && reverse) {
|
|
486
|
+
opts.stderr("cannot use --signed and --reverse together\n");
|
|
487
|
+
return Promise.resolve(1);
|
|
488
|
+
}
|
|
489
|
+
if (!opaque && !wrapped && !signed && flags.has("--key-format")) {
|
|
490
|
+
opts.stderr("--key-format requires --opaque, --wrapped, or --signed\n");
|
|
408
491
|
return Promise.resolve(1);
|
|
409
492
|
}
|
|
410
493
|
const brand = input.slice(0, 3).toLowerCase();
|
|
@@ -433,6 +516,14 @@ function runInspect(args, opts) {
|
|
|
433
516
|
}
|
|
434
517
|
return runOpaqueInspect(brand, input, format, opts);
|
|
435
518
|
}
|
|
519
|
+
if (signed) {
|
|
520
|
+
const format = parseSigningKeyFormat(values, opts);
|
|
521
|
+
if (isKeyFormatError(format)) {
|
|
522
|
+
opts.stderr(format + "\n");
|
|
523
|
+
return Promise.resolve(1);
|
|
524
|
+
}
|
|
525
|
+
return runSignedInspect(brand, input, format, opts);
|
|
526
|
+
}
|
|
436
527
|
if (reverse) {
|
|
437
528
|
let reverseCodec;
|
|
438
529
|
try {
|
|
@@ -554,15 +645,186 @@ async function runOpaqueInspect(brand, input, format, opts) {
|
|
|
554
645
|
}));
|
|
555
646
|
return 0;
|
|
556
647
|
}
|
|
648
|
+
async function runSignedInspect(brand, input, format, opts) {
|
|
649
|
+
let structCodec;
|
|
650
|
+
try {
|
|
651
|
+
structCodec = createTimestampId(brand, codecOpts(opts));
|
|
652
|
+
} catch (err) {
|
|
653
|
+
opts.stderr(formatCliError(err) + "\n");
|
|
654
|
+
return 1;
|
|
655
|
+
}
|
|
656
|
+
const validation = structCodec["~standard"].validate(input);
|
|
657
|
+
if (validation.issues) {
|
|
658
|
+
opts.stderr(validation.issues[0].message + "\n");
|
|
659
|
+
return 1;
|
|
660
|
+
}
|
|
661
|
+
const canonical = validation.value;
|
|
662
|
+
const timestamp = structCodec.extractTimestamp(canonical);
|
|
663
|
+
const nowMs = (opts.now ?? Date.now)();
|
|
664
|
+
const keyResult = await loadSigningKey(opts, format);
|
|
665
|
+
if (typeof keyResult === "string") {
|
|
666
|
+
opts.stdout(formatSignedInspectOutput({
|
|
667
|
+
brand,
|
|
668
|
+
timestamp,
|
|
669
|
+
canonical,
|
|
670
|
+
input,
|
|
671
|
+
nowMs,
|
|
672
|
+
verification: "unavailable"
|
|
673
|
+
}));
|
|
674
|
+
opts.stderr(keyResult + "\n");
|
|
675
|
+
return 1;
|
|
676
|
+
}
|
|
677
|
+
const verifyResult = await createSignedTimestampId(brand, {
|
|
678
|
+
keys: [keyResult],
|
|
679
|
+
allowDuplicateBrand: true,
|
|
680
|
+
...codecOpts(opts)
|
|
681
|
+
}).safeVerify(input);
|
|
682
|
+
if (!verifyResult.ok) {
|
|
683
|
+
/* v8 ignore next 4 -- defensive: both codecs share the same wire parse so ParseError
|
|
684
|
+
is unreachable after the createTimestampId pre-validation above passes */
|
|
685
|
+
if (verifyResult.error !== "verification_failed") {
|
|
686
|
+
opts.stderr(verifyResult.error + "\n");
|
|
687
|
+
return 1;
|
|
688
|
+
}
|
|
689
|
+
opts.stdout(formatSignedInspectOutput({
|
|
690
|
+
brand,
|
|
691
|
+
timestamp,
|
|
692
|
+
canonical,
|
|
693
|
+
input,
|
|
694
|
+
nowMs,
|
|
695
|
+
verification: "failed"
|
|
696
|
+
}));
|
|
697
|
+
opts.stderr("verification_failed: verification failed\n");
|
|
698
|
+
return 1;
|
|
699
|
+
}
|
|
700
|
+
opts.stdout(formatSignedInspectOutput({
|
|
701
|
+
brand,
|
|
702
|
+
timestamp,
|
|
703
|
+
canonical: verifyResult.id,
|
|
704
|
+
input,
|
|
705
|
+
nowMs,
|
|
706
|
+
verification: "ok"
|
|
707
|
+
}));
|
|
708
|
+
return 0;
|
|
709
|
+
}
|
|
710
|
+
//#endregion
|
|
711
|
+
//#region src/cli/variants.ts
|
|
712
|
+
const opaqueVariant = {
|
|
713
|
+
flag: "--opaque",
|
|
714
|
+
key: {
|
|
715
|
+
envVar: "IDS_KEY",
|
|
716
|
+
formatEnvVar: "IDS_KEY_FORMAT",
|
|
717
|
+
encode: encodeOpaqueKey,
|
|
718
|
+
decode: decodeOpaqueKey,
|
|
719
|
+
import: importOpaqueKey
|
|
720
|
+
},
|
|
721
|
+
inspectMode: "keyed-readable",
|
|
722
|
+
construct(brand, opts, key) {
|
|
723
|
+
try {
|
|
724
|
+
return createOpaqueTimestampId(brand, {
|
|
725
|
+
key,
|
|
726
|
+
...codecOpts(opts)
|
|
727
|
+
});
|
|
728
|
+
} catch (err) {
|
|
729
|
+
return formatCliError(err);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
const reverseVariant = {
|
|
734
|
+
flag: "--reverse",
|
|
735
|
+
inspectMode: "readable",
|
|
736
|
+
construct(brand, opts) {
|
|
737
|
+
try {
|
|
738
|
+
return createReverseTimestampId(brand, codecOpts(opts));
|
|
739
|
+
} catch (err) {
|
|
740
|
+
return formatCliError(err);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
const wrappedVariant = {
|
|
745
|
+
flag: "--wrapped",
|
|
746
|
+
key: {
|
|
747
|
+
envVar: "IDS_WRAPPING_KEY",
|
|
748
|
+
formatEnvVar: "IDS_WRAPPING_KEY_FORMAT",
|
|
749
|
+
encode: encodeWrappingKey,
|
|
750
|
+
decode: decodeWrappingKey,
|
|
751
|
+
import: importWrappingKey
|
|
752
|
+
},
|
|
753
|
+
inspectMode: "unwrap",
|
|
754
|
+
extraFlags: ["--kind"],
|
|
755
|
+
construct(brand, _opts, key, values) {
|
|
756
|
+
const kind = parseKind(values ?? /* @__PURE__ */ new Map());
|
|
757
|
+
if (kind === void 0) return "--kind is required with --wrapped";
|
|
758
|
+
if (isKindError(kind)) return kind;
|
|
759
|
+
try {
|
|
760
|
+
return createWrappedKeyId(brand, {
|
|
761
|
+
kind,
|
|
762
|
+
keys: [key],
|
|
763
|
+
allowDuplicateBrand: true
|
|
764
|
+
});
|
|
765
|
+
} catch (err) {
|
|
766
|
+
return formatCliError(err);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
const signedVariant = {
|
|
771
|
+
flag: "--signed",
|
|
772
|
+
key: {
|
|
773
|
+
envVar: "IDS_SIGNING_KEY",
|
|
774
|
+
formatEnvVar: "IDS_SIGNING_KEY_FORMAT",
|
|
775
|
+
encode: encodeSigningKey,
|
|
776
|
+
decode: decodeSigningKey,
|
|
777
|
+
import: importSigningKey
|
|
778
|
+
},
|
|
779
|
+
inspectMode: "verify",
|
|
780
|
+
construct(brand, opts, key) {
|
|
781
|
+
try {
|
|
782
|
+
return createSignedTimestampId(brand, {
|
|
783
|
+
keys: [key],
|
|
784
|
+
...codecOpts(opts)
|
|
785
|
+
});
|
|
786
|
+
} catch (err) {
|
|
787
|
+
return formatCliError(err);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
const conflictPriorityOrder = [
|
|
792
|
+
signedVariant,
|
|
793
|
+
reverseVariant,
|
|
794
|
+
wrappedVariant,
|
|
795
|
+
opaqueVariant
|
|
796
|
+
];
|
|
797
|
+
const keygenPolicy = {
|
|
798
|
+
default: opaqueVariant,
|
|
799
|
+
selectable: [wrappedVariant, signedVariant],
|
|
800
|
+
intrinsicFlags: ["--bits"]
|
|
801
|
+
};
|
|
802
|
+
//#endregion
|
|
803
|
+
//#region src/cli/dispatch.ts
|
|
804
|
+
function deriveAllowedFlags(policy) {
|
|
805
|
+
const flags = new Set(policy.intrinsicFlags);
|
|
806
|
+
let hasKeyed = policy.default.key !== void 0;
|
|
807
|
+
for (const v of policy.selectable) {
|
|
808
|
+
if (v.flag !== void 0) flags.add(v.flag);
|
|
809
|
+
if (v.key !== void 0) hasKeyed = true;
|
|
810
|
+
if (v.extraFlags !== void 0) for (const f of v.extraFlags) flags.add(f);
|
|
811
|
+
}
|
|
812
|
+
if (hasKeyed) flags.add("--key-format");
|
|
813
|
+
return flags;
|
|
814
|
+
}
|
|
815
|
+
function resolveVariant(policy, flags) {
|
|
816
|
+
const selected = conflictPriorityOrder.filter((v) => policy.selectable.includes(v) && v.flag !== void 0 && flags.has(v.flag));
|
|
817
|
+
if (selected.length === 0) return policy.default;
|
|
818
|
+
if (selected.length === 1) return selected[0];
|
|
819
|
+
return `cannot use ${selected[0].flag} and ${selected[1].flag} together`;
|
|
820
|
+
}
|
|
557
821
|
//#endregion
|
|
558
822
|
//#region src/cli/commands/keygen.ts
|
|
559
823
|
function runKeygen(args, opts) {
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
"--key-format"
|
|
565
|
-
]));
|
|
824
|
+
const allowedFlags = deriveAllowedFlags(keygenPolicy);
|
|
825
|
+
const variantExtraFlags = new Set(keygenPolicy.selectable.flatMap((v) => v.extraFlags ?? []));
|
|
826
|
+
const { flags, values, positionals, errors } = splitFlags(args, allowedFlags);
|
|
827
|
+
const unsupported = unsupportedFlagForCommand("keygen", flags, new Set([...allowedFlags].filter((f) => !variantExtraFlags.has(f))));
|
|
566
828
|
if (unsupported !== void 0) {
|
|
567
829
|
opts.stderr(unsupported + "\n");
|
|
568
830
|
return Promise.resolve(1);
|
|
@@ -576,20 +838,29 @@ function runKeygen(args, opts) {
|
|
|
576
838
|
opts.stderr(`unexpected argument: ${extra}\n`);
|
|
577
839
|
return Promise.resolve(1);
|
|
578
840
|
}
|
|
841
|
+
const variant = resolveVariant(keygenPolicy, flags);
|
|
842
|
+
if (typeof variant === "string") {
|
|
843
|
+
opts.stderr(variant + "\n");
|
|
844
|
+
return Promise.resolve(1);
|
|
845
|
+
}
|
|
579
846
|
const bits = parseBits(values);
|
|
580
847
|
if (typeof bits === "string") {
|
|
581
848
|
opts.stderr(bits + "\n");
|
|
582
849
|
return Promise.resolve(1);
|
|
583
850
|
}
|
|
584
|
-
const format =
|
|
851
|
+
const format = parseKeyFormatFromFlag(values);
|
|
585
852
|
if (isKeyFormatError(format)) {
|
|
586
853
|
opts.stderr(format + "\n");
|
|
587
854
|
return Promise.resolve(1);
|
|
588
855
|
}
|
|
856
|
+
/* v8 ignore next 4 -- defensive guard; all keygenPolicy variants have key defined */
|
|
857
|
+
if (variant.key === void 0) {
|
|
858
|
+
opts.stderr("internal: keygen policy variant has no key facet\n");
|
|
859
|
+
return Promise.resolve(1);
|
|
860
|
+
}
|
|
589
861
|
const bytes = new Uint8Array(bits / 8);
|
|
590
862
|
crypto.getRandomValues(bytes);
|
|
591
|
-
|
|
592
|
-
opts.stdout((wrapped ? encodeWrappingKey(bytes, format) : encodeOpaqueKey(bytes, format)) + "\n");
|
|
863
|
+
opts.stdout(variant.key.encode(bytes, format) + "\n");
|
|
593
864
|
return Promise.resolve(0);
|
|
594
865
|
}
|
|
595
866
|
//#endregion
|