@fluojs/jwt 1.0.0-beta.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.
@@ -0,0 +1,124 @@
1
+ let _initClass;
2
+ function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
3
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
4
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
5
+ function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
6
+ function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
7
+ import { createHmac, createSign } from 'node:crypto';
8
+ import { Inject } from '@fluojs/core';
9
+ import { JwtConfigurationError } from '../errors.js';
10
+ import { normalizeRefreshTokenOptions } from '../refresh/refresh-token.js';
11
+ import { ASYMMETRIC_HASH, HMAC_HASH, JWT_OPTIONS } from './verifier.js';
12
+ function encodeBase64Url(value) {
13
+ return Buffer.from(value).toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
14
+ }
15
+ function resolveSigningKeyEntry(options, algorithm) {
16
+ const keys = options.keys;
17
+ if (!Array.isArray(keys) || keys.length === 0) {
18
+ return undefined;
19
+ }
20
+ if (algorithm in HMAC_HASH) {
21
+ return keys.find(entry => typeof entry.secret === 'string' && entry.secret.length > 0);
22
+ }
23
+ return keys.find(entry => entry.privateKey !== undefined);
24
+ }
25
+
26
+ /**
27
+ * Issues access and refresh tokens with the configured signing keys and algorithms.
28
+ */
29
+ let _DefaultJwtSigner;
30
+ class DefaultJwtSigner {
31
+ static {
32
+ [_DefaultJwtSigner, _initClass] = _applyDecs(this, [Inject(JWT_OPTIONS)], []).c;
33
+ }
34
+ refreshAlgorithms;
35
+ constructor(options) {
36
+ this.options = options;
37
+ this.refreshAlgorithms = this.options.algorithms.filter(algorithm => algorithm in HMAC_HASH);
38
+ }
39
+ async signAccessToken(claims) {
40
+ return this.signToken(claims, this.options, false);
41
+ }
42
+ async signRefreshToken(claims) {
43
+ const refreshOptions = this.resolveRefreshSigningOptions();
44
+ return this.signToken(claims, refreshOptions, true);
45
+ }
46
+ resolveRefreshSigningOptions() {
47
+ const refreshToken = normalizeRefreshTokenOptions(this.options.refreshToken);
48
+ return {
49
+ audience: this.options.audience,
50
+ accessTokenTtlSeconds: refreshToken.expiresInSeconds,
51
+ algorithms: this.refreshAlgorithms,
52
+ issuer: this.options.issuer,
53
+ secret: refreshToken.secret
54
+ };
55
+ }
56
+ async signToken(claims, options, hmacOnly) {
57
+ const algorithm = options.algorithms.find(alg => {
58
+ if (hmacOnly) {
59
+ return alg in HMAC_HASH;
60
+ }
61
+ return alg in HMAC_HASH || alg in ASYMMETRIC_HASH;
62
+ });
63
+ if (!algorithm) {
64
+ if (hmacOnly) {
65
+ throw new JwtConfigurationError('JWT refresh token signer requires at least one HMAC algorithm (HS256/HS384/HS512) in the allowed algorithms list.');
66
+ }
67
+ throw new JwtConfigurationError('JWT signer requires at least one supported algorithm (HS256/HS384/HS512/RS256/RS384/RS512/ES256/ES384/ES512) in the allowed algorithms list.');
68
+ }
69
+ const isAsymmetric = algorithm in ASYMMETRIC_HASH;
70
+ const now = Math.floor(Date.now() / 1000);
71
+ const ttl = options.accessTokenTtlSeconds ?? 3600;
72
+ const payload = {
73
+ ...claims,
74
+ aud: claims.aud ?? options.audience,
75
+ exp: claims.exp ?? now + ttl,
76
+ iat: claims.iat ?? now,
77
+ iss: claims.iss ?? options.issuer
78
+ };
79
+ const activeKey = resolveSigningKeyEntry(options, algorithm);
80
+ const header = {
81
+ alg: algorithm,
82
+ typ: 'JWT',
83
+ ...(activeKey ? {
84
+ kid: activeKey.kid
85
+ } : {})
86
+ };
87
+ const headerSegment = encodeBase64Url(JSON.stringify(header));
88
+ const payloadSegment = encodeBase64Url(JSON.stringify(payload));
89
+ const signingInput = `${headerSegment}.${payloadSegment}`;
90
+ let signatureSegment;
91
+ if (isAsymmetric) {
92
+ const privateKey = activeKey?.privateKey ?? options.privateKey;
93
+ if (!privateKey) {
94
+ throw new JwtConfigurationError('JWT private key is not configured.');
95
+ }
96
+ const hash = ASYMMETRIC_HASH[algorithm];
97
+ if (!hash) {
98
+ throw new JwtConfigurationError(`No hash mapping for asymmetric algorithm "${algorithm}".`);
99
+ }
100
+ const signer = createSign(hash);
101
+ signer.update(signingInput);
102
+ const isEc = algorithm.startsWith('ES');
103
+ signatureSegment = isEc ? signer.sign({
104
+ dsaEncoding: 'ieee-p1363',
105
+ key: privateKey
106
+ }, 'base64url') : signer.sign(privateKey, 'base64url');
107
+ } else {
108
+ const secret = activeKey?.secret ?? options.secret;
109
+ if (!secret) {
110
+ throw new JwtConfigurationError('JWT secret is not configured.');
111
+ }
112
+ const hash = HMAC_HASH[algorithm];
113
+ if (!hash) {
114
+ throw new JwtConfigurationError(`No hash mapping for HMAC algorithm "${algorithm}".`);
115
+ }
116
+ signatureSegment = encodeBase64Url(createHmac(hash, secret).update(signingInput).digest());
117
+ }
118
+ return `${headerSegment}.${payloadSegment}.${signatureSegment}`;
119
+ }
120
+ static {
121
+ _initClass();
122
+ }
123
+ }
124
+ export { _DefaultJwtSigner as DefaultJwtSigner };
@@ -0,0 +1,38 @@
1
+ import type { JwtAlgorithm, JwtPrincipal, JwtVerifierOptions } from '../types.js';
2
+ /**
3
+ * Provides the resolved JWT verifier options through dependency injection.
4
+ */
5
+ export declare const JWT_OPTIONS: unique symbol;
6
+ /**
7
+ * Maps supported HMAC JWT algorithms to their Node.js hash names.
8
+ */
9
+ export declare const HMAC_HASH: Partial<Record<JwtAlgorithm, string>>;
10
+ /**
11
+ * Maps supported asymmetric JWT algorithms to their Node.js hash names.
12
+ */
13
+ export declare const ASYMMETRIC_HASH: Partial<Record<JwtAlgorithm, string>>;
14
+ /**
15
+ * Verifies JWT access and refresh tokens against the configured key sources.
16
+ */
17
+ export declare class DefaultJwtVerifier {
18
+ private readonly options;
19
+ private readonly jwksClient;
20
+ private readonly keyResolutionState;
21
+ private readonly refreshKeyResolutionState;
22
+ private readonly refreshVerificationOptions;
23
+ constructor(options: JwtVerifierOptions);
24
+ verifyAccessToken(token: string): Promise<JwtPrincipal>;
25
+ verifyRefreshToken(token: string): Promise<JwtPrincipal>;
26
+ private createRefreshVerificationOptions;
27
+ private verifyToken;
28
+ private parseTokenSegments;
29
+ private verifyTokenSignature;
30
+ private verifyHmacTokenSignature;
31
+ private verifyAsymmetricTokenSignature;
32
+ private resolveProviderKey;
33
+ private validateTokenClaims;
34
+ private validateMaxAgeClaims;
35
+ private validateIssuerAndAudience;
36
+ private resolveJwksPublicKey;
37
+ }
38
+ //# sourceMappingURL=verifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifier.d.ts","sourceRoot":"","sources":["../../src/signing/verifier.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAA0B,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE1G;;GAEG;AACH,eAAO,MAAM,WAAW,eAAiC,CAAC;AAE1D;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAI3D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAOjE,CAAC;AAuMF;;GAEG;AACH,qBACa,kBAAkB;IAMjB,OAAO,CAAC,QAAQ,CAAC,OAAO;IALpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IACpD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAqB;IACxD,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAqB;IAC/D,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAiC;gBAE/C,OAAO,EAAE,kBAAkB;IAYlD,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAIvD,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAQ9D,OAAO,CAAC,gCAAgC;YAsB1B,WAAW;IA+BzB,OAAO,CAAC,kBAAkB;YAUZ,oBAAoB;YAgBpB,wBAAwB;YAsBxB,8BAA8B;YAsB9B,kBAAkB;IAWhC,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,oBAAoB;IA2B5B,OAAO,CAAC,yBAAyB;YAiBnB,oBAAoB;CAOnC"}
@@ -0,0 +1,319 @@
1
+ let _initClass;
2
+ function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
3
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
4
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
5
+ function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
6
+ function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
7
+ import { createHmac, createVerify, timingSafeEqual } from 'node:crypto';
8
+ import { Inject } from '@fluojs/core';
9
+ import { JwtConfigurationError, JwtExpiredTokenError, JwtInvalidTokenError } from '../errors.js';
10
+ import { JwksClient } from './jwks.js';
11
+ import { normalizeRefreshTokenOptions } from '../refresh/refresh-token.js';
12
+ /**
13
+ * Provides the resolved JWT verifier options through dependency injection.
14
+ */
15
+ export const JWT_OPTIONS = Symbol.for('fluo.jwt.options');
16
+
17
+ /**
18
+ * Maps supported HMAC JWT algorithms to their Node.js hash names.
19
+ */
20
+ export const HMAC_HASH = {
21
+ HS256: 'sha256',
22
+ HS384: 'sha384',
23
+ HS512: 'sha512'
24
+ };
25
+
26
+ /**
27
+ * Maps supported asymmetric JWT algorithms to their Node.js hash names.
28
+ */
29
+ export const ASYMMETRIC_HASH = {
30
+ RS256: 'sha256',
31
+ RS384: 'sha384',
32
+ RS512: 'sha512',
33
+ ES256: 'sha256',
34
+ ES384: 'sha384',
35
+ ES512: 'sha512'
36
+ };
37
+ function isAllowedAlgorithm(alg, allowed) {
38
+ return typeof alg === 'string' && allowed.includes(alg) && (alg in HMAC_HASH || alg in ASYMMETRIC_HASH);
39
+ }
40
+ function isFiniteNumericDate(value) {
41
+ return typeof value === 'number' && Number.isFinite(value);
42
+ }
43
+ function createKeyResolutionState(keys) {
44
+ const state = {
45
+ hmacKeyCount: 0,
46
+ keyByKid: new Map(),
47
+ publicKeyCount: 0
48
+ };
49
+ if (!Array.isArray(keys) || keys.length === 0) {
50
+ return state;
51
+ }
52
+ for (const entry of keys) {
53
+ state.keyByKid.set(entry.kid, entry);
54
+ if (typeof entry.secret === 'string' && entry.secret.length > 0) {
55
+ state.hmacKeyCount += 1;
56
+ state.defaultHmacSecret = state.hmacKeyCount === 1 ? entry.secret : undefined;
57
+ }
58
+ if (entry.publicKey !== undefined) {
59
+ state.publicKeyCount += 1;
60
+ state.defaultPublicKey = state.publicKeyCount === 1 ? entry.publicKey : undefined;
61
+ }
62
+ }
63
+ return state;
64
+ }
65
+ function resolveHmacSecret(options, keyState, kid) {
66
+ if (typeof kid === 'string' && kid.length > 0) {
67
+ const matchingKey = keyState.keyByKid.get(kid);
68
+ if (!matchingKey) {
69
+ throw new JwtInvalidTokenError('JWT key id (kid) is not recognized.');
70
+ }
71
+ if (typeof matchingKey.secret !== 'string' || matchingKey.secret.length === 0) {
72
+ throw new JwtConfigurationError(`JWT key "${kid}" does not provide an HMAC secret.`);
73
+ }
74
+ return matchingKey.secret;
75
+ }
76
+ if (keyState.hmacKeyCount > 1) {
77
+ throw new JwtInvalidTokenError('JWT is missing key id (kid) for multi-key HMAC verification.');
78
+ }
79
+ return keyState.defaultHmacSecret ?? options.secret;
80
+ }
81
+ function resolveStaticPublicKey(options, keyState, kid) {
82
+ if (typeof kid === 'string' && kid.length > 0) {
83
+ // Only consult keyByKid when a multi-key keys[] array was provided.
84
+ // When the verifier is configured with a single options.publicKey, the
85
+ // map is empty and a kid header in the token should be ignored so that
86
+ // verification proceeds with the single configured key.
87
+ if (keyState.keyByKid.size > 0) {
88
+ const matchingKey = keyState.keyByKid.get(kid);
89
+ if (!matchingKey) {
90
+ throw new JwtInvalidTokenError('JWT key id (kid) is not recognized.');
91
+ }
92
+ if (matchingKey.publicKey === undefined) {
93
+ throw new JwtConfigurationError(`JWT key "${kid}" does not provide a public key.`);
94
+ }
95
+ return matchingKey.publicKey;
96
+ }
97
+ }
98
+ if (keyState.publicKeyCount > 1) {
99
+ throw new JwtInvalidTokenError('JWT is missing key id (kid) for multi-key public key verification.');
100
+ }
101
+ return keyState.defaultPublicKey ?? options.publicKey;
102
+ }
103
+ function verifyHmacSignature(algorithm, secret, signingInput, signatureSegment) {
104
+ const hash = HMAC_HASH[algorithm];
105
+ if (!hash) {
106
+ throw new JwtInvalidTokenError();
107
+ }
108
+ const expected = encodeBase64Url(createHmac(hash, secret).update(signingInput).digest());
109
+ const expectedBuf = Buffer.from(expected, 'base64url');
110
+ const actualBuf = Buffer.from(signatureSegment, 'base64url');
111
+ if (expectedBuf.length !== actualBuf.length || !timingSafeEqual(expectedBuf, actualBuf)) {
112
+ throw new JwtInvalidTokenError();
113
+ }
114
+ }
115
+ function verifyAsymmetricSignature(algorithm, publicKey, signingInput, signatureSegment) {
116
+ const hash = ASYMMETRIC_HASH[algorithm];
117
+ if (!hash) {
118
+ throw new JwtInvalidTokenError();
119
+ }
120
+ const verifier = createVerify(hash);
121
+ verifier.update(signingInput);
122
+ const isEc = algorithm.startsWith('ES');
123
+ const valid = verifier.verify(isEc ? {
124
+ dsaEncoding: 'ieee-p1363',
125
+ key: publicKey
126
+ } : publicKey, signatureSegment, 'base64url');
127
+ if (!valid) {
128
+ throw new JwtInvalidTokenError();
129
+ }
130
+ }
131
+ function decodeBase64Url(value) {
132
+ const normalized = value.replace(/-/g, '+').replace(/_/g, '/');
133
+ const padding = normalized.length % 4 === 0 ? '' : '='.repeat(4 - normalized.length % 4);
134
+ return Buffer.from(normalized + padding, 'base64');
135
+ }
136
+ function parseJwtPart(value) {
137
+ try {
138
+ return JSON.parse(decodeBase64Url(value).toString('utf8'));
139
+ } catch {
140
+ throw new JwtInvalidTokenError();
141
+ }
142
+ }
143
+ function encodeBase64Url(value) {
144
+ return value.toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
145
+ }
146
+ function normalizePrincipal(claims) {
147
+ if (typeof claims.sub !== 'string' || claims.sub.length === 0) {
148
+ throw new JwtInvalidTokenError('JWT is missing a valid subject claim.');
149
+ }
150
+ const scopes = Array.isArray(claims.scopes) ? claims.scopes.filter(scope => typeof scope === 'string') : typeof claims.scope === 'string' ? claims.scope.split(' ').filter(Boolean) : undefined;
151
+ const roles = Array.isArray(claims.roles) ? claims.roles.filter(role => typeof role === 'string') : undefined;
152
+ return {
153
+ audience: claims.aud,
154
+ claims: {
155
+ ...claims
156
+ },
157
+ issuer: claims.iss,
158
+ roles,
159
+ scopes,
160
+ subject: claims.sub
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Verifies JWT access and refresh tokens against the configured key sources.
166
+ */
167
+ let _DefaultJwtVerifier;
168
+ class DefaultJwtVerifier {
169
+ static {
170
+ [_DefaultJwtVerifier, _initClass] = _applyDecs(this, [Inject(JWT_OPTIONS)], []).c;
171
+ }
172
+ jwksClient;
173
+ keyResolutionState;
174
+ refreshKeyResolutionState;
175
+ refreshVerificationOptions;
176
+ constructor(options) {
177
+ this.options = options;
178
+ this.jwksClient = options.jwksUri ? new JwksClient(options.jwksUri, options.jwksCacheTtl, options.jwksRequestTimeoutMs) : undefined;
179
+ this.keyResolutionState = createKeyResolutionState(options.keys);
180
+ this.refreshVerificationOptions = options.refreshToken ? this.createRefreshVerificationOptions(normalizeRefreshTokenOptions(options.refreshToken)) : undefined;
181
+ this.refreshKeyResolutionState = createKeyResolutionState(this.refreshVerificationOptions?.keys);
182
+ }
183
+ async verifyAccessToken(token) {
184
+ return this.verifyToken(token, this.options, this.keyResolutionState, this.jwksClient);
185
+ }
186
+ async verifyRefreshToken(token) {
187
+ if (!this.refreshVerificationOptions) {
188
+ throw new JwtConfigurationError('JWT refresh token options are not configured.');
189
+ }
190
+ return this.verifyToken(token, this.refreshVerificationOptions, this.refreshKeyResolutionState, undefined);
191
+ }
192
+ createRefreshVerificationOptions(refreshToken) {
193
+ const algorithms = this.options.algorithms.filter(algorithm => algorithm in HMAC_HASH);
194
+ if (algorithms.length === 0) {
195
+ throw new JwtConfigurationError('JWT refresh token verifier requires at least one HMAC algorithm (HS256/HS384/HS512) in the allowed algorithms list.');
196
+ }
197
+ return {
198
+ algorithms,
199
+ audience: this.options.audience,
200
+ clockSkewSeconds: this.options.clockSkewSeconds,
201
+ issuer: this.options.issuer,
202
+ maxAge: refreshToken.verifyMaxAgeSeconds,
203
+ requireExp: true,
204
+ secret: refreshToken.secret
205
+ };
206
+ }
207
+ async verifyToken(token, options, keyResolutionState, jwksClient) {
208
+ const [headerSegment, payloadSegment, signatureSegment] = this.parseTokenSegments(token);
209
+ const header = parseJwtPart(headerSegment);
210
+ const algorithms = options.algorithms;
211
+ if (!isAllowedAlgorithm(header.alg, algorithms)) {
212
+ throw new JwtInvalidTokenError('JWT algorithm is not allowed.');
213
+ }
214
+ const signingInput = `${headerSegment}.${payloadSegment}`;
215
+ await this.verifyTokenSignature({
216
+ ...header,
217
+ alg: header.alg
218
+ }, signingInput, signatureSegment, options, keyResolutionState, jwksClient);
219
+ const payload = parseJwtPart(payloadSegment);
220
+ this.validateTokenClaims(payload, options);
221
+ return normalizePrincipal(payload);
222
+ }
223
+ parseTokenSegments(token) {
224
+ const segments = token.split('.');
225
+ if (segments.length !== 3) {
226
+ throw new JwtInvalidTokenError();
227
+ }
228
+ return segments;
229
+ }
230
+ async verifyTokenSignature(header, signingInput, signatureSegment, options, keyResolutionState, jwksClient) {
231
+ if (header.alg in HMAC_HASH) {
232
+ await this.verifyHmacTokenSignature(header, signingInput, signatureSegment, options, keyResolutionState);
233
+ return;
234
+ }
235
+ await this.verifyAsymmetricTokenSignature(header, signingInput, signatureSegment, options, keyResolutionState, jwksClient);
236
+ }
237
+ async verifyHmacTokenSignature(header, signingInput, signatureSegment, options, keyResolutionState) {
238
+ const providerKey = await this.resolveProviderKey(options, header);
239
+ if (providerKey !== undefined && typeof providerKey !== 'string') {
240
+ throw new JwtConfigurationError('secretOrKeyProvider must return a string for HMAC algorithms.');
241
+ }
242
+ const secret = providerKey ?? resolveHmacSecret(options, keyResolutionState, header.kid);
243
+ if (!secret) {
244
+ throw new JwtConfigurationError('JWT secret is not configured.');
245
+ }
246
+ verifyHmacSignature(header.alg, secret, signingInput, signatureSegment);
247
+ }
248
+ async verifyAsymmetricTokenSignature(header, signingInput, signatureSegment, options, keyResolutionState, jwksClient) {
249
+ const providerKey = await this.resolveProviderKey(options, header);
250
+ const publicKey = providerKey ?? (jwksClient ? await this.resolveJwksPublicKey(header.kid, jwksClient) : resolveStaticPublicKey(options, keyResolutionState, header.kid));
251
+ if (!publicKey) {
252
+ throw new JwtConfigurationError('JWT public key is not configured.');
253
+ }
254
+ verifyAsymmetricSignature(header.alg, publicKey, signingInput, signatureSegment);
255
+ }
256
+ async resolveProviderKey(options, header) {
257
+ if (!options.secretOrKeyProvider) {
258
+ return undefined;
259
+ }
260
+ return options.secretOrKeyProvider({
261
+ ...header
262
+ });
263
+ }
264
+ validateTokenClaims(payload, options) {
265
+ const now = Math.floor(Date.now() / 1000);
266
+ const clockSkew = options.clockSkewSeconds ?? 0;
267
+ if (options.requireExp !== false && typeof payload.exp !== 'number') {
268
+ throw new JwtInvalidTokenError('JWT is missing a required expiration claim.');
269
+ }
270
+ this.validateMaxAgeClaims(payload, options.maxAge, clockSkew, now);
271
+ if (typeof payload.exp === 'number' && payload.exp + clockSkew < now) {
272
+ throw new JwtExpiredTokenError();
273
+ }
274
+ if (typeof payload.nbf === 'number' && payload.nbf - clockSkew > now) {
275
+ throw new JwtInvalidTokenError('JWT is not active yet.');
276
+ }
277
+ this.validateIssuerAndAudience(payload, options);
278
+ }
279
+ validateMaxAgeClaims(payload, maxAge, clockSkew, now) {
280
+ if (typeof maxAge !== 'number') {
281
+ return;
282
+ }
283
+ if (!Number.isFinite(maxAge) || maxAge < 0) {
284
+ throw new JwtConfigurationError('JWT maxAge must be a non-negative finite number.');
285
+ }
286
+ if (!isFiniteNumericDate(payload.iat)) {
287
+ throw new JwtInvalidTokenError('JWT iat claim must be a finite numeric date when maxAge is configured.');
288
+ }
289
+ if (payload.iat - clockSkew > now) {
290
+ throw new JwtInvalidTokenError('JWT iat claim cannot be in the future.');
291
+ }
292
+ if (now - payload.iat > maxAge + clockSkew) {
293
+ throw new JwtExpiredTokenError('JWT exceeds maxAge.');
294
+ }
295
+ }
296
+ validateIssuerAndAudience(payload, options) {
297
+ if (options.issuer && payload.iss !== options.issuer) {
298
+ throw new JwtInvalidTokenError('JWT issuer does not match.');
299
+ }
300
+ if (!options.audience) {
301
+ return;
302
+ }
303
+ const expectedAudience = Array.isArray(options.audience) ? options.audience : [options.audience];
304
+ const actualAudience = Array.isArray(payload.aud) ? payload.aud : payload.aud ? [payload.aud] : [];
305
+ if (!expectedAudience.some(audience => actualAudience.includes(audience))) {
306
+ throw new JwtInvalidTokenError('JWT audience does not match.');
307
+ }
308
+ }
309
+ async resolveJwksPublicKey(kid, jwksClient) {
310
+ if (typeof kid !== 'string' || kid.length === 0) {
311
+ throw new JwtInvalidTokenError('JWT is missing key id (kid) for JWKS resolution.');
312
+ }
313
+ return jwksClient.getSigningKey(kid);
314
+ }
315
+ static {
316
+ _initClass();
317
+ }
318
+ }
319
+ export { _DefaultJwtVerifier as DefaultJwtVerifier };
@@ -0,0 +1,19 @@
1
+ import type { PlatformDiagnosticIssue, PlatformHealthReport, PlatformReadinessReport, PlatformSnapshot } from '@fluojs/runtime';
2
+ export interface JwtPlatformStatusSnapshot {
3
+ readiness: PlatformReadinessReport;
4
+ health: PlatformHealthReport;
5
+ ownership: PlatformSnapshot['ownership'];
6
+ details: Record<string, unknown>;
7
+ }
8
+ export interface JwtStatusAdapterInput {
9
+ componentId?: string;
10
+ readinessCritical?: boolean;
11
+ refreshTokenEnabled?: boolean;
12
+ refreshTokenStoreReady?: boolean;
13
+ refreshTokenStoreReason?: string;
14
+ refreshTokenDependencyId?: string;
15
+ signingKeySource?: 'shared-secret' | 'key-pair' | 'jwks' | 'key-provider';
16
+ }
17
+ export declare function createJwtPlatformStatusSnapshot(input: JwtStatusAdapterInput): JwtPlatformStatusSnapshot;
18
+ export declare function createJwtPlatformDiagnosticIssues(input: JwtStatusAdapterInput): PlatformDiagnosticIssue[];
19
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,oBAAoB,EACpB,uBAAuB,EACvB,gBAAgB,EACjB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,uBAAuB,CAAC;IACnC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,gBAAgB,CAAC,EAAE,eAAe,GAAG,UAAU,GAAG,MAAM,GAAG,cAAc,CAAC;CAC3E;AAwCD,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,qBAAqB,GAAG,yBAAyB,CA4CvG;AAED,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,qBAAqB,GAAG,uBAAuB,EAAE,CAqBzG"}
package/dist/status.js ADDED
@@ -0,0 +1,83 @@
1
+ function isRefreshTokenStoreReady(input) {
2
+ if (!input.refreshTokenEnabled) {
3
+ return true;
4
+ }
5
+ return input.refreshTokenStoreReady ?? true;
6
+ }
7
+ function createReadiness(input) {
8
+ const critical = input.readinessCritical ?? false;
9
+ if (isRefreshTokenStoreReady(input)) {
10
+ return {
11
+ critical,
12
+ status: 'ready'
13
+ };
14
+ }
15
+ return {
16
+ critical,
17
+ reason: input.refreshTokenStoreReason ?? 'JWT refresh token backing store is unavailable.',
18
+ status: critical ? 'not-ready' : 'degraded'
19
+ };
20
+ }
21
+ function createHealth(input) {
22
+ if (isRefreshTokenStoreReady(input)) {
23
+ return {
24
+ status: 'healthy'
25
+ };
26
+ }
27
+ return {
28
+ reason: input.refreshTokenStoreReason ?? 'JWT refresh token backing store is unavailable.',
29
+ status: 'degraded'
30
+ };
31
+ }
32
+ export function createJwtPlatformStatusSnapshot(input) {
33
+ const componentId = input.componentId ?? 'jwt.default';
34
+ const refreshStoreReady = isRefreshTokenStoreReady(input);
35
+ return {
36
+ details: {
37
+ policyBoundary: {
38
+ applicationOwned: ['login credential validation', 'session lifecycle policy', 'consent and account linking orchestration'],
39
+ frameworkOwned: ['jwt sign/verify primitives', 'claim normalization', 'refresh token primitive lifecycle']
40
+ },
41
+ refreshToken: {
42
+ backingStore: {
43
+ dependencyId: input.refreshTokenDependencyId,
44
+ reason: input.refreshTokenStoreReason,
45
+ ready: refreshStoreReady
46
+ },
47
+ enabled: input.refreshTokenEnabled ?? false
48
+ },
49
+ signingKeySource: input.signingKeySource ?? 'shared-secret',
50
+ telemetry: {
51
+ labels: {
52
+ component_id: componentId,
53
+ component_kind: 'jwt',
54
+ operation: 'token-verify',
55
+ result: refreshStoreReady ? 'ready' : 'degraded'
56
+ },
57
+ namespace: 'jwt'
58
+ }
59
+ },
60
+ health: createHealth(input),
61
+ ownership: {
62
+ externallyManaged: true,
63
+ ownsResources: false
64
+ },
65
+ readiness: createReadiness(input)
66
+ };
67
+ }
68
+ export function createJwtPlatformDiagnosticIssues(input) {
69
+ if (isRefreshTokenStoreReady(input)) {
70
+ return [];
71
+ }
72
+ const componentId = input.componentId ?? 'jwt.default';
73
+ const critical = input.readinessCritical ?? false;
74
+ return [{
75
+ code: 'AUTH_JWT_REFRESH_TOKEN_BACKING_STORE_NOT_READY',
76
+ componentId,
77
+ cause: input.refreshTokenStoreReason,
78
+ dependsOn: input.refreshTokenDependencyId ? [input.refreshTokenDependencyId] : undefined,
79
+ fixHint: 'Restore refresh-token store connectivity or disable refresh token mode for this environment.',
80
+ message: critical ? 'JWT refresh token mode is configured as critical, but its backing store is not ready.' : 'JWT refresh token backing store is degraded; access-token verification remains available.',
81
+ severity: critical ? 'error' : 'warning'
82
+ }];
83
+ }
@@ -0,0 +1,56 @@
1
+ import type { KeyObject } from 'node:crypto';
2
+ import type { RefreshTokenOptions } from './refresh/refresh-token.js';
3
+ export type JwtAlgorithm = 'HS256' | 'HS384' | 'HS512' | 'RS256' | 'RS384' | 'RS512' | 'ES256' | 'ES384' | 'ES512';
4
+ export interface JwtKeyEntry {
5
+ kid: string;
6
+ secret?: string;
7
+ privateKey?: string | KeyObject;
8
+ publicKey?: string | KeyObject;
9
+ }
10
+ export interface JwtVerifierOptions {
11
+ algorithms: JwtAlgorithm[];
12
+ accessTokenTtlSeconds?: number;
13
+ audience?: string | string[];
14
+ clockSkewSeconds?: number;
15
+ issuer?: string;
16
+ jwksCacheTtl?: number;
17
+ jwksRequestTimeoutMs?: number;
18
+ jwksUri?: string;
19
+ keys?: JwtKeyEntry[];
20
+ maxAge?: number;
21
+ requireExp?: boolean;
22
+ secretOrKeyProvider?: (header: {
23
+ alg: string;
24
+ kid?: string;
25
+ [key: string]: unknown;
26
+ }) => Promise<string | KeyObject>;
27
+ secret?: string;
28
+ privateKey?: string | KeyObject;
29
+ publicKey?: string | KeyObject;
30
+ refreshToken?: RefreshTokenOptions;
31
+ }
32
+ export interface JwtClaims extends Record<string, unknown> {
33
+ aud?: string | string[];
34
+ exp?: number;
35
+ iat?: number;
36
+ iss?: string;
37
+ nbf?: number;
38
+ scope?: string;
39
+ scopes?: string[];
40
+ sub?: string;
41
+ }
42
+ export interface JwtPrincipal {
43
+ subject: string;
44
+ issuer?: string;
45
+ audience?: string | string[];
46
+ roles?: string[];
47
+ scopes?: string[];
48
+ claims: Record<string, unknown>;
49
+ }
50
+ export interface JwtVerifier {
51
+ verifyAccessToken(token: string): Promise<JwtPrincipal>;
52
+ }
53
+ export interface JwtSigner {
54
+ signAccessToken(claims: JwtClaims): Promise<string>;
55
+ }
56
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAEnH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACrH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,YAAY,CAAC,EAAE,mBAAmB,CAAC;CACpC;AAED,MAAM,WAAW,SAAU,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACxD,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,SAAS;IACxB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrD"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};