@abloatai/transaction 0.41.0 → 0.43.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/dist/keys/index.d.ts +15 -13
- package/dist/keys/index.d.ts.map +1 -1
- package/dist/keys/index.js +37 -38
- package/dist/keys/index.js.map +1 -1
- package/package.json +1 -1
- package/src/keys/index.ts +39 -40
package/dist/keys/index.d.ts
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
* so a browser bundle never pulls in `node:crypto`.
|
|
8
8
|
*
|
|
9
9
|
* A data-plane key looks like
|
|
10
|
-
* `<sk|rk|ek|pk>_<
|
|
11
|
-
* A control-plane management key is `mk_<body><checksum
|
|
12
|
-
*
|
|
10
|
+
* `<sk|rk|ek|pk>_<30 base62 chars><6-char base62 CRC32 checksum>`.
|
|
11
|
+
* A control-plane management key is `mk_<body><checksum>`. A key's project and
|
|
12
|
+
* branch are server-side bindings, not claims encoded in the plaintext. The recognizable prefix lets secret
|
|
13
13
|
* scanners spot a leaked key, and the trailing checksum lets the format reject a
|
|
14
|
-
* mistyped or forged key locally, without a database round-trip. Older
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* mistyped or forged key locally, without a database round-trip. Older
|
|
15
|
+
* `<kind>_<live|test>_…` keys remain valid compatibility inputs and expose their
|
|
16
|
+
* legacy environment hint when parsed.
|
|
17
17
|
*/
|
|
18
18
|
import { z } from 'zod';
|
|
19
19
|
import { type KeyEnvironment } from '../environment.js';
|
|
@@ -26,7 +26,7 @@ export interface ParsedApiKey {
|
|
|
26
26
|
/** The original plaintext. */
|
|
27
27
|
raw: string;
|
|
28
28
|
kind: ApiKeyKind;
|
|
29
|
-
/**
|
|
29
|
+
/** Legacy hint for old `_live_`/`_test_` keys; null for all current keys. */
|
|
30
30
|
env: ApiKeyEnv | null;
|
|
31
31
|
/** The random body + checksum after the recognizable prefix. */
|
|
32
32
|
body: string;
|
|
@@ -46,10 +46,8 @@ export declare function parseApiKey(raw: string): ParsedApiKey | null;
|
|
|
46
46
|
* Read the environment off a STORED display prefix (`keyPrefix`, the first 12
|
|
47
47
|
* chars — `rk_test_abcd`), rather than off a full plaintext key.
|
|
48
48
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* the credential's own mode. Returns null when the prefix is not a recognizable
|
|
52
|
-
* key spelling, so callers can fail closed rather than fall back to a default.
|
|
49
|
+
* Current keys return null because routing comes from their persisted branch.
|
|
50
|
+
* This helper exists only while stored legacy prefixes remain in circulation.
|
|
53
51
|
*/
|
|
54
52
|
export declare function environmentFromStoredKeyPrefix(prefix: string): KeyEnvironment | null;
|
|
55
53
|
/** True when the key uses the new checksummed format (regardless of validity). */
|
|
@@ -57,8 +55,12 @@ export declare function isChecksummedKey(raw: string): boolean;
|
|
|
57
55
|
/** Verify the embedded checksum. Meaningful only for checksummed-format keys. */
|
|
58
56
|
export declare function keyChecksumMatches(raw: string): boolean;
|
|
59
57
|
/**
|
|
60
|
-
* Mint a key: `<prefix>_<
|
|
61
|
-
* once), its SHA-256 hash (persisted), and the 12-char display prefix.
|
|
58
|
+
* Mint a branch-bound key: `<prefix>_<body><checksum>`. Returns the plaintext
|
|
59
|
+
* (shown once), its SHA-256 hash (persisted), and the 12-char display prefix.
|
|
60
|
+
*
|
|
61
|
+
* `env` remains in the signature so older callers can upgrade independently,
|
|
62
|
+
* but it no longer changes the plaintext. The persisted branch binding decides
|
|
63
|
+
* where the key acts.
|
|
62
64
|
*/
|
|
63
65
|
export declare function generateApiKey(env?: ApiKeyEnv | null, kind?: ApiKeyKind): {
|
|
64
66
|
plaintext: string;
|
package/dist/keys/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/keys/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/keys/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAIL,KAAK,cAAc,EAEpB,MAAM,mBAAmB,CAAC;AAoB3B,eAAO,MAAM,aAAa,6EAMhB,CAAC;AACX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAIxD,eAAO,MAAM,YAAY,oCAAmB,CAAC;AAC7C,MAAM,MAAM,SAAS,GAAG,cAAc,CAAC;AAiFvC,gEAAgE;AAChE,MAAM,WAAW,YAAY;IAC3B,8BAA8B;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,UAAU,CAAC;IACjB,6EAA6E;IAC7E,GAAG,EAAE,SAAS,GAAG,IAAI,CAAC;IACtB,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,WAAW,EAAE,OAAO,CAAC;CACtB;AAMD;;;;;GAKG;AACH,eAAO,MAAM,YAAY,8DAgCvB,CAAC;AAIH,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAG5D;AAED;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAIpF;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAMrD;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOvD;AAID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,GAAG,GAAE,SAAS,GAAG,IAAW,EAC5B,IAAI,GAAE,UAAqB,GAC1B;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAMrD;AAED,4DAA4D;AAC5D,wBAAgB,qBAAqB,IAAI;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAEA;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,2FAA2F;AAC3F,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAE9C;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,IAAI;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAG5E"}
|
package/dist/keys/index.js
CHANGED
|
@@ -7,17 +7,17 @@
|
|
|
7
7
|
* so a browser bundle never pulls in `node:crypto`.
|
|
8
8
|
*
|
|
9
9
|
* A data-plane key looks like
|
|
10
|
-
* `<sk|rk|ek|pk>_<
|
|
11
|
-
* A control-plane management key is `mk_<body><checksum
|
|
12
|
-
*
|
|
10
|
+
* `<sk|rk|ek|pk>_<30 base62 chars><6-char base62 CRC32 checksum>`.
|
|
11
|
+
* A control-plane management key is `mk_<body><checksum>`. A key's project and
|
|
12
|
+
* branch are server-side bindings, not claims encoded in the plaintext. The recognizable prefix lets secret
|
|
13
13
|
* scanners spot a leaked key, and the trailing checksum lets the format reject a
|
|
14
|
-
* mistyped or forged key locally, without a database round-trip. Older
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* mistyped or forged key locally, without a database round-trip. Older
|
|
15
|
+
* `<kind>_<live|test>_…` keys remain valid compatibility inputs and expose their
|
|
16
|
+
* legacy environment hint when parsed.
|
|
17
17
|
*/
|
|
18
18
|
import { createHash, randomBytes } from 'node:crypto';
|
|
19
19
|
import { z } from 'zod';
|
|
20
|
-
import { KEY_ENVIRONMENTS, KEY_PREFIX_ENVIRONMENTS, environmentFromKeyPrefix,
|
|
20
|
+
import { KEY_ENVIRONMENTS, KEY_PREFIX_ENVIRONMENTS, environmentFromKeyPrefix, } from '../environment.js';
|
|
21
21
|
// ── Vocabulary ──────────────────────────────────────────────────────────
|
|
22
22
|
// The five credential kinds:
|
|
23
23
|
// management (mk_) — project control-plane authority. It can manage projects
|
|
@@ -42,9 +42,8 @@ export const API_KEY_KINDS = [
|
|
|
42
42
|
'ephemeral',
|
|
43
43
|
'publishable',
|
|
44
44
|
];
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
// no representation here and must not reach `generateApiKey`.
|
|
45
|
+
// Retained as a compatibility vocabulary for old keys and APIs. New keys do not
|
|
46
|
+
// encode an environment: the key row's branch binding is authoritative.
|
|
48
47
|
export const API_KEY_ENVS = KEY_ENVIRONMENTS;
|
|
49
48
|
const PREFIX_BY_KIND = {
|
|
50
49
|
management: 'mk',
|
|
@@ -67,8 +66,10 @@ const KEY_BODY_LEN = 30;
|
|
|
67
66
|
const CHECKSUM_LEN = 6;
|
|
68
67
|
/** A new checksummed body is exactly this long and pure base62. */
|
|
69
68
|
const CHECKSUMMED_BODY_LEN = KEY_BODY_LEN + CHECKSUM_LEN;
|
|
70
|
-
/** `<sk|rk|ek|pk>_<
|
|
71
|
-
const DATA_KEY_RE = /^(sk|rk|ek|pk)_(
|
|
69
|
+
/** Current `<sk|rk|ek|pk>_<body>` spelling. */
|
|
70
|
+
const DATA_KEY_RE = /^(sk|rk|ek|pk)_([0-9A-Za-z\-_]+)$/;
|
|
71
|
+
/** Compatibility spelling used before branches became the routing boundary. */
|
|
72
|
+
const LEGACY_DATA_KEY_RE = /^(sk|rk|ek|pk)_(live|test)_([0-9A-Za-z\-_]+)$/;
|
|
72
73
|
/** `mk_<body>`; management credentials have no data livemode segment. */
|
|
73
74
|
const MANAGEMENT_KEY_RE = /^(mk)_([0-9A-Za-z\-_]+)$/;
|
|
74
75
|
const BASE62_RE = /^[0-9A-Za-z]+$/;
|
|
@@ -125,15 +126,16 @@ function bodyIsChecksummed(body) {
|
|
|
125
126
|
* `checksummed: false` and is left for the server to validate by hash.
|
|
126
127
|
*/
|
|
127
128
|
export const apiKeySchema = z.string().transform((raw, ctx) => {
|
|
128
|
-
const
|
|
129
|
+
const legacyDataMatch = LEGACY_DATA_KEY_RE.exec(raw);
|
|
130
|
+
const dataMatch = legacyDataMatch ? null : DATA_KEY_RE.exec(raw);
|
|
129
131
|
const managementMatch = MANAGEMENT_KEY_RE.exec(raw);
|
|
130
|
-
if (!dataMatch && !managementMatch) {
|
|
132
|
+
if (!legacyDataMatch && !dataMatch && !managementMatch) {
|
|
131
133
|
ctx.addIssue({ code: 'custom', message: 'not a valid Ablo API key format' });
|
|
132
134
|
return z.NEVER;
|
|
133
135
|
}
|
|
134
|
-
const prefix = dataMatch?.[1] ?? managementMatch?.[1];
|
|
135
|
-
const env =
|
|
136
|
-
const body =
|
|
136
|
+
const prefix = legacyDataMatch?.[1] ?? dataMatch?.[1] ?? managementMatch?.[1];
|
|
137
|
+
const env = legacyDataMatch?.[2];
|
|
138
|
+
const body = legacyDataMatch?.[3] ?? dataMatch?.[2] ?? managementMatch?.[2];
|
|
137
139
|
const kind = prefix === undefined ? undefined : KIND_BY_PREFIX[prefix];
|
|
138
140
|
// Unreachable on a KEY_RE match (all three groups are non-optional and the
|
|
139
141
|
// prefix alternation is exactly the KIND_BY_PREFIX key set) — narrows the
|
|
@@ -150,9 +152,7 @@ export const apiKeySchema = z.string().transform((raw, ctx) => {
|
|
|
150
152
|
return {
|
|
151
153
|
raw,
|
|
152
154
|
kind,
|
|
153
|
-
env:
|
|
154
|
-
? null
|
|
155
|
-
: environmentFromKeyPrefix(env),
|
|
155
|
+
env: env === undefined ? null : environmentFromKeyPrefix(env),
|
|
156
156
|
body,
|
|
157
157
|
checksummed,
|
|
158
158
|
};
|
|
@@ -167,10 +167,8 @@ export function parseApiKey(raw) {
|
|
|
167
167
|
* Read the environment off a STORED display prefix (`keyPrefix`, the first 12
|
|
168
168
|
* chars — `rk_test_abcd`), rather than off a full plaintext key.
|
|
169
169
|
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* the credential's own mode. Returns null when the prefix is not a recognizable
|
|
173
|
-
* key spelling, so callers can fail closed rather than fall back to a default.
|
|
170
|
+
* Current keys return null because routing comes from their persisted branch.
|
|
171
|
+
* This helper exists only while stored legacy prefixes remain in circulation.
|
|
174
172
|
*/
|
|
175
173
|
export function environmentFromStoredKeyPrefix(prefix) {
|
|
176
174
|
const spelling = /^(?:sk|rk|ek|pk)_([a-z]+)_/.exec(prefix)?.[1];
|
|
@@ -179,32 +177,33 @@ export function environmentFromStoredKeyPrefix(prefix) {
|
|
|
179
177
|
}
|
|
180
178
|
/** True when the key uses the new checksummed format (regardless of validity). */
|
|
181
179
|
export function isChecksummedKey(raw) {
|
|
182
|
-
const body =
|
|
180
|
+
const body = LEGACY_DATA_KEY_RE.exec(raw)?.[3] ??
|
|
181
|
+
DATA_KEY_RE.exec(raw)?.[2] ??
|
|
182
|
+
MANAGEMENT_KEY_RE.exec(raw)?.[2];
|
|
183
183
|
return body !== undefined && bodyIsChecksummed(body);
|
|
184
184
|
}
|
|
185
185
|
/** Verify the embedded checksum. Meaningful only for checksummed-format keys. */
|
|
186
186
|
export function keyChecksumMatches(raw) {
|
|
187
|
-
const body =
|
|
187
|
+
const body = LEGACY_DATA_KEY_RE.exec(raw)?.[3] ??
|
|
188
|
+
DATA_KEY_RE.exec(raw)?.[2] ??
|
|
189
|
+
MANAGEMENT_KEY_RE.exec(raw)?.[2];
|
|
188
190
|
if (body === undefined || !bodyIsChecksummed(body))
|
|
189
191
|
return false;
|
|
190
192
|
return checksum6(raw.slice(0, -CHECKSUM_LEN)) === body.slice(KEY_BODY_LEN);
|
|
191
193
|
}
|
|
192
194
|
// ── Mint + hash (node:crypto) ───────────────────────────────────────────
|
|
193
195
|
/**
|
|
194
|
-
* Mint a key: `<prefix>_<
|
|
195
|
-
* once), its SHA-256 hash (persisted), and the 12-char display prefix.
|
|
196
|
+
* Mint a branch-bound key: `<prefix>_<body><checksum>`. Returns the plaintext
|
|
197
|
+
* (shown once), its SHA-256 hash (persisted), and the 12-char display prefix.
|
|
198
|
+
*
|
|
199
|
+
* `env` remains in the signature so older callers can upgrade independently,
|
|
200
|
+
* but it no longer changes the plaintext. The persisted branch binding decides
|
|
201
|
+
* where the key acts.
|
|
196
202
|
*/
|
|
197
|
-
export function generateApiKey(env =
|
|
203
|
+
export function generateApiKey(env = null, kind = 'secret') {
|
|
198
204
|
const body = randomBase62(KEY_BODY_LEN);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
if (kind !== 'management' && env === null) {
|
|
203
|
-
throw new Error(`${kind} credentials require a live/test mode`);
|
|
204
|
-
}
|
|
205
|
-
const payload = kind === 'management'
|
|
206
|
-
? `${PREFIX_BY_KIND[kind]}_${body}`
|
|
207
|
-
: `${PREFIX_BY_KIND[kind]}_${environmentToKeyPrefix(env)}_${body}`;
|
|
205
|
+
void env;
|
|
206
|
+
const payload = `${PREFIX_BY_KIND[kind]}_${body}`;
|
|
208
207
|
const plaintext = `${payload}${checksum6(payload)}`;
|
|
209
208
|
return { plaintext, hash: hashApiKey(plaintext), prefix: plaintext.slice(0, 12) };
|
|
210
209
|
}
|
package/dist/keys/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/keys/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,wBAAwB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/keys/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abloatai/transaction",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "The headless Ablo transaction client and canonical contracts for reads, commits, settlement, claims, and durable observation.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
package/src/keys/index.ts
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
* so a browser bundle never pulls in `node:crypto`.
|
|
8
8
|
*
|
|
9
9
|
* A data-plane key looks like
|
|
10
|
-
* `<sk|rk|ek|pk>_<
|
|
11
|
-
* A control-plane management key is `mk_<body><checksum
|
|
12
|
-
*
|
|
10
|
+
* `<sk|rk|ek|pk>_<30 base62 chars><6-char base62 CRC32 checksum>`.
|
|
11
|
+
* A control-plane management key is `mk_<body><checksum>`. A key's project and
|
|
12
|
+
* branch are server-side bindings, not claims encoded in the plaintext. The recognizable prefix lets secret
|
|
13
13
|
* scanners spot a leaked key, and the trailing checksum lets the format reject a
|
|
14
|
-
* mistyped or forged key locally, without a database round-trip. Older
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* mistyped or forged key locally, without a database round-trip. Older
|
|
15
|
+
* `<kind>_<live|test>_…` keys remain valid compatibility inputs and expose their
|
|
16
|
+
* legacy environment hint when parsed.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { createHash, randomBytes } from 'node:crypto';
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
KEY_ENVIRONMENTS,
|
|
23
23
|
KEY_PREFIX_ENVIRONMENTS,
|
|
24
24
|
environmentFromKeyPrefix,
|
|
25
|
-
environmentToKeyPrefix,
|
|
26
25
|
type KeyEnvironment,
|
|
27
26
|
type KeyPrefixEnvironment,
|
|
28
27
|
} from '../environment.js';
|
|
@@ -54,9 +53,8 @@ export const API_KEY_KINDS = [
|
|
|
54
53
|
] as const;
|
|
55
54
|
export type ApiKeyKind = (typeof API_KEY_KINDS)[number];
|
|
56
55
|
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
// no representation here and must not reach `generateApiKey`.
|
|
56
|
+
// Retained as a compatibility vocabulary for old keys and APIs. New keys do not
|
|
57
|
+
// encode an environment: the key row's branch binding is authoritative.
|
|
60
58
|
export const API_KEY_ENVS = KEY_ENVIRONMENTS;
|
|
61
59
|
export type ApiKeyEnv = KeyEnvironment;
|
|
62
60
|
|
|
@@ -83,8 +81,10 @@ const CHECKSUM_LEN = 6;
|
|
|
83
81
|
/** A new checksummed body is exactly this long and pure base62. */
|
|
84
82
|
const CHECKSUMMED_BODY_LEN = KEY_BODY_LEN + CHECKSUM_LEN;
|
|
85
83
|
|
|
86
|
-
/** `<sk|rk|ek|pk>_<
|
|
87
|
-
const DATA_KEY_RE = /^(sk|rk|ek|pk)_(
|
|
84
|
+
/** Current `<sk|rk|ek|pk>_<body>` spelling. */
|
|
85
|
+
const DATA_KEY_RE = /^(sk|rk|ek|pk)_([0-9A-Za-z\-_]+)$/;
|
|
86
|
+
/** Compatibility spelling used before branches became the routing boundary. */
|
|
87
|
+
const LEGACY_DATA_KEY_RE = /^(sk|rk|ek|pk)_(live|test)_([0-9A-Za-z\-_]+)$/;
|
|
88
88
|
/** `mk_<body>`; management credentials have no data livemode segment. */
|
|
89
89
|
const MANAGEMENT_KEY_RE = /^(mk)_([0-9A-Za-z\-_]+)$/;
|
|
90
90
|
const BASE62_RE = /^[0-9A-Za-z]+$/;
|
|
@@ -142,7 +142,7 @@ export interface ParsedApiKey {
|
|
|
142
142
|
/** The original plaintext. */
|
|
143
143
|
raw: string;
|
|
144
144
|
kind: ApiKeyKind;
|
|
145
|
-
/**
|
|
145
|
+
/** Legacy hint for old `_live_`/`_test_` keys; null for all current keys. */
|
|
146
146
|
env: ApiKeyEnv | null;
|
|
147
147
|
/** The random body + checksum after the recognizable prefix. */
|
|
148
148
|
body: string;
|
|
@@ -161,15 +161,16 @@ function bodyIsChecksummed(body: string): boolean {
|
|
|
161
161
|
* `checksummed: false` and is left for the server to validate by hash.
|
|
162
162
|
*/
|
|
163
163
|
export const apiKeySchema = z.string().transform((raw, ctx): ParsedApiKey => {
|
|
164
|
-
const
|
|
164
|
+
const legacyDataMatch = LEGACY_DATA_KEY_RE.exec(raw);
|
|
165
|
+
const dataMatch = legacyDataMatch ? null : DATA_KEY_RE.exec(raw);
|
|
165
166
|
const managementMatch = MANAGEMENT_KEY_RE.exec(raw);
|
|
166
|
-
if (!dataMatch && !managementMatch) {
|
|
167
|
+
if (!legacyDataMatch && !dataMatch && !managementMatch) {
|
|
167
168
|
ctx.addIssue({ code: 'custom', message: 'not a valid Ablo API key format' });
|
|
168
169
|
return z.NEVER;
|
|
169
170
|
}
|
|
170
|
-
const prefix = dataMatch?.[1] ?? managementMatch?.[1];
|
|
171
|
-
const env =
|
|
172
|
-
const body =
|
|
171
|
+
const prefix = legacyDataMatch?.[1] ?? dataMatch?.[1] ?? managementMatch?.[1];
|
|
172
|
+
const env = legacyDataMatch?.[2];
|
|
173
|
+
const body = legacyDataMatch?.[3] ?? dataMatch?.[2] ?? managementMatch?.[2];
|
|
173
174
|
const kind = prefix === undefined ? undefined : KIND_BY_PREFIX[prefix];
|
|
174
175
|
// Unreachable on a KEY_RE match (all three groups are non-optional and the
|
|
175
176
|
// prefix alternation is exactly the KIND_BY_PREFIX key set) — narrows the
|
|
@@ -187,9 +188,7 @@ export const apiKeySchema = z.string().transform((raw, ctx): ParsedApiKey => {
|
|
|
187
188
|
raw,
|
|
188
189
|
kind,
|
|
189
190
|
env:
|
|
190
|
-
|
|
191
|
-
? null
|
|
192
|
-
: environmentFromKeyPrefix(env as KeyPrefixEnvironment),
|
|
191
|
+
env === undefined ? null : environmentFromKeyPrefix(env as KeyPrefixEnvironment),
|
|
193
192
|
body,
|
|
194
193
|
checksummed,
|
|
195
194
|
};
|
|
@@ -207,10 +206,8 @@ export function parseApiKey(raw: string): ParsedApiKey | null {
|
|
|
207
206
|
* Read the environment off a STORED display prefix (`keyPrefix`, the first 12
|
|
208
207
|
* chars — `rk_test_abcd`), rather than off a full plaintext key.
|
|
209
208
|
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* the credential's own mode. Returns null when the prefix is not a recognizable
|
|
213
|
-
* key spelling, so callers can fail closed rather than fall back to a default.
|
|
209
|
+
* Current keys return null because routing comes from their persisted branch.
|
|
210
|
+
* This helper exists only while stored legacy prefixes remain in circulation.
|
|
214
211
|
*/
|
|
215
212
|
export function environmentFromStoredKeyPrefix(prefix: string): KeyEnvironment | null {
|
|
216
213
|
const spelling = /^(?:sk|rk|ek|pk)_([a-z]+)_/.exec(prefix)?.[1];
|
|
@@ -220,13 +217,19 @@ export function environmentFromStoredKeyPrefix(prefix: string): KeyEnvironment |
|
|
|
220
217
|
|
|
221
218
|
/** True when the key uses the new checksummed format (regardless of validity). */
|
|
222
219
|
export function isChecksummedKey(raw: string): boolean {
|
|
223
|
-
const body =
|
|
220
|
+
const body =
|
|
221
|
+
LEGACY_DATA_KEY_RE.exec(raw)?.[3] ??
|
|
222
|
+
DATA_KEY_RE.exec(raw)?.[2] ??
|
|
223
|
+
MANAGEMENT_KEY_RE.exec(raw)?.[2];
|
|
224
224
|
return body !== undefined && bodyIsChecksummed(body);
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
/** Verify the embedded checksum. Meaningful only for checksummed-format keys. */
|
|
228
228
|
export function keyChecksumMatches(raw: string): boolean {
|
|
229
|
-
const body =
|
|
229
|
+
const body =
|
|
230
|
+
LEGACY_DATA_KEY_RE.exec(raw)?.[3] ??
|
|
231
|
+
DATA_KEY_RE.exec(raw)?.[2] ??
|
|
232
|
+
MANAGEMENT_KEY_RE.exec(raw)?.[2];
|
|
230
233
|
if (body === undefined || !bodyIsChecksummed(body)) return false;
|
|
231
234
|
return checksum6(raw.slice(0, -CHECKSUM_LEN)) === body.slice(KEY_BODY_LEN);
|
|
232
235
|
}
|
|
@@ -234,24 +237,20 @@ export function keyChecksumMatches(raw: string): boolean {
|
|
|
234
237
|
// ── Mint + hash (node:crypto) ───────────────────────────────────────────
|
|
235
238
|
|
|
236
239
|
/**
|
|
237
|
-
* Mint a key: `<prefix>_<
|
|
238
|
-
* once), its SHA-256 hash (persisted), and the 12-char display prefix.
|
|
240
|
+
* Mint a branch-bound key: `<prefix>_<body><checksum>`. Returns the plaintext
|
|
241
|
+
* (shown once), its SHA-256 hash (persisted), and the 12-char display prefix.
|
|
242
|
+
*
|
|
243
|
+
* `env` remains in the signature so older callers can upgrade independently,
|
|
244
|
+
* but it no longer changes the plaintext. The persisted branch binding decides
|
|
245
|
+
* where the key acts.
|
|
239
246
|
*/
|
|
240
247
|
export function generateApiKey(
|
|
241
|
-
env: ApiKeyEnv | null =
|
|
248
|
+
env: ApiKeyEnv | null = null,
|
|
242
249
|
kind: ApiKeyKind = 'secret',
|
|
243
250
|
): { plaintext: string; hash: string; prefix: string } {
|
|
244
251
|
const body = randomBase62(KEY_BODY_LEN);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
if (kind !== 'management' && env === null) {
|
|
249
|
-
throw new Error(`${kind} credentials require a live/test mode`);
|
|
250
|
-
}
|
|
251
|
-
const payload =
|
|
252
|
-
kind === 'management'
|
|
253
|
-
? `${PREFIX_BY_KIND[kind]}_${body}`
|
|
254
|
-
: `${PREFIX_BY_KIND[kind]}_${environmentToKeyPrefix(env as ApiKeyEnv)}_${body}`;
|
|
252
|
+
void env;
|
|
253
|
+
const payload = `${PREFIX_BY_KIND[kind]}_${body}`;
|
|
255
254
|
const plaintext = `${payload}${checksum6(payload)}`;
|
|
256
255
|
return { plaintext, hash: hashApiKey(plaintext), prefix: plaintext.slice(0, 12) };
|
|
257
256
|
}
|