@salamandradevs/core 0.1.4 → 0.2.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/README.md +4 -2
- package/dist/index.js +4 -2
- package/dist/keys/config/index.js +3 -0
- package/dist/keys/config/schema.js +51 -0
- package/dist/keys/config/types.js +2 -0
- package/dist/keys/contracts.js +2 -0
- package/dist/keys/factory.js +61 -0
- package/dist/keys/index.js +10 -0
- package/dist/keys/jose/index.js +3 -0
- package/dist/keys/jose/jose-keyfile.js +97 -0
- package/dist/keys/jose/jose-keys-provider.js +256 -0
- package/dist/keys/jose/schema.js +68 -0
- package/dist/keys/jose/types.js +2 -0
- package/dist/keys/jwks/index.js +2 -0
- package/dist/keys/jwks/jwks-verification-key-provider.js +43 -0
- package/dist/keys/openbao/index.js +3 -0
- package/dist/keys/openbao/openbao-keys-provider.js +461 -0
- package/dist/keys/openbao/schema.js +67 -0
- package/dist/keys/openbao/types.js +2 -0
- package/dist/keys/schema.js +65 -0
- package/dist/keys/types.js +2 -0
- package/dist/keys/validation.js +100 -0
- package/dist/runtime/context.js +42 -0
- package/dist/runtime/index.js +2 -0
- package/dist/storage/records-handler.js +2 -2
- package/dist/tokens/access-token-verifier.js +75 -0
- package/dist/tokens/contracts.js +2 -0
- package/dist/tokens/index.js +5 -0
- package/dist/tokens/schema.js +36 -0
- package/dist/tokens/types.js +2 -0
- package/package.json +2 -1
- package/types/index.d.ts +3 -1
- package/types/index.d.ts.map +1 -1
- package/types/keys/config/index.d.ts +3 -0
- package/types/keys/config/index.d.ts.map +1 -0
- package/types/keys/config/schema.d.ts +66 -0
- package/types/keys/config/schema.d.ts.map +1 -0
- package/types/keys/config/types.d.ts +8 -0
- package/types/keys/config/types.d.ts.map +1 -0
- package/types/keys/contracts.d.ts +52 -0
- package/types/keys/contracts.d.ts.map +1 -0
- package/types/keys/factory.d.ts +22 -0
- package/types/keys/factory.d.ts.map +1 -0
- package/types/keys/index.d.ts +10 -0
- package/types/keys/index.d.ts.map +1 -0
- package/types/keys/jose/index.d.ts +3 -0
- package/types/keys/jose/index.d.ts.map +1 -0
- package/types/keys/jose/jose-keyfile.d.ts +35 -0
- package/types/keys/jose/jose-keyfile.d.ts.map +1 -0
- package/types/keys/jose/jose-keys-provider.d.ts +112 -0
- package/types/keys/jose/jose-keys-provider.d.ts.map +1 -0
- package/types/keys/jose/schema.d.ts +91 -0
- package/types/keys/jose/schema.d.ts.map +1 -0
- package/types/keys/jose/types.d.ts +35 -0
- package/types/keys/jose/types.d.ts.map +1 -0
- package/types/keys/jwks/index.d.ts +2 -0
- package/types/keys/jwks/index.d.ts.map +1 -0
- package/types/keys/jwks/jwks-verification-key-provider.d.ts +27 -0
- package/types/keys/jwks/jwks-verification-key-provider.d.ts.map +1 -0
- package/types/keys/openbao/index.d.ts +3 -0
- package/types/keys/openbao/index.d.ts.map +1 -0
- package/types/keys/openbao/openbao-keys-provider.d.ts +175 -0
- package/types/keys/openbao/openbao-keys-provider.d.ts.map +1 -0
- package/types/keys/openbao/schema.d.ts +68 -0
- package/types/keys/openbao/schema.d.ts.map +1 -0
- package/types/keys/openbao/types.d.ts +52 -0
- package/types/keys/openbao/types.d.ts.map +1 -0
- package/types/keys/schema.d.ts +73 -0
- package/types/keys/schema.d.ts.map +1 -0
- package/types/keys/types.d.ts +53 -0
- package/types/keys/types.d.ts.map +1 -0
- package/types/keys/validation.d.ts +19 -0
- package/types/keys/validation.d.ts.map +1 -0
- package/types/runtime/context.d.ts +67 -0
- package/types/runtime/context.d.ts.map +1 -0
- package/types/runtime/index.d.ts +2 -0
- package/types/runtime/index.d.ts.map +1 -0
- package/types/tokens/access-token-verifier.d.ts +36 -0
- package/types/tokens/access-token-verifier.d.ts.map +1 -0
- package/types/tokens/contracts.d.ts +21 -0
- package/types/tokens/contracts.d.ts.map +1 -0
- package/types/tokens/index.d.ts +5 -0
- package/types/tokens/index.d.ts.map +1 -0
- package/types/tokens/schema.d.ts +34 -0
- package/types/tokens/schema.d.ts.map +1 -0
- package/types/tokens/types.d.ts +20 -0
- package/types/tokens/types.d.ts.map +1 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Public JSON Web Key material schema.
|
|
4
|
+
*
|
|
5
|
+
* - Kty: Key type.
|
|
6
|
+
* - Kid: Public key identifier.
|
|
7
|
+
* - Use: Optional intended key use.
|
|
8
|
+
* - Alg: Optional JOSE algorithm.
|
|
9
|
+
* - Crv: Optional elliptic curve name.
|
|
10
|
+
* - X: Optional elliptic-curve or RSA coordinate.
|
|
11
|
+
* - Y: Optional elliptic-curve coordinate.
|
|
12
|
+
* - N: Optional RSA modulus.
|
|
13
|
+
* - E: Optional RSA exponent.
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* Provider-neutral: this is the public key as served to every key consumer, so it carries
|
|
17
|
+
* no provider-specific fields. Shared across every signing-key provider so each validates
|
|
18
|
+
* the lifecycle document against one definition.
|
|
19
|
+
* @group Keys
|
|
20
|
+
* @category Key Providers
|
|
21
|
+
*/
|
|
22
|
+
export const publicJwkSchema = z
|
|
23
|
+
.object({
|
|
24
|
+
kty: z.string(),
|
|
25
|
+
kid: z.string().min(1),
|
|
26
|
+
use: z.string().optional(),
|
|
27
|
+
alg: z.string().optional(),
|
|
28
|
+
crv: z.string().optional(),
|
|
29
|
+
x: z.string().optional(),
|
|
30
|
+
y: z.string().optional(),
|
|
31
|
+
n: z.string().optional(),
|
|
32
|
+
e: z.string().optional()
|
|
33
|
+
})
|
|
34
|
+
.strict();
|
|
35
|
+
/**
|
|
36
|
+
* Durable signing-key lifecycle document schema.
|
|
37
|
+
*
|
|
38
|
+
* - CurrentKid: Identifier of the sole active key.
|
|
39
|
+
* - Keys: Complete provider-managed key lifecycle.
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* The neutral contract every provider's durable store must satisfy. Behavioural invariants
|
|
43
|
+
* beyond this shape (exactly one active key, status-consistent timestamps) live in
|
|
44
|
+
* {@link validateSigningKeyState}.
|
|
45
|
+
* @group Keys
|
|
46
|
+
* @category Key Providers
|
|
47
|
+
*/
|
|
48
|
+
export const signingKeyStateSchema = z
|
|
49
|
+
.object({
|
|
50
|
+
currentKid: z.string().min(1).nullable(),
|
|
51
|
+
keys: z.array(z
|
|
52
|
+
.object({
|
|
53
|
+
kid: z.string().min(1),
|
|
54
|
+
providerKeyId: z.string().min(1),
|
|
55
|
+
status: z.enum(['pending', 'active', 'retired']),
|
|
56
|
+
publicJwk: publicJwkSchema,
|
|
57
|
+
activateAfter: z.string().pipe(z.iso.datetime()),
|
|
58
|
+
activatedAt: z.string().pipe(z.iso.datetime()).nullable(),
|
|
59
|
+
retiredAt: z.string().pipe(z.iso.datetime()).nullable(),
|
|
60
|
+
verifyUntil: z.string().pipe(z.iso.datetime()).nullable()
|
|
61
|
+
})
|
|
62
|
+
.strict())
|
|
63
|
+
})
|
|
64
|
+
.strict();
|
|
65
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2NoZW1hLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2tleXMvc2NoZW1hLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxDQUFDLEVBQUUsTUFBTSxLQUFLLENBQUM7QUFFeEI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7QUFDSCxNQUFNLENBQUMsTUFBTSxlQUFlLEdBQUcsQ0FBQztLQUM3QixNQUFNLENBQUM7SUFDTixHQUFHLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRTtJQUNmLEdBQUcsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUN0QixHQUFHLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUMxQixHQUFHLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUMxQixHQUFHLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUMxQixDQUFDLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUN4QixDQUFDLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUN4QixDQUFDLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUN4QixDQUFDLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtDQUN6QixDQUFDO0tBQ0QsTUFBTSxFQUFFLENBQUM7QUFFWjs7Ozs7Ozs7Ozs7O0dBWUc7QUFDSCxNQUFNLENBQUMsTUFBTSxxQkFBcUIsR0FBRyxDQUFDO0tBQ25DLE1BQU0sQ0FBQztJQUNOLFVBQVUsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsRUFBRTtJQUN4QyxJQUFJLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FDWCxDQUFDO1NBQ0UsTUFBTSxDQUFDO1FBQ04sR0FBRyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQ3RCLGFBQWEsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUNoQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLFNBQVMsRUFBRSxRQUFRLEVBQUUsU0FBUyxDQUFDLENBQUM7UUFDaEQsU0FBUyxFQUFFLGVBQWU7UUFDMUIsYUFBYSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNoRCxXQUFXLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUMsUUFBUSxFQUFFO1FBQ3pELFNBQVMsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQyxRQUFRLEVBQUU7UUFDdkQsV0FBVyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDLFFBQVEsRUFBRTtLQUMxRCxDQUFDO1NBQ0QsTUFBTSxFQUFFLENBQ1o7Q0FDRixDQUFDO0tBQ0QsTUFBTSxFQUFFLENBQUMifQ==
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { CustomError } from '@salamandradevs/tools/shared';
|
|
2
|
+
import { signingKeyStateSchema } from './schema.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parses the signing-key lifecycle document and enforces every invariant providers rely on.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Shared by every signing-key provider so the lifecycle invariants have one definition:
|
|
8
|
+
* unique identifiers, an ES256 P-256 public JWK per entry whose `kid` matches its entry,
|
|
9
|
+
* status-consistent timestamps, exactly one active key, at most one pending key, and a
|
|
10
|
+
* `currentKid` that names the active key. Nothing downstream re-checks any of this, so a
|
|
11
|
+
* failure here is fatal on purpose.
|
|
12
|
+
* @group Keys
|
|
13
|
+
* @category Key Providers
|
|
14
|
+
* @param value - Raw lifecycle document read from a provider's durable store.
|
|
15
|
+
* @param algorithm - JOSE algorithm every published key must declare.
|
|
16
|
+
* @returns Validated signing-key state.
|
|
17
|
+
* @throws {CustomError} KEYS_PROVIDER_ERROR when any signing-key invariant fails.
|
|
18
|
+
*/
|
|
19
|
+
export function validateSigningKeyState(value, algorithm) {
|
|
20
|
+
const candidate = signingKeyStateSchema.parse(value);
|
|
21
|
+
const keys = candidate.keys;
|
|
22
|
+
const kids = new Set();
|
|
23
|
+
const providerKeyIds = new Set();
|
|
24
|
+
for (const entry of keys) {
|
|
25
|
+
// Duplicates would make `kid` lookup ambiguous and could point two entries at one signing
|
|
26
|
+
// key, so retiring one would destroy the other's material.
|
|
27
|
+
if (kids.has(entry.kid) || providerKeyIds.has(entry.providerKeyId)) {
|
|
28
|
+
throw new CustomError({
|
|
29
|
+
name: 'KEYS_PROVIDER_ERROR',
|
|
30
|
+
message: 'Signing-key lifecycle state contains duplicate key identifiers'
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
kids.add(entry.kid);
|
|
34
|
+
providerKeyIds.add(entry.providerKeyId);
|
|
35
|
+
// The JWK is served verbatim to every token consumer, so it must be exactly an ES256
|
|
36
|
+
// P-256 verification key whose `kid` matches the entry that carries it.
|
|
37
|
+
if (entry.publicJwk.kty !== 'EC' ||
|
|
38
|
+
entry.publicJwk.crv !== 'P-256' ||
|
|
39
|
+
entry.publicJwk.alg !== algorithm ||
|
|
40
|
+
entry.publicJwk.use !== 'sig' ||
|
|
41
|
+
entry.publicJwk.kid !== entry.kid ||
|
|
42
|
+
!entry.publicJwk.x ||
|
|
43
|
+
!entry.publicJwk.y) {
|
|
44
|
+
throw new CustomError({
|
|
45
|
+
name: 'KEYS_PROVIDER_ERROR',
|
|
46
|
+
message: 'Signing-key lifecycle state contains an invalid public JWK'
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
validateEntryTimestamps(entry);
|
|
50
|
+
}
|
|
51
|
+
const active = keys.filter((entry) => entry.status === 'active');
|
|
52
|
+
const pending = keys.filter((entry) => entry.status === 'pending');
|
|
53
|
+
// Exactly one active key, because issuance picks it by status alone; at most one pending,
|
|
54
|
+
// because a second would be promoted into a key the first already replaced.
|
|
55
|
+
if (active.length !== 1 || pending.length > 1) {
|
|
56
|
+
throw new CustomError({
|
|
57
|
+
name: 'KEYS_PROVIDER_ERROR',
|
|
58
|
+
message: 'Signing-key lifecycle state must contain one active and at most one pending key'
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
// `currentKid` is redundant with the active entry, which makes it a useful cross-check that
|
|
62
|
+
// a hand-written or externally provisioned document did not leave the two disagreeing.
|
|
63
|
+
if (candidate.currentKid !== active[0].kid) {
|
|
64
|
+
throw new CustomError({
|
|
65
|
+
name: 'KEYS_PROVIDER_ERROR',
|
|
66
|
+
message: 'Signing-key lifecycle state has an invalid current key'
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return { currentKid: candidate.currentKid, keys };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Enforces the timestamps each signing-key status permits, and their chronological order.
|
|
73
|
+
*
|
|
74
|
+
* @param entry - Signing-key entry to check.
|
|
75
|
+
* @throws {CustomError} KEYS_PROVIDER_ERROR when the entry carries timestamps its status
|
|
76
|
+
* forbids.
|
|
77
|
+
*/
|
|
78
|
+
function validateEntryTimestamps(entry) {
|
|
79
|
+
// Each status implies exactly which timestamps may be set. Checking this here is what lets
|
|
80
|
+
// the rest of the system treat a present `verifyUntil` as proof the entry is retired.
|
|
81
|
+
const pendingIsClean = entry.activatedAt === null && entry.retiredAt === null && entry.verifyUntil === null;
|
|
82
|
+
const activeIsClean = entry.activatedAt !== null && entry.retiredAt === null && entry.verifyUntil === null;
|
|
83
|
+
const retiredIsOrdered = entry.activatedAt !== null &&
|
|
84
|
+
entry.retiredAt !== null &&
|
|
85
|
+
entry.verifyUntil !== null &&
|
|
86
|
+
Date.parse(entry.activatedAt) <= Date.parse(entry.retiredAt) &&
|
|
87
|
+
Date.parse(entry.retiredAt) <= Date.parse(entry.verifyUntil);
|
|
88
|
+
const valid = entry.status === 'pending'
|
|
89
|
+
? pendingIsClean
|
|
90
|
+
: entry.status === 'active'
|
|
91
|
+
? activeIsClean
|
|
92
|
+
: retiredIsOrdered;
|
|
93
|
+
if (!valid) {
|
|
94
|
+
throw new CustomError({
|
|
95
|
+
name: 'KEYS_PROVIDER_ERROR',
|
|
96
|
+
message: `Signing-key lifecycle entry '${entry.kid}' has timestamps its status forbids`
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9rZXlzL3ZhbGlkYXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBRTNELE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUdwRDs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFDSCxNQUFNLFVBQVUsdUJBQXVCLENBQ3JDLEtBQWMsRUFDZCxTQUFrQjtJQUVsQixNQUFNLFNBQVMsR0FBb0IscUJBQXFCLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3RFLE1BQU0sSUFBSSxHQUErQixTQUFTLENBQUMsSUFBSSxDQUFDO0lBQ3hELE1BQU0sSUFBSSxHQUFnQixJQUFJLEdBQUcsRUFBVSxDQUFDO0lBQzVDLE1BQU0sY0FBYyxHQUFnQixJQUFJLEdBQUcsRUFBVSxDQUFDO0lBRXRELEtBQUssTUFBTSxLQUFLLElBQUksSUFBSSxFQUFFLENBQUM7UUFDekIsMEZBQTBGO1FBQzFGLDJEQUEyRDtRQUMzRCxJQUFJLElBQUksQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxJQUFJLGNBQWMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLGFBQWEsQ0FBQyxFQUFFLENBQUM7WUFDbkUsTUFBTSxJQUFJLFdBQVcsQ0FBQztnQkFDcEIsSUFBSSxFQUFFLHFCQUFxQjtnQkFDM0IsT0FBTyxFQUFFLGdFQUFnRTthQUMxRSxDQUFDLENBQUM7UUFDTCxDQUFDO1FBRUQsSUFBSSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDcEIsY0FBYyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsYUFBYSxDQUFDLENBQUM7UUFFeEMscUZBQXFGO1FBQ3JGLHdFQUF3RTtRQUN4RSxJQUNFLEtBQUssQ0FBQyxTQUFTLENBQUMsR0FBRyxLQUFLLElB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
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { CustomError } from '@salamandradevs/tools/shared';
|
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
|
+
// Async local storage backing the ambient execution context for the current async scope.
|
|
4
|
+
const executionContextStorage = new AsyncLocalStorage();
|
|
5
|
+
/**
|
|
6
|
+
* Runs one callback with the supplied execution context bound to the current async scope.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* The context is visible to every asynchronous operation started within `callback` and is
|
|
10
|
+
* torn down when it returns, so nested calls to {@link getExecutionCtx} resolve the same
|
|
11
|
+
* identity without explicit propagation. Transport middleware wraps request handling in
|
|
12
|
+
* this call.
|
|
13
|
+
* @group Utils
|
|
14
|
+
* @category Execution Context
|
|
15
|
+
* @param context - Execution context for the current request or job.
|
|
16
|
+
* @param callback - Work executed while the context is available.
|
|
17
|
+
* @returns The callback return value.
|
|
18
|
+
*/
|
|
19
|
+
export function runWithExecutionCtx(context, callback) {
|
|
20
|
+
return executionContextStorage.run(context, callback);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Reads the current execution context.
|
|
24
|
+
*
|
|
25
|
+
* @group Utils
|
|
26
|
+
* @category Execution Context
|
|
27
|
+
* @returns The active {@link ExecutionContext} for the current async scope.
|
|
28
|
+
* @throws {CustomError} RESOURCE_NOT_FOUND when called outside a {@link runWithExecutionCtx}
|
|
29
|
+
* scope, so no context is bound.
|
|
30
|
+
*/
|
|
31
|
+
export function getExecutionCtx() {
|
|
32
|
+
const context = executionContextStorage.getStore();
|
|
33
|
+
if (!context) {
|
|
34
|
+
throw new CustomError({
|
|
35
|
+
name: 'RESOURCE_NOT_FOUND',
|
|
36
|
+
message: 'Execution context is not available',
|
|
37
|
+
cause: 'Async local storage could not get the execution context'
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return context;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGV4dC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9ydW50aW1lL2NvbnRleHQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQzNELE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBOENyRCx5RkFBeUY7QUFDekYsTUFBTSx1QkFBdUIsR0FDM0IsSUFBSSxpQkFBaUIsRUFBb0IsQ0FBQztBQUU1Qzs7Ozs7Ozs7Ozs7OztHQWFHO0FBQ0gsTUFBTSxVQUFVLG1CQUFtQixDQUFJLE9BQXlCLEVBQUUsUUFBaUI7SUFDakYsT0FBTyx1QkFBdUIsQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLFFBQVEsQ0FBQyxDQUFDO0FBQ3hELENBQUM7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sVUFBVSxlQUFlO0lBQzdCLE1BQU0sT0FBTyxHQUFpQyx1QkFBdUIsQ0FBQyxRQUFRLEVBQUUsQ0FBQztJQUVqRixJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDYixNQUFNLElBQUksV0FBVyxDQUFDO1lBQ3BCLElBQUksRUFBRSxvQkFBb0I7WUFDMUIsT0FBTyxFQUFFLG9DQUFvQztZQUM3QyxLQUFLLEVBQUUseURBQXlEO1NBQ2pFLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRCxPQUFPLE9BQU8sQ0FBQztBQUNqQixDQUFDIn0=
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { getExecutionCtx } from '../
|
|
2
|
+
import { getExecutionCtx } from '../runtime/context.js';
|
|
3
3
|
/**
|
|
4
4
|
* Base record handler that applies tenant scope and delegates persistence to an
|
|
5
5
|
* {@link ICrud} adapter.
|
|
@@ -137,4 +137,4 @@ export class RecordsHandler {
|
|
|
137
137
|
return this.crud.find({ ...criteria, tenantId }, findOptions);
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
140
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVjb3Jkcy1oYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3N0b3JhZ2UvcmVjb3Jkcy1oYW5kbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFFekMsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBTXhEOzs7Ozs7Ozs7Ozs7O0dBYUc7QUFDSCxNQUFNLE9BQWdCLGNBQWM7SUFRYjtJQUNGO0lBUm5COzs7OztPQUtHO0lBQ0gsWUFDcUIsSUFBYyxFQUNoQixXQUE2QjtRQUQzQixTQUFJLEdBQUosSUFBSSxDQUFVO1FBQ2hCLGdCQUFXLEdBQVgsV0FBVyxDQUFrQjtJQUM3QyxDQUFDO0lBRUo7Ozs7Ozs7Ozs7T0FVRztJQUNILE1BQU0sQ0FBQyxNQUE0QjtRQUNqQyxNQUFNLEVBQUUsYUFBYSxFQUFFLEdBQUcsZUFBZSxFQUFFLENBQUM7UUFDNUMsTUFBTSxHQUFHLEdBQVMsSUFBSSxJQUFJLEVBQUUsQ0FBQztRQUU3QixPQUFPLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDO1lBQ3RCLEdBQUcsTUFBTTtZQUNULFdBQVcsRUFBRSxJQUFJLENBQUMsV0FBVztZQUM3QixRQUFRLEVBQUUsYUFBYSxDQUFDLFFBQVE7WUFDaEMsUUFBUSxFQUFFLFVBQVUsRUFBRTtZQUN0QixTQUFTLEVBQUUsR0FBRztZQUNkLFNBQVMsRUFBRSxhQUFhLENBQUMsT0FBTztZQUNoQyxTQUFTLEVBQUUsR0FBRztZQUNkLFNBQVMsRUFBRSxhQUFhLENBQUMsT0FBTztTQUM1QixDQUFDLENBQUM7SUFDVixDQUFDO0lBRUQ7Ozs7OztPQU1HO0lBQ0gsSUFBSSxDQUFDLFFBQWdCO1FBQ25CLE1BQU0sRUFBRSxRQUFRLEVBQUUsR0FBRyxlQUFlLEVBQUUsQ0FBQyxhQUFhLENBQUM7UUFFckQsTUFBTSxRQUFRLEdBQWU7WUFDM0IsUUFBUTtZQUNSLFFBQVE7U0FDSyxDQUFDO1FBRWhCLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDbEMsQ0FBQztJQUVEOzs7Ozs7Ozs7OztPQVdHO0lBQ0gsTUFBTSxDQUFDLFFBQWdCLEVBQUUsT0FBeUI7UUFDaEQsTUFBTSxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsR0FBRyxlQUFlLEVBQUUsQ0FBQyxhQUFhLENBQUM7UUFFOUQsTUFBTSxRQUFRLEdBQWU7WUFDM0IsUUFBUTtZQUNSLFFBQVE7U0FDSyxDQUFDO1FBRWhCLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxFQUFFO1lBQ2hDLEdBQUcsT0FBTztZQUNWLFNBQVMsRUFBRSxJQUFJLElBQUksRUFBRTtZQUNyQixTQUFTLEVBQUUsT0FBTztTQUNuQixDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7T0FVRztJQUNILE1BQU0sQ0FBQyxRQUFnQjtRQUNyQixNQUFNLEVBQUUsUUFBUSxFQUFFLEdBQUcsZUFBZSxFQUFFLENBQUMsYUFBYSxDQUFDO1FBRXJELE1BQU0sUUFBUSxHQUFlO1lBQzNCLFFBQVE7WUFDUixRQUFRO1NBQ0ssQ0FBQztRQUVoQixPQUFPLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ3BDLENBQUM7SUFFRDs7Ozs7Ozs7Ozs7O09BWUc7SUFDSCxJQUFJLENBQUMsV0FBdUIsRUFBRSxFQUFFLE9BQTBCO1FBQ3hELE1BQU0sRUFBRSxRQUFRLEVBQUUsR0FBRyxlQUFlLEVBQUUsQ0FBQyxhQUFhLENBQUM7UUFFckQsTUFBTSxTQUFTLEdBQXlCO1lBQ3RDLEVBQUUsS0FBSyxFQUFFLFdBQVcsRUFBRSxTQUFTLEVBQUUsTUFBTSxFQUFFO1lBQ3pDLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUUsTUFBTSxFQUFFO1NBQ3pDLENBQUM7UUFFRixNQUFNLFdBQVcsR0FBbUI7WUFDbEMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxNQUEyQjtZQUMzQyxLQUFLLEVBQUUsT0FBTyxDQUFDLEtBQUs7WUFDcEIsT0FBTyxFQUFFLFNBQVM7U0FDbkIsQ0FBQztRQUVGLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxHQUFHLFFBQVEsRUFBRSxRQUFRLEVBQUUsRUFBRSxXQUFXLENBQUMsQ0FBQztJQUNoRSxDQUFDO0NBQ0YifQ==
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { decodeProtectedHeader, jwtVerify } from 'jose';
|
|
2
|
+
import { CustomError } from '@salamandradevs/tools/shared';
|
|
3
|
+
import { accessTokenClaimsSchema } from './schema.js';
|
|
4
|
+
/**
|
|
5
|
+
* Verifies platform access tokens and reconstructs their session grant.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Checks the protected header, resolves the public key by `kid` through the injected
|
|
9
|
+
* {@link IVerificationKeyProvider}, verifies signature, issuer, audience, and expiry with
|
|
10
|
+
* `jose`, then parses the claims with {@link accessTokenClaimsSchema}.
|
|
11
|
+
* @group Tokens
|
|
12
|
+
* @category Access Tokens
|
|
13
|
+
*/
|
|
14
|
+
export class AccessTokenVerifier {
|
|
15
|
+
config;
|
|
16
|
+
keys;
|
|
17
|
+
/**
|
|
18
|
+
* @param config - Issuer, audience, and clock tolerance every token must satisfy.
|
|
19
|
+
* @param keys - Source of public verification keys by `kid`.
|
|
20
|
+
*/
|
|
21
|
+
constructor(config, keys) {
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.keys = keys;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Verifies one bearer access token.
|
|
27
|
+
*
|
|
28
|
+
* @param credential - Bearer credential selected by the transport layer.
|
|
29
|
+
* @returns Trusted session grant read from the token claims.
|
|
30
|
+
* @throws {CustomError} AUTHENTICATION_FAILED when the token is malformed, signed by an
|
|
31
|
+
* unknown key, expired, or carries incomplete claims.
|
|
32
|
+
* @throws {CustomError} EXTERNAL_API_ERROR or CONFIG_ERROR when the key provider fails;
|
|
33
|
+
* propagated unchanged.
|
|
34
|
+
*/
|
|
35
|
+
async verify(credential) {
|
|
36
|
+
try {
|
|
37
|
+
const header = decodeProtectedHeader(credential.token);
|
|
38
|
+
if (header.kid === undefined || header.alg !== 'ES256' || header.typ !== 'JWT') {
|
|
39
|
+
throw new Error('JWT protected header is invalid');
|
|
40
|
+
}
|
|
41
|
+
const key = await this.keys.getPublicKey(header.kid);
|
|
42
|
+
if (!key) {
|
|
43
|
+
throw new Error('JWT signing key is unknown');
|
|
44
|
+
}
|
|
45
|
+
const { payload } = await jwtVerify(credential.token, key, {
|
|
46
|
+
algorithms: ['ES256'],
|
|
47
|
+
issuer: this.config.issuer,
|
|
48
|
+
audience: this.config.audience,
|
|
49
|
+
clockTolerance: this.config.clockToleranceSecs
|
|
50
|
+
});
|
|
51
|
+
const claims = accessTokenClaimsSchema.parse(payload);
|
|
52
|
+
return {
|
|
53
|
+
sessionId: claims.sid,
|
|
54
|
+
subject: claims.sub,
|
|
55
|
+
actorId: claims.actorId,
|
|
56
|
+
tenantId: claims.tenantId,
|
|
57
|
+
roles: claims.roles
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
// An infrastructure fault keeps its classification, so a key-source outage is
|
|
62
|
+
// never reported as an invalid credential.
|
|
63
|
+
if (error instanceof CustomError &&
|
|
64
|
+
(error.name === 'EXTERNAL_API_ERROR' || error.name === 'CONFIG_ERROR')) {
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
throw new CustomError({
|
|
68
|
+
name: 'AUTHENTICATION_FAILED',
|
|
69
|
+
message: 'Credentials verification failed',
|
|
70
|
+
cause: error
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWNjZXNzLXRva2VuLXZlcmlmaWVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3Rva2Vucy9hY2Nlc3MtdG9rZW4tdmVyaWZpZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLHFCQUFxQixFQUFFLFNBQVMsRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUN4RCxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFHM0QsT0FBTyxFQUFFLHVCQUF1QixFQUFFLE1BQU0sYUFBYSxDQUFDO0FBUXREOzs7Ozs7Ozs7R0FTRztBQUNILE1BQU0sT0FBTyxtQkFBbUI7SUFNWDtJQUNBO0lBTm5COzs7T0FHRztJQUNILFlBQ21CLE1BQStCLEVBQy9CLElBQThCO1FBRDlCLFdBQU0sR0FBTixNQUFNLENBQXlCO1FBQy9CLFNBQUksR0FBSixJQUFJLENBQTBCO0lBQzlDLENBQUM7SUFFSjs7Ozs7Ozs7O09BU0c7SUFDSCxLQUFLLENBQUMsTUFBTSxDQUFDLFVBQXVCO1FBQ2xDLElBQUksQ0FBQztZQUNILE1BQU0sTUFBTSxHQUE4QixxQkFBcUIsQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLENBQUM7WUFFbEYsSUFBSSxNQUFNLENBQUMsR0FBRyxLQUFLLFNBQVMsSUFBSSxNQUFNLENBQUMsR0FBRyxLQUFLLE9BQU8sSUFBSSxNQUFNLENBQUMsR0FBRyxLQUFLLEtBQUssRUFBRSxDQUFDO2dCQUMvRSxNQUFNLElBQUksS0FBSyxDQUFDLGlDQUFpQyxDQUFDLENBQUM7WUFDckQsQ0FBQztZQUVELE1BQU0sR0FBRyxHQUFxQixNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUV2RSxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7Z0JBQ1QsTUFBTSxJQUFJLEtBQUssQ0FBQyw0QkFBNEIsQ0FBQyxDQUFDO1lBQ2hELENBQUM7WUFFRCxNQUFNLEVBQUUsT0FBTyxFQUFFLEdBQUcsTUFBTSxTQUFTLENBQUMsVUFBVSxDQUFDLEtBQUssRUFBRSxHQUFHLEVBQUU7Z0JBQ3pELFVBQVUsRUFBRSxDQUFDLE9BQU8sQ0FBQztnQkFDckIsTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTTtnQkFDMUIsUUFBUSxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUTtnQkFDOUIsY0FBYyxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsa0JBQWtCO2FBQy9DLENBQUMsQ0FBQztZQUNILE1BQU0sTUFBTSxHQUFzQix1QkFBdUIsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7WUFFekUsT0FBTztnQkFDTCxTQUFTLEVBQUUsTUFBTSxDQUFDLEdBQUc7Z0JBQ3JCLE9BQU8sRUFBRSxNQUFNLENBQUMsR0FBRztnQkFDbkIsT0FBTyxFQUFFLE1BQU0sQ0FBQyxPQUFPO2dCQUN2QixRQUFRLEVBQUUsTUFBTSxDQUFDLFFBQVE7Z0JBQ3pCLEtBQUssRUFBRSxNQUFNLENBQUMsS0FBSzthQUNwQixDQUFDO1FBQ0osQ0FBQztRQUFDLE9BQU8sS0FBYyxFQUFFLENBQUM7WUFDeEIsOEVBQThFO1lBQzlFLDJDQUEyQztZQUMzQyxJQUNFLEtBQUssWUFBWSxXQUFXO2dCQUM1QixDQUFDLEtBQUssQ0FBQyxJQUFJLEtBQUssb0JBQW9CLElBQUksS0FBSyxDQUFDLElBQUksS0FBSyxjQUFjLENBQUMsRUFDdEUsQ0FBQztnQkFDRCxNQUFNLEtBQUssQ0FBQztZQUNkLENBQUM7WUFFRCxNQUFNLElBQUksV0FBVyxDQUFDO2dCQUNwQixJQUFJLEVBQUUsdUJBQXVCO2dCQUM3QixPQUFPLEVBQUUsaUNBQWlDO2dCQUMxQyxLQUFLLEVBQUUsS0FBSzthQUNiLENBQUMsQ0FBQztRQUNMLENBQUM7SUFDSCxDQUFDO0NBQ0YifQ==
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export * from './access-token-verifier.js';
|
|
2
|
+
export * from './contracts.js';
|
|
3
|
+
export * from './schema.js';
|
|
4
|
+
export * from './types.js';
|
|
5
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdG9rZW5zL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsNEJBQTRCLENBQUM7QUFDM0MsY0FBYyxnQkFBZ0IsQ0FBQztBQUMvQixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLFlBQVksQ0FBQyJ9
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Access-token verification policy every verifier shares with the issuer.
|
|
4
|
+
*
|
|
5
|
+
* - Issuer: Required `iss`, the issuing service's base URL.
|
|
6
|
+
* - Audience: Required `aud`.
|
|
7
|
+
* - ClockToleranceSecs: Allowed clock skew when checking `exp` and `iat`.
|
|
8
|
+
*/
|
|
9
|
+
export const accessTokenVerifyConfigSchema = z
|
|
10
|
+
.object({
|
|
11
|
+
issuer: z.url(),
|
|
12
|
+
audience: z.string().min(1),
|
|
13
|
+
clockToleranceSecs: z.number().int().nonnegative()
|
|
14
|
+
})
|
|
15
|
+
.strict();
|
|
16
|
+
/**
|
|
17
|
+
* Platform claims carried by every access token.
|
|
18
|
+
*
|
|
19
|
+
* - Sid: Server-side session identifier.
|
|
20
|
+
* - Sub: Authenticated subject.
|
|
21
|
+
* - ActorId: Local actor represented by the session.
|
|
22
|
+
* - TenantId: Tenant the session belongs to.
|
|
23
|
+
* - Roles: Roles granted to the session.
|
|
24
|
+
*
|
|
25
|
+
* @remarks
|
|
26
|
+
* Not strict: `jwtVerify` already checks the registered claims (`iss`, `aud`, `iat`,
|
|
27
|
+
* `exp`), and parsing strips them.
|
|
28
|
+
*/
|
|
29
|
+
export const accessTokenClaimsSchema = z.object({
|
|
30
|
+
sid: z.string(),
|
|
31
|
+
sub: z.string(),
|
|
32
|
+
actorId: z.string(),
|
|
33
|
+
tenantId: z.string(),
|
|
34
|
+
roles: z.array(z.string().min(1))
|
|
35
|
+
});
|
|
36
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2NoZW1hLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3Rva2Vucy9zY2hlbWEudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLENBQUMsRUFBRSxNQUFNLEtBQUssQ0FBQztBQUV4Qjs7Ozs7O0dBTUc7QUFDSCxNQUFNLENBQUMsTUFBTSw2QkFBNkIsR0FBRyxDQUFDO0tBQzNDLE1BQU0sQ0FBQztJQUNOLE1BQU0sRUFBRSxDQUFDLENBQUMsR0FBRyxFQUFFO0lBQ2YsUUFBUSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQzNCLGtCQUFrQixFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxXQUFXLEVBQUU7Q0FDbkQsQ0FBQztLQUNELE1BQU0sRUFBRSxDQUFDO0FBRVo7Ozs7Ozs7Ozs7OztHQVlHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sdUJBQXVCLEdBQUcsQ0FBQyxDQUFDLE1BQU0sQ0FBQztJQUM5QyxHQUFHLEVBQUUsQ0FBQyxDQUFDLE1BQU0sRUFBRTtJQUNmLEdBQUcsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFO0lBQ2YsT0FBTyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDbkIsUUFBUSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUU7SUFDcEIsS0FBSyxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztDQUNsQyxDQUFDLENBQUMifQ==
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salamandradevs/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Shared record layer and database contracts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@12.16.1",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
],
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@salamandradevs/tools": "^0.1.0",
|
|
50
|
+
"jose": "^6.2.12",
|
|
50
51
|
"mongodb": "^7.6.0",
|
|
51
52
|
"mysql2": "^3.23.4",
|
|
52
53
|
"zod": "^4.5.4"
|
package/types/index.d.ts
CHANGED
package/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/keys/config/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Supported signing-key provider identifiers; grows as new providers are added.
|
|
4
|
+
*
|
|
5
|
+
* @group Keys
|
|
6
|
+
* @category Key Providers
|
|
7
|
+
*/
|
|
8
|
+
export declare const signingKeyProviderSchema: z.ZodEnum<{
|
|
9
|
+
jose: "jose";
|
|
10
|
+
openbao: "openbao";
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Signing-key provider selector resolved from the environment.
|
|
14
|
+
*
|
|
15
|
+
* - Provider: Active signing-key provider; read from `SIGNING_KEY_PROVIDER`.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* `provider` is marked `.meta({ env })`, so `loadEnvVariables` resolves it from
|
|
19
|
+
* `SIGNING_KEY_PROVIDER`; the active provider is chosen at runtime, never baked into the
|
|
20
|
+
* configuration file.
|
|
21
|
+
* @group Keys
|
|
22
|
+
* @category Key Providers
|
|
23
|
+
*/
|
|
24
|
+
export declare const signingKeyProviderSelectorSchema: z.ZodObject<{
|
|
25
|
+
provider: z.ZodEnum<{
|
|
26
|
+
jose: "jose";
|
|
27
|
+
openbao: "openbao";
|
|
28
|
+
}>;
|
|
29
|
+
}, z.core.$strict>;
|
|
30
|
+
/**
|
|
31
|
+
* Provider-neutral signing lifecycle schema.
|
|
32
|
+
*
|
|
33
|
+
* - Algorithm: JOSE algorithm supported by this key set.
|
|
34
|
+
* - Jose: Jose provider configuration, present when the deployment may select `jose`.
|
|
35
|
+
* - Openbao: OpenBao provider configuration, present when the deployment may select
|
|
36
|
+
* `openbao`.
|
|
37
|
+
*
|
|
38
|
+
* @remarks
|
|
39
|
+
* Each provider block is optional so a deployment configures only the providers it uses;
|
|
40
|
+
* the active one is chosen at runtime by `SIGNING_KEY_PROVIDER` and must have its block
|
|
41
|
+
* present. Composed into each service's own `serviceConfigSchema` and validated there; core
|
|
42
|
+
* never reads a configuration file.
|
|
43
|
+
* @group Keys
|
|
44
|
+
* @category Key Providers
|
|
45
|
+
*/
|
|
46
|
+
export declare const signingKeysConfigSchema: z.ZodObject<{
|
|
47
|
+
algorithm: z.ZodLiteral<"ES256">;
|
|
48
|
+
jose: z.ZodOptional<z.ZodObject<{
|
|
49
|
+
keyNamePrefix: z.ZodString;
|
|
50
|
+
storage: z.ZodObject<{
|
|
51
|
+
filePath: z.ZodString;
|
|
52
|
+
passphrase: z.ZodString;
|
|
53
|
+
}, z.core.$strict>;
|
|
54
|
+
}, z.core.$strict>>;
|
|
55
|
+
openbao: z.ZodOptional<z.ZodObject<{
|
|
56
|
+
transitMount: z.ZodString;
|
|
57
|
+
kvMount: z.ZodString;
|
|
58
|
+
statePath: z.ZodString;
|
|
59
|
+
keyNamePrefix: z.ZodString;
|
|
60
|
+
requestTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
61
|
+
refreshCooldownSecs: z.ZodNumber;
|
|
62
|
+
baoAddr: z.ZodURL;
|
|
63
|
+
baoToken: z.ZodString;
|
|
64
|
+
}, z.core.$strict>>;
|
|
65
|
+
}, z.core.$strict>;
|
|
66
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/keys/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB;;;EAA8B,CAAC;AAEpE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gCAAgC;;;;;kBAIlC,CAAC;AAEZ;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;kBAMzB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { signingKeyProviderSchema } from './schema.js';
|
|
3
|
+
import type { signingKeysConfigSchema } from './schema.js';
|
|
4
|
+
/** Durable signing lifecycle and per-provider configuration. */
|
|
5
|
+
export type SigningKeysConfig = z.infer<typeof signingKeysConfigSchema>;
|
|
6
|
+
/** Signing-key provider selected at runtime by `SIGNING_KEY_PROVIDER`. */
|
|
7
|
+
export type SigningKeyProvider = z.infer<typeof signingKeyProviderSchema>;
|
|
8
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/keys/config/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,gEAAgE;AAChE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,0EAA0E;AAC1E,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CryptoKey } from 'jose';
|
|
2
|
+
import type { PublicJwk, SigningKeyHandle } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Owner of a key set that signs with it and lists its public keys.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* {@link JoseKeysProvider} and {@link OpenBaoKeysProvider} are the shipped implementations,
|
|
8
|
+
* each over the key set it owns. A consumer that only issues signatures depends on this
|
|
9
|
+
* role, never on {@link IVerificationKeyProvider} or {@link IKeysProvider}.
|
|
10
|
+
* @group Keys
|
|
11
|
+
* @category Key Providers
|
|
12
|
+
*/
|
|
13
|
+
export interface ISigningKeyProvider {
|
|
14
|
+
/** Loads durable state and ensures that an active key exists. */
|
|
15
|
+
initialize(): Promise<void>;
|
|
16
|
+
/** Returns a signer bound to the active key. */
|
|
17
|
+
getCurrentSigner(): Promise<SigningKeyHandle>;
|
|
18
|
+
/** Lists public keys eligible for JWKS publication. */
|
|
19
|
+
listPublicKeys(): Promise<readonly PublicJwk[]>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Source of public keys that verify signatures, looked up by key identifier.
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* {@link JoseKeysProvider} and {@link OpenBaoKeysProvider} implement it over the key set they
|
|
26
|
+
* own, next to {@link ISigningKeyProvider}; {@link JwksVerificationKeyProvider} implements it
|
|
27
|
+
* over a remote JWKS.
|
|
28
|
+
* @group Keys
|
|
29
|
+
* @category Key Providers
|
|
30
|
+
*/
|
|
31
|
+
export interface IVerificationKeyProvider {
|
|
32
|
+
/**
|
|
33
|
+
* Resolves an eligible public verification key by exact key identifier.
|
|
34
|
+
*
|
|
35
|
+
* @param kid - Key identifier carried with the signature.
|
|
36
|
+
* @returns The public key, or null when no eligible key has that identifier.
|
|
37
|
+
*/
|
|
38
|
+
getPublicKey(kid: string): Promise<CryptoKey | null>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Owner of a key set, which signs with it and verifies against it.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* Adds no members: it names the class the composition root builds once, which then hands
|
|
45
|
+
* each consumer only {@link ISigningKeyProvider} or {@link IVerificationKeyProvider}.
|
|
46
|
+
* {@link JoseKeysProvider} and {@link OpenBaoKeysProvider} are the shipped implementations.
|
|
47
|
+
* @group Keys
|
|
48
|
+
* @category Key Providers
|
|
49
|
+
*/
|
|
50
|
+
export interface IKeysProvider extends ISigningKeyProvider, IVerificationKeyProvider {
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=contracts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../src/keys/contracts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEtC,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9D;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC,iEAAiE;IACjE,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5B,gDAAgD;IAChD,gBAAgB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAE9C,uDAAuD;IACvD,cAAc,IAAI,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CAAC;CACjD;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;CACtD;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAc,SAAQ,mBAAmB,EAAE,wBAAwB;CAAG"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { IKeysProvider } from './contracts.js';
|
|
2
|
+
import type { SigningKeysConfig } from './config/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Selects and constructs the keys provider named by `SIGNING_KEY_PROVIDER`.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Construction only: each provider resolves its own environment as it is constructed but
|
|
8
|
+
* performs no input or output, so all network and file work is deferred to `initialize()`.
|
|
9
|
+
* The `switch` is the single extension point; a new provider is a new enum member, a new
|
|
10
|
+
* `case`, and its own configuration block, with no change to the bootstrap step or the
|
|
11
|
+
* services that consume the provider. Returns the owner of the key set as
|
|
12
|
+
* {@link IKeysProvider}; the composition root hands each consumer only the role it uses.
|
|
13
|
+
* @group Keys
|
|
14
|
+
* @category Key Providers
|
|
15
|
+
* @param config - Signing lifecycle and per-provider configuration.
|
|
16
|
+
* @returns The keys provider named by `SIGNING_KEY_PROVIDER`, ready for `initialize()`.
|
|
17
|
+
* @throws {CustomError} ENV_VAR_ERROR when `SIGNING_KEY_PROVIDER` is missing or not a
|
|
18
|
+
* recognized provider, or (for OpenBao) when the provider's own variables are missing.
|
|
19
|
+
* @throws {CustomError} CONFIG_ERROR when the selected provider has no configuration block.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createKeysProvider(config: SigningKeysConfig): IKeysProvider;
|
|
22
|
+
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/keys/factory.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,iBAAiB,GAAG,aAAa,CAiD3E"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './config/index.js';
|
|
2
|
+
export * from './contracts.js';
|
|
3
|
+
export * from './factory.js';
|
|
4
|
+
export * from './jose/index.js';
|
|
5
|
+
export * from './jwks/index.js';
|
|
6
|
+
export * from './openbao/index.js';
|
|
7
|
+
export * from './schema.js';
|
|
8
|
+
export * from './types.js';
|
|
9
|
+
export * from './validation.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/keys/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/keys/jose/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,aAAa,CAAC"}
|