@aetherframework/middleware 1.0.2

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,929 @@
1
+ /**
2
+ * @license MIT
3
+ * Copyright (c) 2026-present AetherFramework Contributors.
4
+ * SPDX-License-Identifier: MIT
5
+ * @module @aetherframework/middleware/middleware/middleware/jwt.js
6
+ */
7
+
8
+ /**
9
+ * Helper: Parse algorithm configuration from string to array
10
+ * @param {string} algorithmsString - Comma-separated algorithm string
11
+ * @returns {string[]} Array of algorithm names
12
+ */
13
+ function parseAlgorithms(algorithmsString) {
14
+ if (!algorithmsString) return ["HS256"];
15
+ return algorithmsString.split(",").map((alg) => alg.trim());
16
+ }
17
+
18
+ /**
19
+ * Helper: Base64 URL-safe encode
20
+ * @param {string} str - String to encode
21
+ * @returns {string} Base64 URL-safe encoded string
22
+ */
23
+ function base64UrlEncode(str) {
24
+ return btoa(str)
25
+ .replace(/\+/g, "-")
26
+ .replace(/\//g, "_")
27
+ .replace(/=+$/, "");
28
+ }
29
+
30
+ /**
31
+ * Helper: Base64 URL-safe decode
32
+ * @param {string} base64Url - Base64 URL-safe string to decode
33
+ * @returns {string} Decoded string
34
+ */
35
+ function base64UrlDecode(base64Url) {
36
+ // Add padding if needed
37
+ let base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
38
+ const pad = base64.length % 4;
39
+ if (pad) {
40
+ if (pad === 1) {
41
+ throw new Error("Invalid base64 string");
42
+ }
43
+ base64 += "=".repeat(4 - pad);
44
+ }
45
+ return atob(base64);
46
+ }
47
+
48
+ /**
49
+ * Helper: Convert string to ArrayBuffer
50
+ * @param {string} str - String to convert
51
+ * @returns {Uint8Array} ArrayBuffer representation
52
+ */
53
+ function strToBuffer(str) {
54
+ const encoder = new TextEncoder();
55
+ return encoder.encode(str);
56
+ }
57
+
58
+ /**
59
+ * Helper: Convert ArrayBuffer to Base64 string
60
+ * @param {ArrayBuffer} buffer - ArrayBuffer to convert
61
+ * @returns {string} Base64 string
62
+ */
63
+ function arrayBufferToBase64(buffer) {
64
+ if (typeof Buffer !== "undefined") {
65
+ // Node.js environment
66
+ const Buffer = require("buffer").Buffer;
67
+ return Buffer.from(buffer).toString("base64");
68
+ } else {
69
+ // Browser environment
70
+ let binary = "";
71
+ const bytes = new Uint8Array(buffer);
72
+ for (let i = 0; i < bytes.byteLength; i++) {
73
+ binary += String.fromCharCode(bytes[i]);
74
+ }
75
+ return btoa(binary);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Helper: Convert Base64 to ArrayBuffer
81
+ * @param {string} base64 - Base64 string to convert
82
+ * @returns {Uint8Array} ArrayBuffer representation
83
+ */
84
+ function base64ToArrayBuffer(base64) {
85
+ if (typeof Buffer !== "undefined") {
86
+ // Node.js environment
87
+ const Buffer = require("buffer").Buffer;
88
+ return new Uint8Array(Buffer.from(base64, "base64"));
89
+ } else {
90
+ // Browser environment
91
+ const binary = atob(base64);
92
+ const bytes = new Uint8Array(binary.length);
93
+ for (let i = 0; i < binary.length; i++) {
94
+ bytes[i] = binary.charCodeAt(i);
95
+ }
96
+ return bytes;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Algorithm registry for supported JWT algorithms
102
+ */
103
+ const ALGORITHMS = {
104
+ // HMAC algorithms (symmetric)
105
+ HS256: {
106
+ name: "HMAC",
107
+ hash: "SHA-256",
108
+ type: "symmetric",
109
+ minKeyLength: 32, // Minimum key length in bytes
110
+ },
111
+ HS384: {
112
+ name: "HMAC",
113
+ hash: "SHA-384",
114
+ type: "symmetric",
115
+ minKeyLength: 48,
116
+ },
117
+ HS512: {
118
+ name: "HMAC",
119
+ hash: "SHA-512",
120
+ type: "symmetric",
121
+ minKeyLength: 64,
122
+ },
123
+
124
+ // RSA algorithms (asymmetric) - Note: Requires proper key handling
125
+ RS256: {
126
+ name: "RSASSA-PKCS1-v1_5",
127
+ hash: "SHA-256",
128
+ type: "asymmetric",
129
+ requires: { publicKey: true, privateKey: true },
130
+ },
131
+ RS384: {
132
+ name: "RSASSA-PKCS1-v1_5",
133
+ hash: "SHA-384",
134
+ type: "asymmetric",
135
+ requires: { publicKey: true, privateKey: true },
136
+ },
137
+ RS512: {
138
+ name: "RSASSA-PKCS1-v1_5",
139
+ hash: "SHA-512",
140
+ type: "asymmetric",
141
+ requires: { publicKey: true, privateKey: true },
142
+ },
143
+
144
+ // ECDSA algorithms (asymmetric)
145
+ ES256: {
146
+ name: "ECDSA",
147
+ hash: "SHA-256",
148
+ type: "asymmetric",
149
+ curve: "P-256",
150
+ requires: { publicKey: true, privateKey: true },
151
+ },
152
+ ES384: {
153
+ name: "ECDSA",
154
+ hash: "SHA-384",
155
+ type: "asymmetric",
156
+ curve: "P-384",
157
+ requires: { publicKey: true, privateKey: true },
158
+ },
159
+ ES512: {
160
+ name: "ECDSA",
161
+ hash: "SHA-512",
162
+ type: "asymmetric",
163
+ curve: "P-521",
164
+ requires: { publicKey: true, privateKey: true },
165
+ },
166
+
167
+ // EdDSA algorithm (asymmetric)
168
+ EdDSA: {
169
+ name: "Ed25519",
170
+ type: "asymmetric",
171
+ requires: { publicKey: true, privateKey: true },
172
+ },
173
+ };
174
+
175
+ /**
176
+ * Generate HMAC signature for symmetric algorithms
177
+ * @param {string} algorithm - Algorithm name (HS256, HS384, HS512)
178
+ * @param {string} key - Secret key
179
+ * @param {string} data - Data to sign
180
+ * @returns {Promise<Uint8Array>} Signature
181
+ */
182
+ async function generateHmacSignature(algorithm, key, data) {
183
+ const algo = ALGORITHMS[algorithm];
184
+ if (!algo) {
185
+ throw new Error(`Unsupported HMAC algorithm: ${algorithm}`);
186
+ }
187
+
188
+ const keyBuf = strToBuffer(key);
189
+ const dataBuf = strToBuffer(data);
190
+
191
+ if (typeof crypto !== "undefined" && crypto.subtle) {
192
+ // Browser/Node.js 15+ environment
193
+ const cryptoKey = await crypto.subtle.importKey(
194
+ "raw",
195
+ keyBuf,
196
+ { name: algo.name, hash: { name: algo.hash } },
197
+ false,
198
+ ["sign", "verify"]
199
+ );
200
+ const signature = await crypto.subtle.sign(algo.name, cryptoKey, dataBuf);
201
+ return new Uint8Array(signature);
202
+ } else if (typeof require !== "undefined") {
203
+ // Node.js environment with crypto module
204
+ const crypto = require("crypto");
205
+ const hmac = crypto.createHmac(algo.hash.toLowerCase().replace("-", ""), key);
206
+ hmac.update(data);
207
+ return new Uint8Array(hmac.digest());
208
+ } else {
209
+ throw new Error("Crypto API not available");
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Verify HMAC signature for symmetric algorithms
215
+ * @param {string} algorithm - Algorithm name (HS256, HS384, HS512)
216
+ * @param {string} key - Secret key
217
+ * @param {string} data - Original data
218
+ * @param {Uint8Array} signature - Signature to verify
219
+ * @returns {Promise<boolean>} True if signature is valid
220
+ */
221
+ async function verifyHmacSignature(algorithm, key, data, signature) {
222
+ const algo = ALGORITHMS[algorithm];
223
+ if (!algo) {
224
+ throw new Error(`Unsupported HMAC algorithm: ${algorithm}`);
225
+ }
226
+
227
+ const keyBuf = strToBuffer(key);
228
+ const dataBuf = strToBuffer(data);
229
+
230
+ if (typeof crypto !== "undefined" && crypto.subtle) {
231
+ // Browser/Node.js 15+ environment
232
+ const cryptoKey = await crypto.subtle.importKey(
233
+ "raw",
234
+ keyBuf,
235
+ { name: algo.name, hash: { name: algo.hash } },
236
+ false,
237
+ ["verify"]
238
+ );
239
+ return await crypto.subtle.verify(algo.name, cryptoKey, signature, dataBuf);
240
+ } else if (typeof require !== "undefined") {
241
+ // Node.js environment with crypto module
242
+ const crypto = require("crypto");
243
+ const hmac = crypto.createHmac(algo.hash.toLowerCase().replace("-", ""), key);
244
+ hmac.update(data);
245
+ const expectedSignature = hmac.digest();
246
+
247
+ // Constant-time comparison to prevent timing attacks
248
+ const expected = new Uint8Array(expectedSignature);
249
+ const provided = new Uint8Array(signature);
250
+
251
+ if (expected.length !== provided.length) return false;
252
+
253
+ let result = 0;
254
+ for (let i = 0; i < expected.length; i++) {
255
+ result |= expected[i]^ provided[i];
256
+ }
257
+ return result === 0;
258
+ } else {
259
+ throw new Error("Crypto API not available");
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Generate RSA/ECDSA signature for asymmetric algorithms
265
+ * @param {string} algorithm - Algorithm name (RS256, RS384, RS512, ES256, ES384, ES512)
266
+ * @param {string|Object} key - Private key (PEM string or JWK)
267
+ * @param {string} data - Data to sign
268
+ * @returns {Promise<Uint8Array>} Signature
269
+ */
270
+ async function generateAsymmetricSignature(algorithm, key, data) {
271
+ const algo = ALGORITHMS[algorithm];
272
+ if (!algo) {
273
+ throw new Error(`Unsupported asymmetric algorithm: ${algorithm}`);
274
+ }
275
+
276
+ const dataBuf = strToBuffer(data);
277
+
278
+ if (typeof crypto !== "undefined" && crypto.subtle) {
279
+ // Browser/Node.js 15+ environment
280
+ let cryptoKey;
281
+
282
+ if (typeof key === "string") {
283
+ // PEM format key
284
+ cryptoKey = await crypto.subtle.importKey(
285
+ "pkcs8",
286
+ pemToArrayBuffer(key),
287
+ { name: algo.name, hash: { name: algo.hash } },
288
+ false,
289
+ ["sign"]
290
+ );
291
+ } else {
292
+ // JWK format key
293
+ cryptoKey = await crypto.subtle.importKey(
294
+ "jwk",
295
+ key,
296
+ { name: algo.name, hash: { name: algo.hash } },
297
+ false,
298
+ ["sign"]
299
+ );
300
+ }
301
+
302
+ const signature = await crypto.subtle.sign(
303
+ { name: algo.name, hash: { name: algo.hash } },
304
+ cryptoKey,
305
+ dataBuf
306
+ );
307
+ return new Uint8Array(signature);
308
+ } else if (typeof require !== "undefined") {
309
+ // Node.js environment with crypto module
310
+ const crypto = require("crypto");
311
+ const sign = crypto.createSign(algo.hash.replace("SHA-", "RSA-SHA"));
312
+ sign.update(data);
313
+ sign.end();
314
+
315
+ if (typeof key === "string") {
316
+ return new Uint8Array(sign.sign(key));
317
+ } else {
318
+ throw new Error("JWK format not supported in Node.js crypto module");
319
+ }
320
+ } else {
321
+ throw new Error("Crypto API not available");
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Verify RSA/ECDSA signature for asymmetric algorithms
327
+ * @param {string} algorithm - Algorithm name (RS256, RS384, RS512, ES256, ES384, ES512)
328
+ * @param {string|Object} key - Public key (PEM string or JWK)
329
+ * @param {string} data - Original data
330
+ * @param {Uint8Array} signature - Signature to verify
331
+ * @returns {Promise<boolean>} True if signature is valid
332
+ */
333
+ async function verifyAsymmetricSignature(algorithm, key, data, signature) {
334
+ const algo = ALGORITHMS[algorithm];
335
+ if (!algo) {
336
+ throw new Error(`Unsupported asymmetric algorithm: ${algorithm}`);
337
+ }
338
+
339
+ const dataBuf = strToBuffer(data);
340
+
341
+ if (typeof crypto !== "undefined" && crypto.subtle) {
342
+ // Browser/Node.js 15+ environment
343
+ let cryptoKey;
344
+
345
+ if (typeof key === "string") {
346
+ // PEM format key
347
+ cryptoKey = await crypto.subtle.importKey(
348
+ "spki",
349
+ pemToArrayBuffer(key),
350
+ { name: algo.name, hash: { name: algo.hash } },
351
+ false,
352
+ ["verify"]
353
+ );
354
+ } else {
355
+ // JWK format key
356
+ cryptoKey = await crypto.subtle.importKey(
357
+ "jwk",
358
+ key,
359
+ { name: algo.name, hash: { name: algo.hash } },
360
+ false,
361
+ ["verify"]
362
+ );
363
+ }
364
+
365
+ return await crypto.subtle.verify(
366
+ { name: algo.name, hash: { name: algo.hash } },
367
+ cryptoKey,
368
+ signature,
369
+ dataBuf
370
+ );
371
+ } else if (typeof require !== "undefined") {
372
+ // Node.js environment with crypto module
373
+ const crypto = require("crypto");
374
+ const verify = crypto.createVerify(algo.hash.replace("SHA-", "RSA-SHA"));
375
+ verify.update(data);
376
+ verify.end();
377
+
378
+ if (typeof key === "string") {
379
+ return verify.verify(key, Buffer.from(signature));
380
+ } else {
381
+ throw new Error("JWK format not supported in Node.js crypto module");
382
+ }
383
+ } else {
384
+ throw new Error("Crypto API not available");
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Convert PEM key to ArrayBuffer
390
+ * @param {string} pem - PEM formatted key
391
+ * @returns {ArrayBuffer} ArrayBuffer representation
392
+ */
393
+ function pemToArrayBuffer(pem) {
394
+ // Remove PEM headers and footers
395
+ const base64 = pem
396
+ .replace(/-----BEGIN.*?-----/g, "")
397
+ .replace(/-----END.*?-----/g, "")
398
+ .replace(/\s/g, "");
399
+
400
+ return base64ToArrayBuffer(base64);
401
+ }
402
+
403
+ /**
404
+ * Generate signature based on algorithm type
405
+ * @param {string} algorithm - JWT algorithm name
406
+ * @param {string|Object} key - Secret or key
407
+ * @param {string} data - Data to sign
408
+ * @returns {Promise<Uint8Array>} Signature
409
+ */
410
+ async function generateSignature(algorithm, key, data) {
411
+ const algo = ALGORITHMS[algorithm];
412
+ if (!algo) {
413
+ throw new Error(`Unsupported algorithm: ${algorithm}`);
414
+ }
415
+
416
+ if (algo.type === "symmetric") {
417
+ return generateHmacSignature(algorithm, key, data);
418
+ } else if (algo.type === "asymmetric") {
419
+ return generateAsymmetricSignature(algorithm, key, data);
420
+ } else {
421
+ throw new Error(`Unknown algorithm type: ${algo.type}`);
422
+ }
423
+ }
424
+
425
+ /**
426
+ * Verify signature based on algorithm type
427
+ * @param {string} algorithm - JWT algorithm name
428
+ * @param {string|Object} key - Secret or key
429
+ * @param {string} data - Original data
430
+ * @param {Uint8Array} signature - Signature to verify
431
+ * @returns {Promise<boolean>} True if signature is valid
432
+ */
433
+ async function verifySignature(algorithm, key, data, signature) {
434
+ const algo = ALGORITHMS[algorithm];
435
+ if (!algo) {
436
+ throw new Error(`Unsupported algorithm: ${algorithm}`);
437
+ }
438
+
439
+ if (algo.type === "symmetric") {
440
+ return verifyHmacSignature(algorithm, key, data, signature);
441
+ } else if (algo.type === "asymmetric") {
442
+ return verifyAsymmetricSignature(algorithm, key, data, signature);
443
+ } else {
444
+ throw new Error(`Unknown algorithm type: ${algo.type}`);
445
+ }
446
+ }
447
+
448
+ /**
449
+ * JWT Sign function - Create a JWT token with multiple algorithm support
450
+ * @param {Object} payload - The JWT payload
451
+ * @param {string|Object} key - Secret key for symmetric algorithms or private key for asymmetric
452
+ * @param {Object} options - Signing options
453
+ * @returns {Promise<string>} - JWT token
454
+ */
455
+ async function jwtSign(payload, key, options = {}) {
456
+ const algorithm = options.algorithm || "HS256";
457
+ const expiresIn = options.expiresIn || "7d";
458
+ const audience = options.audience;
459
+ const issuer = options.issuer;
460
+ const subject = options.subject;
461
+ const jwtId = options.jwtId;
462
+
463
+ const algo = ALGORITHMS[algorithm];
464
+ if (!algo) {
465
+ throw new Error(`Algorithm ${algorithm} not supported. Supported algorithms: ${Object.keys(ALGORITHMS).join(", ")}`);
466
+ }
467
+
468
+ // Prepare header
469
+ const header = {
470
+ alg: algorithm,
471
+ typ: "JWT",
472
+ };
473
+
474
+ // Prepare payload with standard claims
475
+ const finalPayload = { ...payload };
476
+
477
+ // iat: issued at
478
+ finalPayload.iat = Math.floor(Date.now() / 1000);
479
+
480
+ // exp: expiration
481
+ if (expiresIn) {
482
+ let seconds = 0;
483
+ if (typeof expiresIn === "number") {
484
+ seconds = expiresIn;
485
+ } else if (typeof expiresIn === "string") {
486
+ const match = expiresIn.match(/^(\d+)([smhd])$/);
487
+ if (match) {
488
+ const value = parseInt(match, 10);
489
+ const unit = match;
490
+ switch (unit) {
491
+ case "s": seconds = value; break;
492
+ case "m": seconds = value * 60; break;
493
+ case "h": seconds = value * 60 * 60; break;
494
+ case "d": seconds = value * 60 * 60 * 24; break;
495
+ default: seconds = value;
496
+ }
497
+ } else if (expiresIn === "7d") {
498
+ seconds = 7 * 24 * 60 * 60; // Default 7 days
499
+ }
500
+ }
501
+ if (seconds > 0) {
502
+ finalPayload.exp = finalPayload.iat + seconds;
503
+ }
504
+ }
505
+
506
+ // Add other standard claims
507
+ if (audience) finalPayload.aud = audience;
508
+ if (issuer) finalPayload.iss = issuer;
509
+ if (subject) finalPayload.sub = subject;
510
+ if (jwtId) finalPayload.jti = jwtId;
511
+
512
+ // Create header and payload parts
513
+ const headerEncoded = base64UrlEncode(JSON.stringify(header));
514
+ const payloadEncoded = base64UrlEncode(JSON.stringify(finalPayload));
515
+ const dataToSign = `${headerEncoded}.${payloadEncoded}`;
516
+
517
+ // Create signature
518
+ const signatureBuffer = await generateSignature(algorithm, key, dataToSign);
519
+
520
+ // Convert signature to base64 URL-safe
521
+ const signatureBase64 = arrayBufferToBase64(signatureBuffer);
522
+ const signatureEncoded = signatureBase64
523
+ .replace(/\+/g, "-")
524
+ .replace(/\//g, "_")
525
+ .replace(/=+$/, "");
526
+
527
+ return `${dataToSign}.${signatureEncoded}`;
528
+ }
529
+
530
+ /**
531
+ * JWT Verify function - Verify a JWT token with multiple algorithm support
532
+ * @param {string} token - The JWT token to verify
533
+ * @param {string|Object} key - Secret key for symmetric algorithms or public key for asymmetric
534
+ * @param {Object} options - Verification options
535
+ * @returns {Promise<Object>} - Decoded payload if valid
536
+ */
537
+ async function jwtVerify(token, key, options = {}) {
538
+ const parts = token.split(".");
539
+ if (parts.length !== 3) {
540
+ throw new Error("Invalid token format");
541
+ }
542
+
543
+ const [headerEncoded, payloadEncoded, signatureEncoded] = parts;
544
+
545
+ // Decode header
546
+ let header;
547
+ try {
548
+ header = JSON.parse(base64UrlDecode(headerEncoded));
549
+ } catch (e) {
550
+ throw new Error("Invalid token header");
551
+ }
552
+
553
+ // Check algorithm
554
+ const algorithm = header.alg;
555
+ const algo = ALGORITHMS[algorithm];
556
+ if (!algo) {
557
+ throw new Error(`Algorithm ${algorithm} not supported. Supported algorithms: ${Object.keys(ALGORITHMS).join(", ")}`);
558
+ }
559
+
560
+ // Verify signature
561
+ const dataToSign = `${headerEncoded}.${payloadEncoded}`;
562
+ const signatureBuffer = base64ToArrayBuffer(
563
+ signatureEncoded.replace(/-/g, "+").replace(/_/g, "/")
564
+ );
565
+
566
+ const isValid = await verifySignature(algorithm, key, dataToSign, signatureBuffer);
567
+ if (!isValid) {
568
+ throw new Error("Invalid signature");
569
+ }
570
+
571
+ // Decode payload
572
+ let payload;
573
+ try {
574
+ payload = JSON.parse(base64UrlDecode(payloadEncoded));
575
+ } catch (e) {
576
+ throw new Error("Invalid token payload");
577
+ }
578
+
579
+ // Verify expiration
580
+ const now = Math.floor(Date.now() / 1000);
581
+ if (payload.exp && payload.exp < now) {
582
+ if (!options.ignoreExpiration) {
583
+ throw new Error("Token expired");
584
+ }
585
+ }
586
+
587
+ // Verify not before
588
+ if (payload.nbf && payload.nbf > now) {
589
+ throw new Error("Token not yet active");
590
+ }
591
+
592
+ // Verify audience
593
+ if (options.audience) {
594
+ const aud = options.audience;
595
+ const tokenAud = payload.aud;
596
+ if (Array.isArray(aud)) {
597
+ if (!Array.isArray(tokenAud) || !tokenAud.some(a => aud.includes(a))) {
598
+ throw new Error("Token audience invalid");
599
+ }
600
+ } else if (tokenAud !== aud) {
601
+ throw new Error("Token audience invalid");
602
+ }
603
+ }
604
+
605
+ // Verify issuer
606
+ if (options.issuer && payload.iss !== options.issuer) {
607
+ throw new Error("Token issuer invalid");
608
+ }
609
+
610
+ // Verify subject
611
+ if (options.subject && payload.sub !== options.subject) {
612
+ throw new Error("Token subject invalid");
613
+ }
614
+
615
+ return payload;
616
+ }
617
+
618
+ /**
619
+ * JWT Decode function - Decode a JWT token without verification
620
+ * @param {string} token - The JWT token to decode
621
+ * @param {Object} options - Decoding options
622
+ * @returns {Object} - Decoded payload
623
+ */
624
+ function jwtDecode(token, options = {}) {
625
+ const parts = token.split(".");
626
+ if (parts.length !== 3) {
627
+ throw new Error("Invalid token format");
628
+ }
629
+
630
+ const [, payloadEncoded] = parts;
631
+
632
+ try {
633
+ const payload = JSON.parse(base64UrlDecode(payloadEncoded));
634
+ return options.complete ? {
635
+ header: JSON.parse(base64UrlDecode(parts)),
636
+ payload,
637
+ signature: parts
638
+ } : payload;
639
+ } catch (e) {
640
+ throw new Error("Invalid token payload");
641
+ }
642
+ }
643
+
644
+ // --- Create JWT middleware for AetherJS ---
645
+ /**
646
+ * Create JWT middleware for AetherJS with multiple algorithm support
647
+ * @param {Object} options - JWT configuration options
648
+ * @returns {Function} - JWT middleware function
649
+ */
650
+ function createJwtMiddleware(options = {}) {
651
+ // Load configuration from environment variables
652
+ const envConfig = {
653
+ enabled: process.env.JWT_ENABLED,
654
+ secret: process.env.JWT_SECRET,
655
+ privateKey: process.env.JWT_PRIVATE_KEY,
656
+ publicKey: process.env.JWT_PUBLIC_KEY,
657
+ algorithms: process.env.JWT_ALGORITHMS,
658
+ audience: process.env.JWT_AUDIENCE,
659
+ issuer: process.env.JWT_ISSUER,
660
+ expiresIn: process.env.JWT_EXPIRES_IN,
661
+ ignoreExpiration: process.env.JWT_IGNORE_EXPIRATION,
662
+ credentialsRequired: process.env.JWT_CREDENTIALS_REQUIRED,
663
+ tokenHeader: process.env.JWT_TOKEN_HEADER,
664
+ tokenQuery: process.env.JWT_TOKEN_QUERY,
665
+ tokenCookie: process.env.JWT_TOKEN_COOKIE,
666
+ };
667
+
668
+ // Default configuration
669
+ const defaults = {
670
+ enabled: envConfig.enabled !== "false",
671
+ secret: envConfig.secret || "your-super-secret-jwt-key-change-in-production",
672
+ privateKey: envConfig.privateKey,
673
+ publicKey: envConfig.publicKey,
674
+ algorithms: parseAlgorithms(envConfig.algorithms),
675
+ algorithm: envConfig.algorithms
676
+ ? parseAlgorithms(envConfig.algorithms)
677
+ : "HS256",
678
+ audience: envConfig.audience,
679
+ issuer: envConfig.issuer,
680
+ expiresIn: envConfig.expiresIn || "7d",
681
+ ignoreExpiration: envConfig.ignoreExpiration === "true",
682
+ credentialsRequired: envConfig.credentialsRequired !== "false",
683
+ tokenHeader: envConfig.tokenHeader || "authorization",
684
+ tokenQuery: envConfig.tokenQuery || "token",
685
+ tokenCookie: envConfig.tokenCookie || "token",
686
+
687
+ // Token extraction methods
688
+ extractors: [
689
+ (context) => {
690
+ const header = context.getHeader(defaults.tokenHeader);
691
+ if (header && header.startsWith("Bearer ")) {
692
+ return header.substring(7);
693
+ }
694
+ return null;
695
+ },
696
+ (context) => {
697
+ return context.query[defaults.tokenQuery] || null;
698
+ },
699
+ (context) => {
700
+ const cookies = parseCookies(context.getHeader("cookie") || "");
701
+ return cookies[defaults.tokenCookie] || null;
702
+ },
703
+ ],
704
+
705
+ validationOptions: {
706
+ algorithms: parseAlgorithms(envConfig.algorithms),
707
+ audience: envConfig.audience,
708
+ issuer: envConfig.issuer,
709
+ ignoreExpiration: envConfig.ignoreExpiration === "true",
710
+ },
711
+
712
+ onError: (context, error) => {
713
+ context.setStatus(401).json({
714
+ error: "Unauthorized",
715
+ message: error.message,
716
+ });
717
+ },
718
+
719
+ onMissing: (context) => {
720
+ context.setStatus(401).json({
721
+ error: "Unauthorized",
722
+ message: "No token provided",
723
+ });
724
+ },
725
+ };
726
+
727
+ const config = { ...defaults, ...options };
728
+
729
+ function parseCookies(cookieHeader) {
730
+ const cookies = {};
731
+ if (!cookieHeader) return cookies;
732
+
733
+ const pairs = cookieHeader.split(";");
734
+ for (let i = 0; i < pairs.length; i++) {
735
+ const eqIdx = pairs[i].indexOf("=");
736
+ if (eqIdx === -1) continue;
737
+ const key = pairs[i].substring(0, eqIdx).trim();
738
+ const value = pairs[i].substring(eqIdx + 1).trim();
739
+ // Only keep the first cookie of each name to prevent overwrite attacks
740
+ if (!cookies[key]) {
741
+ cookies[key] = value;
742
+ }
743
+ }
744
+ return cookies;
745
+ }
746
+
747
+ function extractToken(context) {
748
+ for (let i = 0; i < config.extractors.length; i++) {
749
+ const token = config.extractors[i](context);
750
+ if (token) return token;
751
+ }
752
+ return null;
753
+ }
754
+
755
+ /**
756
+ * Get appropriate key for verification based on algorithm
757
+ * @param {string} algorithm - JWT algorithm
758
+ * @returns {string|Object} Key for verification
759
+ */
760
+ function getVerificationKey(algorithm) {
761
+ const algo = ALGORITHMS[algorithm];
762
+ if (!algo) {
763
+ throw new Error(`Unsupported algorithm: ${algorithm}`);
764
+ }
765
+
766
+ if (algo.type === "symmetric") {
767
+ return config.secret;
768
+ } else if (algo.type === "asymmetric") {
769
+ return config.publicKey || config.secret;
770
+ }
771
+ return config.secret;
772
+ }
773
+
774
+ /**
775
+ * Get appropriate key for signing based on algorithm
776
+ * @param {string} algorithm - JWT algorithm
777
+ * @returns {string|Object} Key for signing
778
+ */
779
+ function getSigningKey(algorithm) {
780
+ const algo = ALGORITHMS[algorithm];
781
+ if (!algo) {
782
+ throw new Error(`Unsupported algorithm: ${algorithm}`);
783
+ }
784
+
785
+ if (algo.type === "symmetric") {
786
+ return config.secret;
787
+ } else if (algo.type === "asymmetric") {
788
+ return config.privateKey || config.secret;
789
+ }
790
+ return config.secret;
791
+ }
792
+
793
+ /**
794
+ * 💡 Ultimate optimization: Remove async Promise, change to synchronous verification, eliminate thread pool queuing block
795
+ */
796
+ async function verifyTokenSync(token) {
797
+ // Decode header first to get algorithm
798
+ const parts = token.split(".");
799
+ if (parts.length !== 3) {
800
+ throw new Error("Invalid token format");
801
+ }
802
+
803
+ const header = JSON.parse(base64UrlDecode(parts));
804
+ const algorithm = header.alg;
805
+ const key = getVerificationKey(algorithm);
806
+
807
+ return jwtVerify(token, key, config.validationOptions);
808
+ }
809
+
810
+ /**
811
+ * Signing is usually not a high-frequency hotspot, can keep async or sync. Maintain async here for backward compatibility.
812
+ */
813
+ async function signToken(payload, signOptions = {}) {
814
+ const algorithm = signOptions.algorithm || config.algorithm || "HS256";
815
+ const key = getSigningKey(algorithm);
816
+ const options = {
817
+ algorithm: algorithm,
818
+ expiresIn: config.expiresIn,
819
+ audience: config.audience,
820
+ issuer: config.issuer,
821
+ ...signOptions,
822
+ };
823
+
824
+ return jwtSign(payload, key, options);
825
+ }
826
+
827
+ return async function jwtMiddleware(context, next) {
828
+ if (!config.enabled) {
829
+ return typeof next === "function" ? next() : null;
830
+ }
831
+
832
+ const token = extractToken(context);
833
+
834
+ if (!token) {
835
+ if (config.credentialsRequired) {
836
+ return config.onMissing(context);
837
+ }
838
+ return typeof next === "function" ? next() : null;
839
+ }
840
+
841
+ try {
842
+ // 💡 Synchronous execution, no wait delay
843
+ const decoded = await verifyTokenSync(token);
844
+
845
+ context.setState("jwt", decoded);
846
+ context.setState("user", decoded);
847
+ context.setState("token", token);
848
+
849
+ context.jwt = {
850
+ payload: decoded,
851
+ token: token,
852
+ refresh: async (newPayload = {}) => {
853
+ const payload = { ...decoded, ...newPayload };
854
+ const newToken = await signToken(payload);
855
+ context.setState("jwt", payload);
856
+ context.setState("token", newToken);
857
+ return newToken;
858
+ },
859
+ verify: async () => {
860
+ return verifyTokenSync(token);
861
+ },
862
+ expiresAt: () => (decoded.exp ? new Date(decoded.exp * 1000) : null),
863
+ isExpired: () =>
864
+ decoded.exp ? Date.now() >= decoded.exp * 1000 : false,
865
+ issuedAt: () => (decoded.iat ? new Date(decoded.iat * 1000) : null),
866
+ };
867
+
868
+ if (typeof next === "function") {
869
+ await next();
870
+ }
871
+ } catch (error) {
872
+ return config.onError(context, error);
873
+ }
874
+ };
875
+ }
876
+
877
+ // Static methods: Decoupled from runtime instance, unified static security configuration
878
+ createJwtMiddleware.sign = async function (payload, options = {}) {
879
+ const algorithm = options.algorithm || "HS256";
880
+ const algo = ALGORITHMS[algorithm];
881
+
882
+ let key;
883
+ if (algo.type === "symmetric") {
884
+ key = process.env.JWT_SECRET || "your-super-secret-jwt-key-change-in-production";
885
+ } else if (algo.type === "asymmetric") {
886
+ key = process.env.JWT_PRIVATE_KEY || process.env.JWT_SECRET;
887
+ } else {
888
+ key = process.env.JWT_SECRET || "your-super-secret-jwt-key-change-in-production";
889
+ }
890
+
891
+ const expiresIn = options.expiresIn || "7d";
892
+
893
+ return jwtSign(payload, key, { algorithm, expiresIn, ...options });
894
+ };
895
+
896
+ createJwtMiddleware.verify = async function (token, options = {}) {
897
+ // Decode header to get algorithm
898
+ const parts = token.split(".");
899
+ if (parts.length !== 3) {
900
+ throw new Error("Invalid token format");
901
+ }
902
+
903
+ const header = JSON.parse(base64UrlDecode(parts));
904
+ const algorithm = header.alg;
905
+ const algo = ALGORITHMS[algorithm];
906
+
907
+ let key;
908
+ if (algo.type === "symmetric") {
909
+ key = process.env.JWT_SECRET || "your-super-secret-jwt-key-change-in-production";
910
+ } else if (algo.type === "asymmetric") {
911
+ key = process.env.JWT_PUBLIC_KEY || process.env.JWT_SECRET;
912
+ } else {
913
+ key = process.env.JWT_SECRET || "your-super-secret-jwt-key-change-in-production";
914
+ }
915
+
916
+ const algorithms = options.algorithms || [algorithm];
917
+ const verifyOptions = { algorithms, ...options };
918
+
919
+ return jwtVerify(token, key, verifyOptions);
920
+ };
921
+
922
+ createJwtMiddleware.decode = function (token, options = {}) {
923
+ return jwtDecode(token, options);
924
+ };
925
+
926
+ // Export algorithm registry for external use
927
+ createJwtMiddleware.ALGORITHMS = ALGORITHMS;
928
+
929
+ export default createJwtMiddleware;