@xlt-token/jwt 2.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xltorg
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,111 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ let node_crypto = require("node:crypto");
3
+ let jsonwebtoken = require("jsonwebtoken");
4
+
5
+ //#region src/jwt-config.ts
6
+ const supportedAlgorithms = new Set([
7
+ "HS256",
8
+ "HS384",
9
+ "HS512",
10
+ "RS256",
11
+ "RS384",
12
+ "RS512",
13
+ "ES256",
14
+ "ES384",
15
+ "ES512"
16
+ ]);
17
+ function createJwtStrategyConfig(input) {
18
+ if (!input.activeKid) throw new Error("JWT activeKid is required");
19
+ if (!input.keys.length) throw new Error("JWT keys must not be empty");
20
+ const keys = /* @__PURE__ */ new Map();
21
+ for (const keyInput of input.keys) {
22
+ const key = normalizeJwtKey(keyInput);
23
+ if (keys.has(key.kid)) throw new Error(`JWT key kid "${key.kid}" is duplicated`);
24
+ keys.set(key.kid, key);
25
+ }
26
+ const activeKey = keys.get(input.activeKid);
27
+ if (!activeKey) throw new Error(`JWT activeKid "${input.activeKid}" does not match any configured key`);
28
+ return {
29
+ activeKid: input.activeKid,
30
+ activeKey,
31
+ keys,
32
+ issuer: input.issuer,
33
+ audience: input.audience
34
+ };
35
+ }
36
+ function normalizeJwtKey(input) {
37
+ if (!input.kid) throw new Error("JWT key kid is required");
38
+ if (!supportedAlgorithms.has(input.algorithm)) throw new Error(`JWT algorithm "${input.algorithm}" is not supported`);
39
+ const signingKey = input.signingKey ?? input.secret;
40
+ const verificationKey = input.verificationKey ?? input.secret ?? input.signingKey;
41
+ if (!signingKey || !verificationKey) throw new Error(`JWT key "${input.kid}" requires signing and verification keys`);
42
+ if (input.algorithm.startsWith("HS")) assertStrongHmacSecret(input.kid, signingKey);
43
+ return {
44
+ kid: input.kid,
45
+ algorithm: input.algorithm,
46
+ signingKey,
47
+ verificationKey
48
+ };
49
+ }
50
+ function assertStrongHmacSecret(kid, secret) {
51
+ if ((typeof secret === "string" ? Buffer.byteLength(secret) : Buffer.isBuffer(secret) ? secret.byteLength : 0) < 32) throw new Error(`JWT key "${kid}" uses a weak HMAC secret; use at least 32 bytes`);
52
+ }
53
+
54
+ //#endregion
55
+ //#region src/jwt-strategy.ts
56
+ var JwtStrategy = class {
57
+ constructor(jwtConfig) {
58
+ this.jwtConfig = jwtConfig;
59
+ this.kind = "jwt";
60
+ }
61
+ createToken(loginId, config, options) {
62
+ const jti = (0, node_crypto.randomUUID)();
63
+ const expiresIn = resolveExpiresIn(options?.timeout ?? config.timeout);
64
+ return this.signPayload({
65
+ sub: loginId,
66
+ jti
67
+ }, expiresIn);
68
+ }
69
+ generateToken(payload) {
70
+ return this.signPayload(payload);
71
+ }
72
+ verifyToken(token) {
73
+ const decoded = (0, jsonwebtoken.decode)(token, { complete: true });
74
+ if (!decoded || typeof decoded === "string") throw new Error("JWT token is malformed");
75
+ const { kid, alg } = decoded.header;
76
+ if (!kid) throw new Error("JWT token header is missing kid");
77
+ const key = this.jwtConfig.keys.get(kid);
78
+ if (!key) throw new Error(`JWT key "${kid}" is not configured`);
79
+ if (alg !== key.algorithm) throw new Error(`JWT algorithm "${String(alg)}" does not match configured key algorithm`);
80
+ const payload = (0, jsonwebtoken.verify)(token, key.verificationKey, {
81
+ algorithms: [key.algorithm],
82
+ ...this.jwtConfig.issuer && { issuer: this.jwtConfig.issuer },
83
+ ...this.jwtConfig.audience && { audience: this.jwtConfig.audience }
84
+ });
85
+ if (typeof payload === "string") throw new Error("JWT payload must be an object");
86
+ if (typeof payload.sub !== "string" || typeof payload.jti !== "string") throw new Error("JWT payload requires string sub and jti claims");
87
+ return payload;
88
+ }
89
+ signPayload(payload, expiresIn) {
90
+ const key = this.jwtConfig.activeKey;
91
+ const options = {
92
+ algorithm: key.algorithm,
93
+ keyid: key.kid,
94
+ ...this.jwtConfig.issuer && { issuer: this.jwtConfig.issuer },
95
+ ...this.jwtConfig.audience && { audience: this.jwtConfig.audience },
96
+ ...expiresIn !== void 0 && { expiresIn }
97
+ };
98
+ return (0, jsonwebtoken.sign)(payload, signingKeyFor(key), options);
99
+ }
100
+ };
101
+ function resolveExpiresIn(timeout) {
102
+ if (typeof timeout === "number" && timeout <= 0) return;
103
+ return timeout;
104
+ }
105
+ function signingKeyFor(key) {
106
+ return key.signingKey;
107
+ }
108
+
109
+ //#endregion
110
+ exports.JwtStrategy = JwtStrategy;
111
+ exports.createJwtStrategyConfig = createJwtStrategyConfig;
@@ -0,0 +1,53 @@
1
+ import { JwtPayload, Secret, SignOptions } from "jsonwebtoken";
2
+ import { DurationInput, TokenStrategy, XltTokenConfig } from "@xlt-token/core";
3
+
4
+ //#region src/jwt-config.d.ts
5
+ type JwtAlgorithm = Extract<SignOptions["algorithm"], "HS256" | "HS384" | "HS512" | "RS256" | "RS384" | "RS512" | "ES256" | "ES384" | "ES512">;
6
+ interface JwtKeyInput {
7
+ kid: string;
8
+ algorithm: JwtAlgorithm;
9
+ secret?: Secret;
10
+ signingKey?: Secret;
11
+ verificationKey?: Secret;
12
+ }
13
+ interface JwtKey {
14
+ kid: string;
15
+ algorithm: JwtAlgorithm;
16
+ signingKey: Secret;
17
+ verificationKey: Secret;
18
+ }
19
+ type JwtAudience = string | [string, ...string[]];
20
+ interface JwtStrategyConfigInput {
21
+ activeKid: string;
22
+ keys: JwtKeyInput[];
23
+ issuer?: string;
24
+ audience?: JwtAudience;
25
+ }
26
+ interface JwtStrategyConfig {
27
+ activeKid: string;
28
+ activeKey: JwtKey;
29
+ keys: ReadonlyMap<string, JwtKey>;
30
+ issuer?: string;
31
+ audience?: JwtAudience;
32
+ }
33
+ declare function createJwtStrategyConfig(input: JwtStrategyConfigInput): JwtStrategyConfig;
34
+ //#endregion
35
+ //#region src/jwt-strategy.d.ts
36
+ type XltJwtPayload = JwtPayload & {
37
+ sub: string;
38
+ jti: string;
39
+ };
40
+ declare class JwtStrategy implements TokenStrategy<XltJwtPayload> {
41
+ private readonly jwtConfig;
42
+ readonly kind: "jwt";
43
+ constructor(jwtConfig: JwtStrategyConfig);
44
+ createToken(loginId: string, config: XltTokenConfig, options?: {
45
+ timeout?: DurationInput;
46
+ }): string;
47
+ generateToken(payload: XltJwtPayload): string;
48
+ verifyToken(token: string): XltJwtPayload;
49
+ private signPayload;
50
+ }
51
+ //#endregion
52
+ export { type JwtAlgorithm, type JwtAudience, type JwtKey, type JwtKeyInput, JwtStrategy, type JwtStrategyConfig, type JwtStrategyConfigInput, type XltJwtPayload, createJwtStrategyConfig };
53
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/jwt-config.ts","../src/jwt-strategy.ts"],"mappings":";;;;KAEY,YAAA,GAAe,OAAA,CACzB,WAAA;AAAA,UAIe,WAAA;EACf,GAAA;EACA,SAAA,EAAW,YAAA;EACX,MAAA,GAAS,MAAA;EACT,UAAA,GAAa,MAAA;EACb,eAAA,GAAkB,MAAA;AAAA;AAAA,UAGH,MAAA;EACf,GAAA;EACA,SAAA,EAAW,YAAA;EACX,UAAA,EAAY,MAAA;EACZ,eAAA,EAAiB,MAAA;AAAA;AAAA,KAGP,WAAA;AAAA,UAEK,sBAAA;EACf,SAAA;EACA,IAAA,EAAM,WAAA;EACN,MAAA;EACA,QAAA,GAAW,WAAA;AAAA;AAAA,UAGI,iBAAA;EACf,SAAA;EACA,SAAA,EAAW,MAAA;EACX,IAAA,EAAM,WAAA,SAAoB,MAAA;EAC1B,MAAA;EACA,QAAA,GAAW,WAAA;AAAA;AAAA,iBAeG,uBAAA,CAAwB,KAAA,EAAO,sBAAA,GAAyB,iBAAA;;;KC7C5D,aAAA,GAAgB,UAAA;EAAe,GAAA;EAAa,GAAA;AAAA;AAAA,cAE3C,WAAA,YAAuB,aAAA,CAAc,aAAA;EAAA,iBAGnB,SAAA;EAAA,SAFpB,IAAA;cAEoB,SAAA,EAAW,iBAAA;EAExC,WAAA,CACE,OAAA,UACA,MAAA,EAAQ,cAAA,EACR,OAAA;IAAY,OAAA,GAAU,aAAA;EAAA;EAcxB,aAAA,CAAc,OAAA,EAAS,aAAA;EAIvB,WAAA,CAAY,KAAA,WAAgB,aAAA;EAAA,QAkCpB,WAAA;AAAA"}
@@ -0,0 +1,53 @@
1
+ import { JwtPayload, Secret, SignOptions } from "jsonwebtoken";
2
+ import { DurationInput, TokenStrategy, XltTokenConfig } from "@xlt-token/core";
3
+
4
+ //#region src/jwt-config.d.ts
5
+ type JwtAlgorithm = Extract<SignOptions["algorithm"], "HS256" | "HS384" | "HS512" | "RS256" | "RS384" | "RS512" | "ES256" | "ES384" | "ES512">;
6
+ interface JwtKeyInput {
7
+ kid: string;
8
+ algorithm: JwtAlgorithm;
9
+ secret?: Secret;
10
+ signingKey?: Secret;
11
+ verificationKey?: Secret;
12
+ }
13
+ interface JwtKey {
14
+ kid: string;
15
+ algorithm: JwtAlgorithm;
16
+ signingKey: Secret;
17
+ verificationKey: Secret;
18
+ }
19
+ type JwtAudience = string | [string, ...string[]];
20
+ interface JwtStrategyConfigInput {
21
+ activeKid: string;
22
+ keys: JwtKeyInput[];
23
+ issuer?: string;
24
+ audience?: JwtAudience;
25
+ }
26
+ interface JwtStrategyConfig {
27
+ activeKid: string;
28
+ activeKey: JwtKey;
29
+ keys: ReadonlyMap<string, JwtKey>;
30
+ issuer?: string;
31
+ audience?: JwtAudience;
32
+ }
33
+ declare function createJwtStrategyConfig(input: JwtStrategyConfigInput): JwtStrategyConfig;
34
+ //#endregion
35
+ //#region src/jwt-strategy.d.ts
36
+ type XltJwtPayload = JwtPayload & {
37
+ sub: string;
38
+ jti: string;
39
+ };
40
+ declare class JwtStrategy implements TokenStrategy<XltJwtPayload> {
41
+ private readonly jwtConfig;
42
+ readonly kind: "jwt";
43
+ constructor(jwtConfig: JwtStrategyConfig);
44
+ createToken(loginId: string, config: XltTokenConfig, options?: {
45
+ timeout?: DurationInput;
46
+ }): string;
47
+ generateToken(payload: XltJwtPayload): string;
48
+ verifyToken(token: string): XltJwtPayload;
49
+ private signPayload;
50
+ }
51
+ //#endregion
52
+ export { type JwtAlgorithm, type JwtAudience, type JwtKey, type JwtKeyInput, JwtStrategy, type JwtStrategyConfig, type JwtStrategyConfigInput, type XltJwtPayload, createJwtStrategyConfig };
53
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/jwt-config.ts","../src/jwt-strategy.ts"],"mappings":";;;;KAEY,YAAA,GAAe,OAAA,CACzB,WAAA;AAAA,UAIe,WAAA;EACf,GAAA;EACA,SAAA,EAAW,YAAA;EACX,MAAA,GAAS,MAAA;EACT,UAAA,GAAa,MAAA;EACb,eAAA,GAAkB,MAAA;AAAA;AAAA,UAGH,MAAA;EACf,GAAA;EACA,SAAA,EAAW,YAAA;EACX,UAAA,EAAY,MAAA;EACZ,eAAA,EAAiB,MAAA;AAAA;AAAA,KAGP,WAAA;AAAA,UAEK,sBAAA;EACf,SAAA;EACA,IAAA,EAAM,WAAA;EACN,MAAA;EACA,QAAA,GAAW,WAAA;AAAA;AAAA,UAGI,iBAAA;EACf,SAAA;EACA,SAAA,EAAW,MAAA;EACX,IAAA,EAAM,WAAA,SAAoB,MAAA;EAC1B,MAAA;EACA,QAAA,GAAW,WAAA;AAAA;AAAA,iBAeG,uBAAA,CAAwB,KAAA,EAAO,sBAAA,GAAyB,iBAAA;;;KC7C5D,aAAA,GAAgB,UAAA;EAAe,GAAA;EAAa,GAAA;AAAA;AAAA,cAE3C,WAAA,YAAuB,aAAA,CAAc,aAAA;EAAA,iBAGnB,SAAA;EAAA,SAFpB,IAAA;cAEoB,SAAA,EAAW,iBAAA;EAExC,WAAA,CACE,OAAA,UACA,MAAA,EAAQ,cAAA,EACR,OAAA;IAAY,OAAA,GAAU,aAAA;EAAA;EAcxB,aAAA,CAAc,OAAA,EAAS,aAAA;EAIvB,WAAA,CAAY,KAAA,WAAgB,aAAA;EAAA,QAkCpB,WAAA;AAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,110 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { decode, sign, verify } from "jsonwebtoken";
3
+
4
+ //#region src/jwt-config.ts
5
+ const supportedAlgorithms = new Set([
6
+ "HS256",
7
+ "HS384",
8
+ "HS512",
9
+ "RS256",
10
+ "RS384",
11
+ "RS512",
12
+ "ES256",
13
+ "ES384",
14
+ "ES512"
15
+ ]);
16
+ function createJwtStrategyConfig(input) {
17
+ if (!input.activeKid) throw new Error("JWT activeKid is required");
18
+ if (!input.keys.length) throw new Error("JWT keys must not be empty");
19
+ const keys = /* @__PURE__ */ new Map();
20
+ for (const keyInput of input.keys) {
21
+ const key = normalizeJwtKey(keyInput);
22
+ if (keys.has(key.kid)) throw new Error(`JWT key kid "${key.kid}" is duplicated`);
23
+ keys.set(key.kid, key);
24
+ }
25
+ const activeKey = keys.get(input.activeKid);
26
+ if (!activeKey) throw new Error(`JWT activeKid "${input.activeKid}" does not match any configured key`);
27
+ return {
28
+ activeKid: input.activeKid,
29
+ activeKey,
30
+ keys,
31
+ issuer: input.issuer,
32
+ audience: input.audience
33
+ };
34
+ }
35
+ function normalizeJwtKey(input) {
36
+ if (!input.kid) throw new Error("JWT key kid is required");
37
+ if (!supportedAlgorithms.has(input.algorithm)) throw new Error(`JWT algorithm "${input.algorithm}" is not supported`);
38
+ const signingKey = input.signingKey ?? input.secret;
39
+ const verificationKey = input.verificationKey ?? input.secret ?? input.signingKey;
40
+ if (!signingKey || !verificationKey) throw new Error(`JWT key "${input.kid}" requires signing and verification keys`);
41
+ if (input.algorithm.startsWith("HS")) assertStrongHmacSecret(input.kid, signingKey);
42
+ return {
43
+ kid: input.kid,
44
+ algorithm: input.algorithm,
45
+ signingKey,
46
+ verificationKey
47
+ };
48
+ }
49
+ function assertStrongHmacSecret(kid, secret) {
50
+ if ((typeof secret === "string" ? Buffer.byteLength(secret) : Buffer.isBuffer(secret) ? secret.byteLength : 0) < 32) throw new Error(`JWT key "${kid}" uses a weak HMAC secret; use at least 32 bytes`);
51
+ }
52
+
53
+ //#endregion
54
+ //#region src/jwt-strategy.ts
55
+ var JwtStrategy = class {
56
+ constructor(jwtConfig) {
57
+ this.jwtConfig = jwtConfig;
58
+ this.kind = "jwt";
59
+ }
60
+ createToken(loginId, config, options) {
61
+ const jti = randomUUID();
62
+ const expiresIn = resolveExpiresIn(options?.timeout ?? config.timeout);
63
+ return this.signPayload({
64
+ sub: loginId,
65
+ jti
66
+ }, expiresIn);
67
+ }
68
+ generateToken(payload) {
69
+ return this.signPayload(payload);
70
+ }
71
+ verifyToken(token) {
72
+ const decoded = decode(token, { complete: true });
73
+ if (!decoded || typeof decoded === "string") throw new Error("JWT token is malformed");
74
+ const { kid, alg } = decoded.header;
75
+ if (!kid) throw new Error("JWT token header is missing kid");
76
+ const key = this.jwtConfig.keys.get(kid);
77
+ if (!key) throw new Error(`JWT key "${kid}" is not configured`);
78
+ if (alg !== key.algorithm) throw new Error(`JWT algorithm "${String(alg)}" does not match configured key algorithm`);
79
+ const payload = verify(token, key.verificationKey, {
80
+ algorithms: [key.algorithm],
81
+ ...this.jwtConfig.issuer && { issuer: this.jwtConfig.issuer },
82
+ ...this.jwtConfig.audience && { audience: this.jwtConfig.audience }
83
+ });
84
+ if (typeof payload === "string") throw new Error("JWT payload must be an object");
85
+ if (typeof payload.sub !== "string" || typeof payload.jti !== "string") throw new Error("JWT payload requires string sub and jti claims");
86
+ return payload;
87
+ }
88
+ signPayload(payload, expiresIn) {
89
+ const key = this.jwtConfig.activeKey;
90
+ const options = {
91
+ algorithm: key.algorithm,
92
+ keyid: key.kid,
93
+ ...this.jwtConfig.issuer && { issuer: this.jwtConfig.issuer },
94
+ ...this.jwtConfig.audience && { audience: this.jwtConfig.audience },
95
+ ...expiresIn !== void 0 && { expiresIn }
96
+ };
97
+ return sign(payload, signingKeyFor(key), options);
98
+ }
99
+ };
100
+ function resolveExpiresIn(timeout) {
101
+ if (typeof timeout === "number" && timeout <= 0) return;
102
+ return timeout;
103
+ }
104
+ function signingKeyFor(key) {
105
+ return key.signingKey;
106
+ }
107
+
108
+ //#endregion
109
+ export { JwtStrategy, createJwtStrategyConfig };
110
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/jwt-config.ts","../src/jwt-strategy.ts"],"sourcesContent":["import type { Secret, SignOptions } from \"jsonwebtoken\";\n\nexport type JwtAlgorithm = Extract<\n SignOptions[\"algorithm\"],\n \"HS256\" | \"HS384\" | \"HS512\" | \"RS256\" | \"RS384\" | \"RS512\" | \"ES256\" | \"ES384\" | \"ES512\"\n>;\n\nexport interface JwtKeyInput {\n kid: string;\n algorithm: JwtAlgorithm;\n secret?: Secret;\n signingKey?: Secret;\n verificationKey?: Secret;\n}\n\nexport interface JwtKey {\n kid: string;\n algorithm: JwtAlgorithm;\n signingKey: Secret;\n verificationKey: Secret;\n}\n\nexport type JwtAudience = string | [string, ...string[]];\n\nexport interface JwtStrategyConfigInput {\n activeKid: string;\n keys: JwtKeyInput[];\n issuer?: string;\n audience?: JwtAudience;\n}\n\nexport interface JwtStrategyConfig {\n activeKid: string;\n activeKey: JwtKey;\n keys: ReadonlyMap<string, JwtKey>;\n issuer?: string;\n audience?: JwtAudience;\n}\n\nconst supportedAlgorithms = new Set<JwtAlgorithm>([\n \"HS256\",\n \"HS384\",\n \"HS512\",\n \"RS256\",\n \"RS384\",\n \"RS512\",\n \"ES256\",\n \"ES384\",\n \"ES512\",\n]);\n\nexport function createJwtStrategyConfig(input: JwtStrategyConfigInput): JwtStrategyConfig {\n if (!input.activeKid) {\n throw new Error(\"JWT activeKid is required\");\n }\n if (!input.keys.length) {\n throw new Error(\"JWT keys must not be empty\");\n }\n\n const keys = new Map<string, JwtKey>();\n for (const keyInput of input.keys) {\n const key = normalizeJwtKey(keyInput);\n if (keys.has(key.kid)) {\n throw new Error(`JWT key kid \"${key.kid}\" is duplicated`);\n }\n keys.set(key.kid, key);\n }\n\n const activeKey = keys.get(input.activeKid);\n if (!activeKey) {\n throw new Error(`JWT activeKid \"${input.activeKid}\" does not match any configured key`);\n }\n\n return {\n activeKid: input.activeKid,\n activeKey,\n keys,\n issuer: input.issuer,\n audience: input.audience,\n };\n}\n\nfunction normalizeJwtKey(input: JwtKeyInput): JwtKey {\n if (!input.kid) {\n throw new Error(\"JWT key kid is required\");\n }\n if (!supportedAlgorithms.has(input.algorithm)) {\n throw new Error(`JWT algorithm \"${input.algorithm}\" is not supported`);\n }\n\n const signingKey = input.signingKey ?? input.secret;\n const verificationKey = input.verificationKey ?? input.secret ?? input.signingKey;\n if (!signingKey || !verificationKey) {\n throw new Error(`JWT key \"${input.kid}\" requires signing and verification keys`);\n }\n if (input.algorithm.startsWith(\"HS\")) {\n assertStrongHmacSecret(input.kid, signingKey);\n }\n\n return {\n kid: input.kid,\n algorithm: input.algorithm,\n signingKey,\n verificationKey,\n };\n}\n\nfunction assertStrongHmacSecret(kid: string, secret: Secret): void {\n const length =\n typeof secret === \"string\"\n ? Buffer.byteLength(secret)\n : Buffer.isBuffer(secret)\n ? secret.byteLength\n : 0;\n\n if (length < 32) {\n throw new Error(`JWT key \"${kid}\" uses a weak HMAC secret; use at least 32 bytes`);\n }\n}\n","import { randomUUID } from \"node:crypto\";\nimport { decode, sign, verify } from \"jsonwebtoken\";\nimport type { JwtPayload, SignOptions } from \"jsonwebtoken\";\nimport type { DurationInput, TokenStrategy, XltTokenConfig } from \"@xlt-token/core\";\nimport type { JwtKey, JwtStrategyConfig } from \"./jwt-config.js\";\n\nexport type XltJwtPayload = JwtPayload & { sub: string; jti: string };\n\nexport class JwtStrategy implements TokenStrategy<XltJwtPayload> {\n readonly kind = \"jwt\" as const;\n\n constructor(private readonly jwtConfig: JwtStrategyConfig) {}\n\n createToken(\n loginId: string,\n config: XltTokenConfig,\n options?: { timeout?: DurationInput },\n ): string {\n const jti = randomUUID();\n const expiresIn = resolveExpiresIn(options?.timeout ?? config.timeout);\n\n return this.signPayload(\n {\n sub: loginId,\n jti,\n },\n expiresIn,\n );\n }\n\n generateToken(payload: XltJwtPayload): string {\n return this.signPayload(payload);\n }\n\n verifyToken(token: string): XltJwtPayload {\n const decoded = decode(token, { complete: true });\n if (!decoded || typeof decoded === \"string\") {\n throw new Error(\"JWT token is malformed\");\n }\n\n const { kid, alg } = decoded.header;\n if (!kid) {\n throw new Error(\"JWT token header is missing kid\");\n }\n\n const key = this.jwtConfig.keys.get(kid);\n if (!key) {\n throw new Error(`JWT key \"${kid}\" is not configured`);\n }\n if (alg !== key.algorithm) {\n throw new Error(`JWT algorithm \"${String(alg)}\" does not match configured key algorithm`);\n }\n\n const payload = verify(token, key.verificationKey, {\n algorithms: [key.algorithm],\n ...(this.jwtConfig.issuer && { issuer: this.jwtConfig.issuer }),\n ...(this.jwtConfig.audience && { audience: this.jwtConfig.audience }),\n });\n if (typeof payload === \"string\") {\n throw new Error(\"JWT payload must be an object\");\n }\n if (typeof payload.sub !== \"string\" || typeof payload.jti !== \"string\") {\n throw new Error(\"JWT payload requires string sub and jti claims\");\n }\n\n return payload as XltJwtPayload;\n }\n\n private signPayload(payload: object, expiresIn?: DurationInput): string {\n const key = this.jwtConfig.activeKey;\n const options: SignOptions = {\n algorithm: key.algorithm,\n keyid: key.kid,\n ...(this.jwtConfig.issuer && { issuer: this.jwtConfig.issuer }),\n ...(this.jwtConfig.audience && { audience: this.jwtConfig.audience }),\n ...(expiresIn !== undefined && { expiresIn }),\n };\n\n return sign(payload, signingKeyFor(key), options);\n }\n}\n\nfunction resolveExpiresIn(timeout: DurationInput): DurationInput | undefined {\n if (typeof timeout === \"number\" && timeout <= 0) {\n return undefined;\n }\n return timeout;\n}\n\nfunction signingKeyFor(key: JwtKey) {\n return key.signingKey;\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@xlt-token/jwt",
3
+ "version": "2.1.0",
4
+ "description": "JWT token strategy with key rotation for xlt-token",
5
+ "keywords": [
6
+ "auth",
7
+ "jwt",
8
+ "token",
9
+ "xlt-token"
10
+ ],
11
+ "homepage": "https://xiaolangtou.github.io/xlt-token/",
12
+ "bugs": {
13
+ "url": "https://github.com/xiaoLangtou/xlt-token/issues"
14
+ },
15
+ "license": "MIT",
16
+ "author": "xltorg",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/xiaoLangtou/xlt-token.git",
20
+ "directory": "packages/jwt"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "LICENSE",
25
+ "README.md"
26
+ ],
27
+ "type": "module",
28
+ "sideEffects": false,
29
+ "main": "./dist/index.cjs",
30
+ "module": "./dist/index.mjs",
31
+ "types": "./dist/index.d.cts",
32
+ "exports": {
33
+ ".": {
34
+ "import": {
35
+ "types": "./dist/index.d.mts",
36
+ "default": "./dist/index.mjs"
37
+ },
38
+ "require": {
39
+ "types": "./dist/index.d.cts",
40
+ "default": "./dist/index.cjs"
41
+ }
42
+ },
43
+ "./package.json": "./package.json"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "dependencies": {
49
+ "@xlt-token/core": "^2.1.0",
50
+ "jsonwebtoken": "^9.0.3"
51
+ },
52
+ "devDependencies": {
53
+ "@types/jsonwebtoken": "^9.0.10",
54
+ "@types/node": "^20.0.0",
55
+ "@vitest/coverage-v8": "^2.1.9",
56
+ "tsdown": "^0.20.0",
57
+ "typescript": "^5.1.0",
58
+ "vitest": "^2.0.0"
59
+ },
60
+ "scripts": {
61
+ "build": "tsdown",
62
+ "build:watch": "tsdown --watch",
63
+ "typecheck": "tsc --noEmit",
64
+ "test": "vitest run",
65
+ "test:cov": "vitest run --coverage"
66
+ }
67
+ }