@smonn/ids 0.9.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 +85 -12
- package/dist/cli.mjs +155 -50
- package/dist/cli.mjs.map +1 -1
- package/dist/{codec-shell-C7_B4oum.mjs → codec-shell-CW2sD6BU.mjs} +3 -3
- package/dist/{codec-shell-C7_B4oum.mjs.map → codec-shell-CW2sD6BU.mjs.map} +1 -1
- package/dist/drizzle.d.mts +32 -2
- package/dist/drizzle.d.mts.map +1 -0
- package/dist/index.mjs +1 -1
- package/dist/{key-material-DUHhmMq-.mjs → key-material-gOnqTNoV.mjs} +3 -3
- package/dist/{key-material-DUHhmMq-.mjs.map → key-material-gOnqTNoV.mjs.map} +1 -1
- package/dist/kysely.d.mts.map +1 -1
- package/dist/kysely.mjs +2 -3
- package/dist/kysely.mjs.map +1 -1
- package/dist/{opaque-BQOlZ2oD.mjs → opaque-BpqxV8oB.mjs} +8 -8
- package/dist/{opaque-BQOlZ2oD.mjs.map → opaque-BpqxV8oB.mjs.map} +1 -1
- package/dist/opaque.mjs +1 -1
- package/dist/prisma.d.mts +1 -2
- package/dist/prisma.d.mts.map +1 -1
- package/dist/prisma.mjs +1 -2
- package/dist/prisma.mjs.map +1 -1
- package/dist/{reverse-C12D1btB.mjs → reverse-d5uEoIET.mjs} +4 -4
- package/dist/{reverse-C12D1btB.mjs.map → reverse-d5uEoIET.mjs.map} +1 -1
- package/dist/reverse.mjs +1 -1
- package/dist/{signed-CwqKTFaQ.mjs → signed-BnRSC03a.mjs} +13 -13
- package/dist/signed-BnRSC03a.mjs.map +1 -0
- package/dist/signed.d.mts.map +1 -1
- package/dist/signed.mjs +1 -1
- package/dist/{timestamp-BjIMQkJf.mjs → timestamp-BbZL8hwg.mjs} +5 -5
- package/dist/{timestamp-BjIMQkJf.mjs.map → timestamp-BbZL8hwg.mjs.map} +1 -1
- package/dist/{timestamp-bytes-Bbg6Y66Z.mjs → timestamp-bytes-DoFjLjDp.mjs} +3 -2
- package/dist/timestamp-bytes-DoFjLjDp.mjs.map +1 -0
- package/dist/{wrapped-DKOsN_dq.mjs → wrapped-BI9UXnAm.mjs} +21 -16
- 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/drizzle-CHtyDXpv.d.mts +0 -33
- package/dist/drizzle-CHtyDXpv.d.mts.map +0 -1
- package/dist/signed-CwqKTFaQ.mjs.map +0 -1
- package/dist/timestamp-bytes-Bbg6Y66Z.mjs.map +0 -1
- package/dist/wrapped-DKOsN_dq.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -105,7 +105,7 @@ try {
|
|
|
105
105
|
| `empty_keyring` | the wrapping keyring is empty | `createWrappedKeyId({ keys })` | Supply at least one `WrappingKey` |
|
|
106
106
|
| `duplicate_keyring_entry` | two keyring entries share the same raw secret | `createWrappedKeyId({ keys })` | Deduplicate the key list |
|
|
107
107
|
| `invalid_lookup_key` | lookup key is out of range or the wrong JS type | `wrap(lookupKey)` | Check the kind's range and JS type |
|
|
108
|
-
| `verification_failed` | no keyring entry verifies the payload tag | `unwrap(id)`
|
|
108
|
+
| `verification_failed` | no keyring entry verifies the payload tag | `unwrap(id)`, `verify(id)` | Check keyring; tamper or wrong key |
|
|
109
109
|
| `invalid_id` | string is not a valid ID for this brand | `parse()`, ORM adapter read paths | Use `safeParse()` for untrusted input |
|
|
110
110
|
|
|
111
111
|
`invalid_id` carries the originating `ParseError` string on `cause` — check `err.cause` for `"not_string"`, `"invalid_prefix"`, or `"invalid_base32"` when you need to distinguish the failure mode.
|
|
@@ -905,7 +905,7 @@ Brand-agnostic subcommands, no install required. Run `npx @smonn/ids --help` for
|
|
|
905
905
|
|
|
906
906
|
### `inspect` (`i`)
|
|
907
907
|
|
|
908
|
-
Decode an ID and print brand, timestamp, canonical form, and whether the input was already canonical.
|
|
908
|
+
Decode an ID and print brand, timestamp (or lookup key), canonical form, and whether the input was already canonical.
|
|
909
909
|
|
|
910
910
|
```bash
|
|
911
911
|
$ npx @smonn/ids inspect usr_01h7b3k9rqxn1cw3p9r8t2sgkz
|
|
@@ -915,13 +915,34 @@ canonical: usr_01h7b3k9rqxn1cw3p9r8t2sgkz
|
|
|
915
915
|
input: canonical
|
|
916
916
|
```
|
|
917
917
|
|
|
918
|
-
Accepts non-canonical input (uppercase, Crockford aliases).
|
|
918
|
+
Accepts non-canonical input (uppercase, Crockford aliases). Pass the flag that matches the codec variant used at generation — without a flag, the **Timestamp codec** is assumed.
|
|
919
|
+
|
|
920
|
+
| Flag | Codec variant | Env var | Notes |
|
|
921
|
+
| ---------------------- | ----------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
922
|
+
| _(none)_ | Timestamp codec | — | Timestamp readable directly |
|
|
923
|
+
| `--opaque` | Opaque Timestamp codec | `IDS_KEY` | Wrong key yields plausible-but-wrong timestamp, not an error (see [CONTEXT.md](./CONTEXT.md)) |
|
|
924
|
+
| `--reverse` | Reverse Timestamp codec | — | No key required; timestamp decoded from inverted bytes |
|
|
925
|
+
| `--wrapped --kind <k>` | Wrapped key codec | `IDS_WRAPPING_KEY` | `--kind` required: `u32`, `i32`, `u64`, `i64`; prints `lookup-key` |
|
|
926
|
+
| `--signed` | Signed Timestamp codec | `IDS_SIGNING_KEY` (optional) | Without key: prints timestamp only. With key: adds `verification: ok` or `verification: failed` |
|
|
927
|
+
|
|
928
|
+
Key format defaults to `hex` for all keyed modes; override with `--key-format hex|base64url` or the matching `_FORMAT` env var (see [Environment variables](#environment-variables) below).
|
|
919
929
|
|
|
920
930
|
```bash
|
|
931
|
+
# Opaque Timestamp (IDS_KEY required):
|
|
921
932
|
IDS_KEY=<hex-or-base64url-key> npx @smonn/ids inspect inv_… --opaque
|
|
922
|
-
```
|
|
923
933
|
|
|
924
|
-
|
|
934
|
+
# Wrapped key (IDS_WRAPPING_KEY and --kind required):
|
|
935
|
+
IDS_WRAPPING_KEY=<hex-or-base64url-key> npx @smonn/ids inspect item_… --wrapped --kind u64
|
|
936
|
+
|
|
937
|
+
# Reverse Timestamp (no key):
|
|
938
|
+
npx @smonn/ids inspect feed_… --reverse
|
|
939
|
+
|
|
940
|
+
# Signed Timestamp — timestamp only (no key):
|
|
941
|
+
npx @smonn/ids inspect evt_… --signed
|
|
942
|
+
|
|
943
|
+
# Signed Timestamp — with verification:
|
|
944
|
+
IDS_SIGNING_KEY=<hex-or-base64url-key> npx @smonn/ids inspect evt_… --signed
|
|
945
|
+
```
|
|
925
946
|
|
|
926
947
|
### `generate` (`g`)
|
|
927
948
|
|
|
@@ -934,15 +955,29 @@ usr_…
|
|
|
934
955
|
usr_…
|
|
935
956
|
```
|
|
936
957
|
|
|
937
|
-
Flags: `--count` / `-c N` (default 1, max 10000). Uses the Timestamp codec unless
|
|
958
|
+
Flags: `--count` / `-c N` (default 1, max 10000). Uses the Timestamp codec unless a mode flag is set.
|
|
959
|
+
|
|
960
|
+
| Flag | Codec variant | Env var |
|
|
961
|
+
| ----------- | ----------------------- | ----------------- |
|
|
962
|
+
| _(none)_ | Timestamp codec | — |
|
|
963
|
+
| `--opaque` | Opaque Timestamp codec | `IDS_KEY` |
|
|
964
|
+
| `--reverse` | Reverse Timestamp codec | — |
|
|
965
|
+
| `--signed` | Signed Timestamp codec | `IDS_SIGNING_KEY` |
|
|
938
966
|
|
|
939
967
|
```bash
|
|
968
|
+
# Opaque Timestamp:
|
|
940
969
|
IDS_KEY=<hex-or-base64url-key> npx @smonn/ids generate inv --opaque --count 2
|
|
970
|
+
|
|
971
|
+
# Reverse Timestamp (newest-first sort order):
|
|
972
|
+
npx @smonn/ids generate feed --reverse --count 5
|
|
973
|
+
|
|
974
|
+
# Signed Timestamp:
|
|
975
|
+
IDS_SIGNING_KEY=<hex-or-base64url-key> npx @smonn/ids generate evt --signed
|
|
941
976
|
```
|
|
942
977
|
|
|
943
978
|
### `keygen` (`k`)
|
|
944
979
|
|
|
945
|
-
Emit a random
|
|
980
|
+
Emit a random key to stdout — for use with `importOpaqueKey`, `importWrappingKey`, or `importSigningKey` (a secret — do not log or commit). Default: 256-bit hex for the Opaque key domain.
|
|
946
981
|
|
|
947
982
|
```bash
|
|
948
983
|
$ npx @smonn/ids keygen
|
|
@@ -952,15 +987,53 @@ $ npx @smonn/ids keygen --bits 128 --key-format base64url
|
|
|
952
987
|
AbCdEf…
|
|
953
988
|
```
|
|
954
989
|
|
|
955
|
-
|
|
990
|
+
| Flag | Key domain | Intended for | Import function |
|
|
991
|
+
| ----------- | ---------- | ------------------ | ------------------- |
|
|
992
|
+
| _(none)_ | Opaque | `IDS_KEY` | `importOpaqueKey` |
|
|
993
|
+
| `--wrapped` | Wrapping | `IDS_WRAPPING_KEY` | `importWrappingKey` |
|
|
994
|
+
| `--signed` | Signing | `IDS_SIGNING_KEY` | `importSigningKey` |
|
|
995
|
+
|
|
996
|
+
Flags: `--bits 128|192|256` (default 256), `--key-format hex|base64url` (default `hex`). Key-format env vars do not affect `keygen` — only `--key-format` on the command line.
|
|
997
|
+
|
|
998
|
+
```bash
|
|
999
|
+
# Wrapping key:
|
|
1000
|
+
npx @smonn/ids keygen --wrapped
|
|
1001
|
+
|
|
1002
|
+
# Signing key (base64url):
|
|
1003
|
+
npx @smonn/ids keygen --signed --key-format base64url
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
### Environment variables
|
|
1007
|
+
|
|
1008
|
+
All keyed modes read secrets from environment variables — not from argv (argv leaks via `ps` and shell history). Missing or malformed key env vars print a clear stderr message and exit non-zero. Invalid input prints the parse error to stderr and exits non-zero.
|
|
1009
|
+
|
|
1010
|
+
| Env var | Used by | Default format |
|
|
1011
|
+
| ------------------------- | ----------------------------- | -------------- |
|
|
1012
|
+
| `IDS_KEY` | `--opaque` | `hex` |
|
|
1013
|
+
| `IDS_KEY_FORMAT` | `--opaque` (format override) | — |
|
|
1014
|
+
| `IDS_WRAPPING_KEY` | `--wrapped` | `hex` |
|
|
1015
|
+
| `IDS_WRAPPING_KEY_FORMAT` | `--wrapped` (format override) | — |
|
|
1016
|
+
| `IDS_SIGNING_KEY` | `--signed` | `hex` |
|
|
1017
|
+
| `IDS_SIGNING_KEY_FORMAT` | `--signed` (format override) | — |
|
|
1018
|
+
|
|
1019
|
+
Key format defaults to `hex` for all modes; override per-invocation with `--key-format hex|base64url` or set the matching `_FORMAT` env var for a session default. `--key-format` on the command line wins over the env var. Key-format env vars do not affect `keygen` output — only `--key-format` applies there.
|
|
1020
|
+
|
|
1021
|
+
### Signed mode (`--signed`)
|
|
1022
|
+
|
|
1023
|
+
`generate --signed` and `inspect --signed` read the HMAC signing key from `IDS_SIGNING_KEY` — not from argv.
|
|
956
1024
|
|
|
957
|
-
|
|
1025
|
+
`inspect --signed` always emits a full timestamp report on stdout and carries a `verification:` line with a three-value verdict:
|
|
958
1026
|
|
|
959
|
-
|
|
1027
|
+
| Case | stdout | stderr | exit |
|
|
1028
|
+
| ------------- | ------------------------------------ | ---------------------------------------------- | ---- |
|
|
1029
|
+
| Correct key | report + `verification: ok` | — | 0 |
|
|
1030
|
+
| Tag mismatch | report + `verification: failed` | `verification_failed: <message>` | 1 |
|
|
1031
|
+
| Key missing | report + `verification: unavailable` | `missing IDS_SIGNING_KEY environment variable` | 1 |
|
|
1032
|
+
| Key malformed | report + `verification: unavailable` | specific key diagnostic | 1 |
|
|
960
1033
|
|
|
961
|
-
|
|
1034
|
+
The timestamp is always readable (Signed Timestamp IDs carry a plaintext timestamp), so `inspect` without `--signed` also decodes it — but without verification.
|
|
962
1035
|
|
|
963
|
-
|
|
1036
|
+
Key format defaults to `hex`; override with `--key-format` or `IDS_SIGNING_KEY_FORMAT`. `--key-format` wins over the environment variable.
|
|
964
1037
|
|
|
965
1038
|
## Design
|
|
966
1039
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1,10 +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 importSigningKey, n as decodeSigningKey, r as encodeSigningKey, t as createSignedTimestampId } from "./signed-
|
|
7
|
-
import { i as importWrappingKey, n as decodeWrappingKey, r as encodeWrappingKey, t as createWrappedKeyId } from "./wrapped-
|
|
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";
|
|
8
8
|
//#region src/cli/codec-options.ts
|
|
9
9
|
function codecOpts(opts) {
|
|
10
10
|
const o = { allowDuplicateBrand: true };
|
|
@@ -31,7 +31,7 @@ function formatSignedInspectOutput(result) {
|
|
|
31
31
|
const relative = formatRelative(result.timestamp.getTime(), result.nowMs);
|
|
32
32
|
const inputLine = describeInputForm(result.input, result.canonical);
|
|
33
33
|
const lines = [`brand: ${result.brand}`, `timestamp: ${result.timestamp.toISOString()} (${relative})`];
|
|
34
|
-
|
|
34
|
+
lines.push(`verification: ${result.verification}`);
|
|
35
35
|
lines.push(`canonical: ${result.canonical}`, `input: ${inputLine}`, "");
|
|
36
36
|
return lines.join("\n");
|
|
37
37
|
}
|
|
@@ -95,19 +95,12 @@ function splitFlagToken(arg) {
|
|
|
95
95
|
inlineValue: arg.slice(eq + 1)
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
|
-
function splitFlags(args) {
|
|
98
|
+
function splitFlags(args, valueFlags) {
|
|
99
99
|
const flags = /* @__PURE__ */ new Set();
|
|
100
100
|
const values = /* @__PURE__ */ new Map();
|
|
101
101
|
const positionals = [];
|
|
102
102
|
const errors = [];
|
|
103
103
|
const seenFlags = /* @__PURE__ */ new Set();
|
|
104
|
-
const valueFlags = new Set([
|
|
105
|
-
"--count",
|
|
106
|
-
"-c",
|
|
107
|
-
"--bits",
|
|
108
|
-
"--key-format",
|
|
109
|
-
"--kind"
|
|
110
|
-
]);
|
|
111
104
|
const addFlag = (flag) => {
|
|
112
105
|
const canonical = canonicalFlag(flag);
|
|
113
106
|
if (seenFlags.has(canonical)) errors.push(`duplicate flag: ${canonical}`);
|
|
@@ -117,11 +110,6 @@ function splitFlags(args) {
|
|
|
117
110
|
for (let i = 0; i < args.length; i++) {
|
|
118
111
|
const raw = args[i];
|
|
119
112
|
const { flag, inlineValue } = splitFlagToken(raw);
|
|
120
|
-
if (flag === "--opaque" || flag === "--wrapped" || flag === "--reverse" || flag === "--signed") {
|
|
121
|
-
addFlag(flag);
|
|
122
|
-
if (inlineValue !== void 0) errors.push(`flag does not take a value: ${flag}`);
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
113
|
if (valueFlags.has(flag)) {
|
|
126
114
|
if (inlineValue !== void 0) {
|
|
127
115
|
addFlag(flag);
|
|
@@ -141,6 +129,7 @@ function splitFlags(args) {
|
|
|
141
129
|
}
|
|
142
130
|
if (flag.startsWith("-")) {
|
|
143
131
|
addFlag(flag);
|
|
132
|
+
if (inlineValue !== void 0) errors.push(`flag does not take a value: ${flag}`);
|
|
144
133
|
continue;
|
|
145
134
|
}
|
|
146
135
|
positionals.push(raw);
|
|
@@ -156,7 +145,7 @@ function canonicalFlag(flag) {
|
|
|
156
145
|
if (flag === "-c") return "--count";
|
|
157
146
|
return flag;
|
|
158
147
|
}
|
|
159
|
-
const knownFlags = new Set([
|
|
148
|
+
const knownFlags = /* @__PURE__ */ new Set([
|
|
160
149
|
"--opaque",
|
|
161
150
|
"--wrapped",
|
|
162
151
|
"--reverse",
|
|
@@ -240,9 +229,6 @@ const opaqueFacet = {
|
|
|
240
229
|
decode: decodeOpaqueKey,
|
|
241
230
|
import: importOpaqueKey
|
|
242
231
|
};
|
|
243
|
-
function parseKeygenFormat(values) {
|
|
244
|
-
return parseKeyFormatFromFlag(values);
|
|
245
|
-
}
|
|
246
232
|
function parseOpaqueKeyFormat(values, opts) {
|
|
247
233
|
return parseKeyFormat(values, opts, opaqueFacet);
|
|
248
234
|
}
|
|
@@ -266,8 +252,14 @@ async function loadSigningKey(opts, format) {
|
|
|
266
252
|
//#endregion
|
|
267
253
|
//#region src/cli/commands/generate.ts
|
|
268
254
|
function runGenerate(args, opts) {
|
|
269
|
-
const { flags, values, positionals, errors } = splitFlags(args
|
|
270
|
-
|
|
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([
|
|
271
263
|
"--count",
|
|
272
264
|
"-c",
|
|
273
265
|
"--opaque",
|
|
@@ -433,8 +425,14 @@ function usage() {
|
|
|
433
425
|
//#endregion
|
|
434
426
|
//#region src/cli/commands/inspect.ts
|
|
435
427
|
function runInspect(args, opts) {
|
|
436
|
-
const { flags, values, positionals, errors } = splitFlags(args
|
|
437
|
-
|
|
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([
|
|
438
436
|
"--opaque",
|
|
439
437
|
"--wrapped",
|
|
440
438
|
"--reverse",
|
|
@@ -663,18 +661,16 @@ async function runSignedInspect(brand, input, format, opts) {
|
|
|
663
661
|
const canonical = validation.value;
|
|
664
662
|
const timestamp = structCodec.extractTimestamp(canonical);
|
|
665
663
|
const nowMs = (opts.now ?? Date.now)();
|
|
666
|
-
|
|
664
|
+
const keyResult = await loadSigningKey(opts, format);
|
|
665
|
+
if (typeof keyResult === "string") {
|
|
667
666
|
opts.stdout(formatSignedInspectOutput({
|
|
668
667
|
brand,
|
|
669
668
|
timestamp,
|
|
670
669
|
canonical,
|
|
671
670
|
input,
|
|
672
|
-
nowMs
|
|
671
|
+
nowMs,
|
|
672
|
+
verification: "unavailable"
|
|
673
673
|
}));
|
|
674
|
-
return 0;
|
|
675
|
-
}
|
|
676
|
-
const keyResult = await loadSigningKey(opts, format);
|
|
677
|
-
if (typeof keyResult === "string") {
|
|
678
674
|
opts.stderr(keyResult + "\n");
|
|
679
675
|
return 1;
|
|
680
676
|
}
|
|
@@ -698,6 +694,7 @@ async function runSignedInspect(brand, input, format, opts) {
|
|
|
698
694
|
nowMs,
|
|
699
695
|
verification: "failed"
|
|
700
696
|
}));
|
|
697
|
+
opts.stderr("verification_failed: verification failed\n");
|
|
701
698
|
return 1;
|
|
702
699
|
}
|
|
703
700
|
opts.stdout(formatSignedInspectOutput({
|
|
@@ -711,15 +708,123 @@ async function runSignedInspect(brand, input, format, opts) {
|
|
|
711
708
|
return 0;
|
|
712
709
|
}
|
|
713
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
|
+
}
|
|
821
|
+
//#endregion
|
|
714
822
|
//#region src/cli/commands/keygen.ts
|
|
715
823
|
function runKeygen(args, opts) {
|
|
716
|
-
const
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
"--bits",
|
|
721
|
-
"--key-format"
|
|
722
|
-
]));
|
|
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))));
|
|
723
828
|
if (unsupported !== void 0) {
|
|
724
829
|
opts.stderr(unsupported + "\n");
|
|
725
830
|
return Promise.resolve(1);
|
|
@@ -733,10 +838,9 @@ function runKeygen(args, opts) {
|
|
|
733
838
|
opts.stderr(`unexpected argument: ${extra}\n`);
|
|
734
839
|
return Promise.resolve(1);
|
|
735
840
|
}
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
opts.stderr("cannot use --signed and --wrapped together\n");
|
|
841
|
+
const variant = resolveVariant(keygenPolicy, flags);
|
|
842
|
+
if (typeof variant === "string") {
|
|
843
|
+
opts.stderr(variant + "\n");
|
|
740
844
|
return Promise.resolve(1);
|
|
741
845
|
}
|
|
742
846
|
const bits = parseBits(values);
|
|
@@ -744,18 +848,19 @@ function runKeygen(args, opts) {
|
|
|
744
848
|
opts.stderr(bits + "\n");
|
|
745
849
|
return Promise.resolve(1);
|
|
746
850
|
}
|
|
747
|
-
const format =
|
|
851
|
+
const format = parseKeyFormatFromFlag(values);
|
|
748
852
|
if (isKeyFormatError(format)) {
|
|
749
853
|
opts.stderr(format + "\n");
|
|
750
854
|
return Promise.resolve(1);
|
|
751
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
|
+
}
|
|
752
861
|
const bytes = new Uint8Array(bits / 8);
|
|
753
862
|
crypto.getRandomValues(bytes);
|
|
754
|
-
|
|
755
|
-
if (signed) encoded = encodeSigningKey(bytes, format);
|
|
756
|
-
else if (wrapped) encoded = encodeWrappingKey(bytes, format);
|
|
757
|
-
else encoded = encodeOpaqueKey(bytes, format);
|
|
758
|
-
opts.stdout(encoded + "\n");
|
|
863
|
+
opts.stdout(variant.key.encode(bytes, format) + "\n");
|
|
759
864
|
return Promise.resolve(0);
|
|
760
865
|
}
|
|
761
866
|
//#endregion
|