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/libs/jwt.js +97 -0
- package/libs/xml.js +357 -0
- package/package.json +5 -3
- package/plugins/db.js +441 -23
- package/utils/curd.js +419 -0
- package/utils/jwt.js +297 -49
- package/libs/xml/DocTypeReader.js +0 -362
- package/libs/xml/OptionsBuilder.js +0 -45
- package/libs/xml/OrderedObjParser.js +0 -597
- package/libs/xml/XMLParser.js +0 -69
- package/libs/xml/ignoreAttributes.js +0 -18
- package/libs/xml/node2json.js +0 -114
- package/libs/xml/strnum.js +0 -136
- package/libs/xml/util.js +0 -68
- package/libs/xml/validator.js +0 -400
- package/libs/xml/xmlNode.js +0 -40
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
|
-
*
|
|
10
|
+
* 签名JWT token
|
|
11
|
+
* @param {object} payload - JWT载荷数据
|
|
12
|
+
* @param {object} options - 签名选项
|
|
13
|
+
* @returns {string} JWT token字符串
|
|
13
14
|
*/
|
|
14
|
-
static
|
|
15
|
-
if (
|
|
15
|
+
static sign(payload, options = {}) {
|
|
16
|
+
if (!payload || typeof payload !== 'object') {
|
|
17
|
+
throw new Error('载荷必须是非空对象');
|
|
18
|
+
}
|
|
16
19
|
|
|
17
|
-
const
|
|
20
|
+
const secret = options.secret || Env.JWT_SECRET;
|
|
21
|
+
const algorithm = options.algorithm || Env.JWT_ALGORITHM || 'HS256';
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
});
|
|
23
|
+
if (!secret) {
|
|
24
|
+
throw new Error('JWT密钥是必需的');
|
|
25
|
+
}
|
|
26
|
+
const now = Math.floor(Date.now() / 1000);
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
// 创建header
|
|
29
|
+
const header = JwtBase.base64UrlEncode(
|
|
30
|
+
JSON.stringify({
|
|
31
|
+
alg: algorithm,
|
|
32
|
+
typ: 'JWT'
|
|
33
|
+
})
|
|
34
|
+
);
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
// 创建payload
|
|
37
|
+
const jwtPayload = { ...payload, iat: now };
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
* 验证
|
|
45
|
-
* @param {string} token - JWT token
|
|
46
|
-
* @
|
|
61
|
+
* 验证JWT token
|
|
62
|
+
* @param {string} token - JWT token字符串
|
|
63
|
+
* @param {object} options - 验证选项
|
|
64
|
+
* @returns {object} 解码后的载荷数据
|
|
47
65
|
*/
|
|
48
|
-
static
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
* 解码
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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('
|
|
155
|
+
throw new Error('JWT解码失败: ' + error.message);
|
|
71
156
|
}
|
|
72
157
|
}
|
|
73
158
|
|
|
74
159
|
/**
|
|
75
|
-
* 获取
|
|
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
|
|
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
|
}
|