@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.
- package/.env.example +88 -0
- package/LICENSE +21 -0
- package/README.md +693 -0
- package/docs/readme/README.md +679 -0
- package/docs/readme/README_zh.md +680 -0
- package/examples/advanced-router-demo.js +119 -0
- package/examples/advanced-server.js +272 -0
- package/examples/basic-server.js +134 -0
- package/examples/benchmark.js +85 -0
- package/examples/router-demo.js +369 -0
- package/index.js +67 -0
- package/package.json +59 -0
- package/src/core/AetherCompiler.js +118 -0
- package/src/core/AetherContext.js +242 -0
- package/src/core/AetherPipeline.js +375 -0
- package/src/core/AetherRouter.js +347 -0
- package/src/core/AetherStore.js +204 -0
- package/src/middleware/body-parser.js +299 -0
- package/src/middleware/compression.js +248 -0
- package/src/middleware/cors.js +162 -0
- package/src/middleware/json.js +214 -0
- package/src/middleware/jwt.js +929 -0
- package/src/middleware/params.js +227 -0
- package/src/middleware/rate-limit.js +167 -0
- package/src/middleware/router.js +36 -0
- package/src/middleware/security.js +116 -0
- package/src/middleware/session.js +167 -0
- package/src/utils/atomic-ops.js +127 -0
- package/src/utils/env-loader.js +128 -0
- package/src/utils/memory-pool.js +93 -0
|
@@ -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;
|