befly 1.2.10 → 2.0.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/utils/jwt.js CHANGED
@@ -1,84 +1,169 @@
1
+ import { Jwt as JwtBase } from '../libs/jwt.js';
1
2
  import { Env } from '../config/env.js';
2
3
 
3
4
  /**
4
5
  * JWT 工具类
6
+ * 提供JWT token的签名、验证和解码功能以及应用层的便捷接口
5
7
  */
6
8
  export class Jwt {
7
- static signer = null;
8
- static verifier = null;
9
- static initialized = false;
10
-
11
9
  /**
12
- * 初始化 JWT 工具
10
+ * 签名JWT token
11
+ * @param {object} payload - JWT载荷数据
12
+ * @param {object} options - 签名选项
13
+ * @returns {string} JWT token字符串
13
14
  */
14
- static async init() {
15
- if (this.initialized) return;
15
+ static sign(payload, options = {}) {
16
+ if (!payload || typeof payload !== 'object') {
17
+ throw new Error('载荷必须是非空对象');
18
+ }
16
19
 
17
- const { createSigner, createVerifier } = await import('fast-jwt');
20
+ const secret = options.secret || Env.JWT_SECRET;
21
+ const algorithm = options.algorithm || Env.JWT_ALGORITHM || 'HS256';
18
22
 
19
- this.signer = createSigner({
20
- key: Env.JWT_SECRET,
21
- expiresIn: Env.JWT_EXPIRES_IN || '7d',
22
- algorithm: Env.JWT_ALGORITHM || 'HS256'
23
- });
23
+ if (!secret) {
24
+ throw new Error('JWT密钥是必需的');
25
+ }
26
+ const now = Math.floor(Date.now() / 1000);
24
27
 
25
- this.verifier = createVerifier({
26
- key: Env.JWT_SECRET,
27
- algorithms: [Env.JWT_ALGORITHM || 'HS256']
28
- });
28
+ // 创建header
29
+ const header = JwtBase.base64UrlEncode(
30
+ JSON.stringify({
31
+ alg: algorithm,
32
+ typ: 'JWT'
33
+ })
34
+ );
29
35
 
30
- this.initialized = true;
31
- }
36
+ // 创建payload
37
+ const jwtPayload = { ...payload, iat: now };
32
38
 
33
- /**
34
- * 签名 JWT token
35
- * @param {object} payload - JWT 载荷
36
- * @returns {Promise<string>} JWT token
37
- */
38
- static async sign(payload) {
39
- await this.init();
40
- return this.signer(payload);
39
+ if (options.expiresIn || Env.JWT_EXPIRES_IN) {
40
+ const expSeconds = JwtBase.parseExpiration(options.expiresIn || Env.JWT_EXPIRES_IN);
41
+ jwtPayload.exp = now + expSeconds;
42
+ }
43
+ if (options.issuer) jwtPayload.iss = options.issuer;
44
+ if (options.audience) jwtPayload.aud = options.audience;
45
+ if (options.subject) jwtPayload.sub = options.subject;
46
+ if (options.notBefore) {
47
+ jwtPayload.nbf = typeof options.notBefore === 'number' ? options.notBefore : now + JwtBase.parseExpiration(options.notBefore);
48
+ }
49
+ if (options.jwtId) jwtPayload.jti = options.jwtId;
50
+
51
+ const encodedPayload = JwtBase.base64UrlEncode(JSON.stringify(jwtPayload));
52
+
53
+ // 创建签名
54
+ const data = `${header}.${encodedPayload}`;
55
+ const signature = JwtBase.createSignature(algorithm, secret, data);
56
+
57
+ return `${data}.${signature}`;
41
58
  }
42
59
 
43
60
  /**
44
- * 验证 JWT token
45
- * @param {string} token - JWT token
46
- * @returns {Promise<object>} 解码后的载荷
61
+ * 验证JWT token
62
+ * @param {string} token - JWT token字符串
63
+ * @param {object} options - 验证选项
64
+ * @returns {object} 解码后的载荷数据
47
65
  */
48
- static async verify(token) {
49
- await this.init();
50
- return this.verifier(token);
66
+ static verify(token, options = {}) {
67
+ if (!token || typeof token !== 'string') {
68
+ throw new Error('Token必须是非空字符串');
69
+ }
70
+
71
+ const secret = options.secret || Env.JWT_SECRET;
72
+ if (!secret) {
73
+ throw new Error('JWT密钥是必需的');
74
+ }
75
+
76
+ const parts = token.split('.');
77
+ if (parts.length !== 3) {
78
+ throw new Error('JWT格式无效');
79
+ }
80
+
81
+ try {
82
+ // 解析header和payload
83
+ const header = JSON.parse(JwtBase.base64UrlDecode(parts[0]));
84
+ const payload = JSON.parse(JwtBase.base64UrlDecode(parts[1]));
85
+ const signature = parts[2];
86
+
87
+ // 验证算法
88
+ if (!JwtBase.ALGORITHMS[header.alg]) {
89
+ throw new Error(`不支持的算法: ${header.alg}`);
90
+ }
91
+
92
+ // 验证签名
93
+ const data = `${parts[0]}.${parts[1]}`;
94
+ const expectedSignature = JwtBase.createSignature(header.alg, secret, data);
95
+
96
+ if (!JwtBase.constantTimeCompare(signature, expectedSignature)) {
97
+ throw new Error('Token签名无效');
98
+ }
99
+
100
+ // 验证时间
101
+ const now = Math.floor(Date.now() / 1000);
102
+
103
+ if (!options.ignoreExpiration && payload.exp && payload.exp < now) {
104
+ throw new Error('Token已过期');
105
+ }
106
+ if (!options.ignoreNotBefore && payload.nbf && payload.nbf > now) {
107
+ throw new Error('Token尚未生效');
108
+ }
109
+
110
+ // 验证issuer、audience、subject
111
+ if (options.issuer && payload.iss !== options.issuer) {
112
+ throw new Error('Token发行者无效');
113
+ }
114
+ if (options.audience) {
115
+ const audiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
116
+ if (!audiences.includes(options.audience)) {
117
+ throw new Error('Token受众无效');
118
+ }
119
+ }
120
+ if (options.subject && payload.sub !== options.subject) {
121
+ throw new Error('Token主题无效');
122
+ }
123
+
124
+ return payload;
125
+ } catch (error) {
126
+ if (error.message.includes('JWT') || error.message.includes('Token') || error.message.includes('无效') || error.message.includes('过期') || error.message.includes('不支持')) {
127
+ throw error;
128
+ }
129
+ throw new Error('Token验证失败: ' + error.message);
130
+ }
51
131
  }
52
132
 
53
133
  /**
54
- * 解码 JWT token (不验证签名)
55
- * @param {string} token - JWT token
134
+ * 解码JWT token (不验证签名)
135
+ * @param {string} token - JWT token字符串
136
+ * @param {boolean} complete - 是否返回完整信息(包含header)
56
137
  * @returns {object} 解码后的内容
57
138
  */
58
- static decode(token) {
59
- try {
60
- const parts = token.split('.');
61
- if (parts.length !== 3) {
62
- throw new Error('Invalid JWT format');
63
- }
139
+ static decode(token, complete = false) {
140
+ if (!token || typeof token !== 'string') {
141
+ throw new Error('Token必须是非空字符串');
142
+ }
64
143
 
65
- const header = JSON.parse(atob(parts[0]));
66
- const payload = JSON.parse(atob(parts[1]));
144
+ const parts = token.split('.');
145
+ if (parts.length !== 3) {
146
+ throw new Error('JWT格式无效');
147
+ }
148
+
149
+ try {
150
+ const header = JSON.parse(JwtBase.base64UrlDecode(parts[0]));
151
+ const payload = JSON.parse(JwtBase.base64UrlDecode(parts[1]));
67
152
 
68
- return { header, payload };
153
+ return complete ? { header, payload, signature: parts[2] } : payload;
69
154
  } catch (error) {
70
- throw new Error('Failed to decode JWT: ' + error.message);
155
+ throw new Error('JWT解码失败: ' + error.message);
71
156
  }
72
157
  }
73
158
 
74
159
  /**
75
- * 获取 token 剩余有效时间 (秒)
76
- * @param {string} token - JWT token
77
- * @returns {number} 剩余秒数,-1 表示已过期或无过期时间
160
+ * 获取token剩余有效时间
161
+ * @param {string} token - JWT token字符串
162
+ * @returns {number} 剩余秒数,-1表示已过期或无过期时间
78
163
  */
79
164
  static getTimeToExpiry(token) {
80
165
  try {
81
- const { payload } = this.decode(token);
166
+ const payload = this.decode(token);
82
167
  if (!payload.exp) return -1;
83
168
  const remaining = payload.exp - Math.floor(Date.now() / 1000);
84
169
  return remaining > 0 ? remaining : -1;
@@ -86,4 +171,167 @@ export class Jwt {
86
171
  return -1;
87
172
  }
88
173
  }
174
+
175
+ /**
176
+ * 检查token是否已过期
177
+ * @param {string} token - JWT token字符串
178
+ * @returns {boolean} 是否已过期
179
+ */
180
+ static isExpired(token) {
181
+ return this.getTimeToExpiry(token) <= 0;
182
+ }
183
+
184
+ // 以下是应用层的便捷方法
185
+
186
+ /**
187
+ * 签名用户认证token
188
+ * @param {object} userInfo - 用户信息对象
189
+ * @param {object} options - 签名选项
190
+ * @returns {string} JWT token字符串
191
+ */
192
+ static signUserToken(userInfo, options = {}) {
193
+ return this.sign(userInfo, options);
194
+ }
195
+
196
+ /**
197
+ * 签名API访问token
198
+ * @param {object} payload - 载荷数据
199
+ * @param {object} options - 签名选项
200
+ * @returns {string} API token字符串
201
+ */
202
+ static signAPIToken(payload, options = {}) {
203
+ return this.sign(payload, { audience: 'api', expiresIn: '1h', ...options });
204
+ }
205
+
206
+ /**
207
+ * 签名刷新token
208
+ * @param {object} payload - 载荷数据
209
+ * @param {object} options - 签名选项
210
+ * @returns {string} 刷新token字符串
211
+ */
212
+ static signRefreshToken(payload, options = {}) {
213
+ return this.sign(payload, { audience: 'refresh', expiresIn: '30d', ...options });
214
+ }
215
+
216
+ /**
217
+ * 签名临时token (用于重置密码等)
218
+ * @param {object} payload - 载荷数据
219
+ * @param {object} options - 签名选项
220
+ * @returns {string} 临时token字符串
221
+ */
222
+ static signTempToken(payload, options = {}) {
223
+ return this.sign(payload, { audience: 'temporary', expiresIn: '15m', ...options });
224
+ }
225
+
226
+ /**
227
+ * 验证用户认证token
228
+ * @param {string} token - JWT token字符串
229
+ * @param {object} options - 验证选项
230
+ * @returns {object} 用户信息对象
231
+ */
232
+ static verifyUserToken(token, options = {}) {
233
+ return this.verify(token, options);
234
+ }
235
+
236
+ /**
237
+ * 验证API访问token
238
+ * @param {string} token - API token字符串
239
+ * @param {object} options - 验证选项
240
+ * @returns {object} 解码后的载荷数据
241
+ */
242
+ static verifyAPIToken(token, options = {}) {
243
+ return this.verify(token, { audience: 'api', ...options });
244
+ }
245
+
246
+ /**
247
+ * 验证刷新token
248
+ * @param {string} token - 刷新token字符串
249
+ * @param {object} options - 验证选项
250
+ * @returns {object} 解码后的载荷数据
251
+ */
252
+ static verifyRefreshToken(token, options = {}) {
253
+ return this.verify(token, { audience: 'refresh', ...options });
254
+ }
255
+
256
+ /**
257
+ * 验证临时token
258
+ * @param {string} token - 临时token字符串
259
+ * @param {object} options - 验证选项
260
+ * @returns {object} 解码后的载荷数据
261
+ */
262
+ static verifyTempToken(token, options = {}) {
263
+ return this.verify(token, { audience: 'temporary', ...options });
264
+ }
265
+
266
+ /**
267
+ * 验证token并检查权限
268
+ * @param {string} token - JWT token字符串
269
+ * @param {string|Array<string>} requiredPermissions - 需要的权限列表
270
+ * @param {object} options - 验证选项
271
+ * @returns {object} 解码后的载荷数据
272
+ */
273
+ static verifyWithPermissions(token, requiredPermissions, options = {}) {
274
+ const payload = this.verify(token, options);
275
+
276
+ if (!payload.permissions) {
277
+ throw new Error('Token中不包含权限信息');
278
+ }
279
+
280
+ const permissions = Array.isArray(requiredPermissions) ? requiredPermissions : [requiredPermissions];
281
+
282
+ const hasPermission = permissions.every((permission) => payload.permissions.includes(permission));
283
+
284
+ if (!hasPermission) {
285
+ throw new Error('权限不足');
286
+ }
287
+
288
+ return payload;
289
+ }
290
+
291
+ /**
292
+ * 验证token并检查角色
293
+ * @param {string} token - JWT token字符串
294
+ * @param {string|Array<string>} requiredRoles - 需要的角色列表
295
+ * @param {object} options - 验证选项
296
+ * @returns {object} 解码后的载荷数据
297
+ */
298
+ static verifyWithRoles(token, requiredRoles, options = {}) {
299
+ const payload = this.verify(token, options);
300
+
301
+ if (!payload.role && !payload.roles) {
302
+ throw new Error('Token中不包含角色信息');
303
+ }
304
+
305
+ const userRoles = payload.roles || [payload.role];
306
+ const roles = Array.isArray(requiredRoles) ? requiredRoles : [requiredRoles];
307
+
308
+ const hasRole = roles.some((role) => userRoles.includes(role));
309
+
310
+ if (!hasRole) {
311
+ throw new Error('角色权限不足');
312
+ }
313
+
314
+ return payload;
315
+ }
316
+
317
+ /**
318
+ * 软验证token (忽略过期时间)
319
+ * @param {string} token - JWT token字符串
320
+ * @param {object} options - 验证选项
321
+ * @returns {object} 解码后的载荷数据
322
+ */
323
+ static verifySoft(token, options = {}) {
324
+ return this.verify(token, { ignoreExpiration: true, ...options });
325
+ }
326
+
327
+ /**
328
+ * 检查token是否即将过期
329
+ * @param {string} token - JWT token字符串
330
+ * @param {number} thresholdSeconds - 过期阈值(秒),默认300秒(5分钟)
331
+ * @returns {boolean} 是否即将过期
332
+ */
333
+ static isNearExpiry(token, thresholdSeconds = 300) {
334
+ const timeToExpiry = this.getTimeToExpiry(token);
335
+ return timeToExpiry > 0 && timeToExpiry <= thresholdSeconds;
336
+ }
89
337
  }