befly 3.8.27 → 3.8.30

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.
Files changed (65) hide show
  1. package/README.md +8 -6
  2. package/checks/checkApi.ts +2 -1
  3. package/checks/checkTable.ts +3 -2
  4. package/hooks/parser.ts +5 -3
  5. package/hooks/permission.ts +12 -5
  6. package/lib/cacheHelper.ts +76 -62
  7. package/lib/connect.ts +8 -35
  8. package/lib/dbHelper.ts +14 -11
  9. package/lib/jwt.ts +58 -437
  10. package/lib/logger.ts +76 -197
  11. package/lib/redisHelper.ts +163 -1
  12. package/lib/sqlBuilder.ts +2 -1
  13. package/lib/validator.ts +9 -8
  14. package/loader/loadApis.ts +4 -7
  15. package/loader/loadHooks.ts +2 -2
  16. package/loader/loadPlugins.ts +4 -4
  17. package/main.ts +4 -17
  18. package/package.json +10 -9
  19. package/paths.ts +0 -6
  20. package/plugins/db.ts +2 -2
  21. package/plugins/jwt.ts +5 -5
  22. package/plugins/redis.ts +1 -1
  23. package/router/api.ts +2 -2
  24. package/router/static.ts +1 -2
  25. package/sync/syncAll.ts +2 -2
  26. package/sync/syncApi.ts +10 -7
  27. package/sync/syncDb/apply.ts +11 -11
  28. package/sync/syncDb/constants.ts +61 -12
  29. package/sync/syncDb/ddl.ts +7 -7
  30. package/sync/syncDb/helpers.ts +3 -3
  31. package/sync/syncDb/schema.ts +16 -19
  32. package/sync/syncDb/table.ts +6 -5
  33. package/sync/syncDb/tableCreate.ts +7 -7
  34. package/sync/syncDb/types.ts +3 -2
  35. package/sync/syncDb/version.ts +4 -4
  36. package/sync/syncDb.ts +11 -10
  37. package/sync/syncDev.ts +10 -48
  38. package/sync/syncMenu.ts +11 -8
  39. package/tests/cacheHelper.test.ts +327 -0
  40. package/tests/dbHelper-columns.test.ts +5 -20
  41. package/tests/dbHelper-execute.test.ts +14 -68
  42. package/tests/fields-redis-cache.test.ts +5 -3
  43. package/tests/integration.test.ts +15 -26
  44. package/tests/jwt.test.ts +36 -94
  45. package/tests/logger.test.ts +32 -34
  46. package/tests/redisHelper.test.ts +270 -0
  47. package/tests/redisKeys.test.ts +76 -0
  48. package/tests/sync-connection.test.ts +0 -6
  49. package/tests/syncDb-apply.test.ts +3 -2
  50. package/tests/syncDb-constants.test.ts +15 -14
  51. package/tests/syncDb-ddl.test.ts +3 -2
  52. package/tests/syncDb-helpers.test.ts +3 -2
  53. package/tests/syncDb-schema.test.ts +3 -3
  54. package/tests/syncDb-types.test.ts +3 -2
  55. package/tests/util.test.ts +5 -1
  56. package/types/befly.d.ts +2 -15
  57. package/types/common.d.ts +11 -93
  58. package/types/database.d.ts +216 -5
  59. package/types/index.ts +1 -0
  60. package/types/logger.d.ts +11 -41
  61. package/types/table.d.ts +213 -0
  62. package/hooks/_rateLimit.ts +0 -64
  63. package/lib/regexAliases.ts +0 -59
  64. package/lib/xml.ts +0 -383
  65. package/tests/xml.test.ts +0 -101
package/lib/jwt.ts CHANGED
@@ -1,457 +1,78 @@
1
- /**
2
- * JWT 工具类 - Befly 项目专用
3
- * 直接集成环境变量,提供开箱即用的 JWT 功能
4
- */
5
-
6
- import { createHmac } from 'crypto';
7
- import type { JwtPayload, JwtSignOptions, JwtVerifyOptions, JwtAlgorithm, JwtHeader, JwtDecoded } from '../types/jwt';
8
- import type { AuthConfig } from '../types/befly';
9
-
10
1
  /**
11
- * JWT 工具类
2
+ * JWT 工具类 - 基于 fast-jwt 实现
12
3
  */
13
- export class Jwt {
14
- /** 默认配置 */
15
- private static config: AuthConfig = {
16
- secret: 'befly-secret',
17
- expiresIn: '7d',
18
- algorithm: 'HS256'
19
- };
20
-
21
- /**
22
- * 配置 JWT
23
- * @param config - JWT 配置
24
- */
25
- static configure(config: AuthConfig) {
26
- this.config = { ...this.config, ...config };
27
- }
28
-
29
- /** 算法映射 */
30
- private static readonly ALGORITHMS: Record<JwtAlgorithm, string> = {
31
- HS256: 'sha256',
32
- HS384: 'sha384',
33
- HS512: 'sha512'
34
- };
35
4
 
36
- /**
37
- * Base64 URL 编码
38
- */
39
- static base64UrlEncode(input: string | Buffer): string {
40
- const base64 = Buffer.isBuffer(input) ? input.toString('base64') : Buffer.from(input, 'utf8').toString('base64');
41
- return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
42
- }
43
-
44
- /**
45
- * Base64 URL 解码
46
- */
47
- static base64UrlDecode(str: string): string {
48
- let padding = 4 - (str.length % 4);
49
- if (padding !== 4) str += '='.repeat(padding);
50
- str = str.replace(/-/g, '+').replace(/_/g, '/');
51
- return Buffer.from(str, 'base64').toString('utf8');
52
- }
53
-
54
- /**
55
- * 解析过期时间
56
- * @param expiresIn - 过期时间(数字秒数或字符串如 "1h", "7d", "-1s")
57
- * @returns 秒数(可以是负数)
58
- */
59
- static parseExpiration(expiresIn: string | number): number {
60
- if (typeof expiresIn === 'number') return expiresIn;
61
- if (typeof expiresIn !== 'string') throw new Error('过期时间格式无效');
5
+ import { createSigner, createVerifier, createDecoder } from 'fast-jwt';
62
6
 
63
- const numericValue = parseInt(expiresIn);
64
- if (!isNaN(numericValue) && numericValue.toString() === expiresIn) return numericValue;
7
+ import type { AuthConfig } from '../types/befly.js';
8
+ import type { JwtPayload, JwtSignOptions, JwtVerifyOptions, JwtDecoded, JwtHeader } from '../types/jwt.js';
65
9
 
66
- // 支持负数时间(用于测试过期token)
67
- const match = expiresIn.match(/^(-?\d+)(ms|[smhdwy])$/);
68
- if (!match) throw new Error('过期时间格式无效');
69
-
70
- const value = parseInt(match[1]); // 包含正负号
71
- const unit = match[2];
10
+ interface FastJwtComplete {
11
+ header: JwtHeader;
12
+ payload: JwtPayload;
13
+ signature: string;
14
+ input: string;
15
+ }
72
16
 
73
- if (unit === 'ms') return Math.floor(value / 1000);
17
+ export class Jwt {
18
+ private config: AuthConfig;
74
19
 
75
- const multipliers: Record<string, number> = {
76
- s: 1,
77
- m: 60,
78
- h: 3600,
79
- d: 86400,
80
- w: 604800,
81
- y: 31536000
20
+ constructor(config: AuthConfig = {}) {
21
+ this.config = {
22
+ secret: config.secret || 'befly-secret',
23
+ expiresIn: config.expiresIn || '7d',
24
+ algorithm: config.algorithm || 'HS256'
82
25
  };
83
-
84
- return value * multipliers[unit];
85
- }
86
-
87
- /**
88
- * 创建签名
89
- */
90
- private static createSignature(algorithm: JwtAlgorithm, secret: string, data: string): string {
91
- const hashAlgorithm = this.ALGORITHMS[algorithm];
92
- if (!hashAlgorithm) throw new Error(`不支持的算法: ${algorithm}`);
93
-
94
- const hmac = createHmac(hashAlgorithm, secret);
95
- hmac.update(data);
96
- return this.base64UrlEncode(hmac.digest());
97
- }
98
-
99
- /**
100
- * 常量时间比较(防止时序攻击)
101
- */
102
- private static constantTimeCompare(a: string, b: string): boolean {
103
- if (a.length !== b.length) return false;
104
- let result = 0;
105
- for (let i = 0; i < a.length; i++) {
106
- result |= a.charCodeAt(i) ^ b.charCodeAt(i);
107
- }
108
- return result === 0;
109
26
  }
110
27
 
111
- /**
112
- * 签名生成 Token
113
- * @param payload - 数据载荷
114
- * @param options - 签名选项
115
- * @returns Token 字符串
116
- */
117
- static async sign(payload: JwtPayload, options: JwtSignOptions = {}): Promise<string> {
118
- const secret = options.secret || this.config.secret || 'befly-secret';
119
- const expiresIn = options.expiresIn || this.config.expiresIn || '7d';
120
- const algorithm = (options.algorithm || this.config.algorithm || 'HS256') as JwtAlgorithm;
121
-
122
- const now = Math.floor(Date.now() / 1000);
123
-
124
- // 创建 header
125
- const header = Jwt.base64UrlEncode(
126
- JSON.stringify({
127
- alg: algorithm,
128
- typ: 'JWT'
129
- })
130
- );
131
-
132
- // 创建 payload
133
- const jwtPayload: JwtPayload = { ...payload, iat: now };
134
-
135
- if (expiresIn) {
136
- const expSeconds = Jwt.parseExpiration(expiresIn);
137
- jwtPayload.exp = now + expSeconds;
138
- }
139
-
140
- if (options.issuer) jwtPayload.iss = options.issuer;
141
- if (options.audience) jwtPayload.aud = options.audience;
142
- if (options.subject) jwtPayload.sub = options.subject;
143
- if (options.notBefore) {
144
- jwtPayload.nbf = typeof options.notBefore === 'number' ? options.notBefore : now + Jwt.parseExpiration(options.notBefore);
145
- }
146
- if (options.jwtId) jwtPayload.jti = options.jwtId;
147
-
148
- const encodedPayload = Jwt.base64UrlEncode(JSON.stringify(jwtPayload));
149
-
150
- // 创建签名
151
- const data = `${header}.${encodedPayload}`;
152
- const signature = Jwt.createSignature(algorithm, secret, data);
153
-
154
- return `${data}.${signature}`;
28
+ sign(payload: JwtPayload, options: JwtSignOptions = {}): string {
29
+ const signer = createSigner({
30
+ key: options.secret || this.config.secret,
31
+ algorithm: options.algorithm || this.config.algorithm,
32
+ expiresIn: options.expiresIn || this.config.expiresIn,
33
+ iss: options.issuer,
34
+ aud: options.audience,
35
+ sub: options.subject,
36
+ jti: options.jwtId,
37
+ notBefore: options.notBefore
38
+ });
39
+ return signer(payload);
155
40
  }
156
41
 
157
- /**
158
- * 验证 Token
159
- * @param token - Token 字符串
160
- * @param options - 验证选项
161
- * @returns 解码后的载荷
162
- */
163
- static async verify<T = JwtPayload>(token: string, options: JwtVerifyOptions = {}): Promise<T> {
42
+ verify<T = JwtPayload>(token: string, options: JwtVerifyOptions = {}): T {
164
43
  if (!token || typeof token !== 'string') {
165
44
  throw new Error('Token必须是非空字符串');
166
45
  }
167
-
168
- const secret = options.secret || this.config.secret || 'befly-secret';
169
- const algorithm = (options.algorithm || this.config.algorithm || 'HS256') as JwtAlgorithm;
170
-
171
- const parts = token.split('.');
172
- if (parts.length !== 3) {
173
- throw new Error('Token 格式无效');
174
- }
175
-
176
- const [headerB64, payloadB64, signature] = parts;
177
-
178
- // 验证签名
179
- const data = `${headerB64}.${payloadB64}`;
180
- const expectedSignature = Jwt.createSignature(algorithm, secret, data);
181
-
182
- if (!Jwt.constantTimeCompare(signature, expectedSignature)) {
183
- throw new Error('Token 签名无效');
184
- }
185
-
186
- // 解码 payload
187
- const payloadStr = Jwt.base64UrlDecode(payloadB64);
188
- let payload: JwtPayload;
189
- try {
190
- payload = JSON.parse(payloadStr);
191
- } catch (e) {
192
- throw new Error('Token 载荷无效');
193
- }
194
-
195
- // 验证过期时间
196
- if (!options.ignoreExpiration) {
197
- const now = Math.floor(Date.now() / 1000);
198
- if (payload.exp && payload.exp < now) {
199
- throw new Error('Token 已过期');
200
- }
201
- }
202
-
203
- // 验证其他声明
204
- if (options.issuer && payload.iss !== options.issuer) {
205
- throw new Error('Token issuer 无效');
206
- }
207
- if (options.audience && payload.aud !== options.audience) {
208
- throw new Error('Token audience 无效');
209
- }
210
- if (options.subject && payload.sub !== options.subject) {
211
- throw new Error('Token subject 无效');
212
- }
213
- if (options.jwtId && payload.jti !== options.jwtId) {
214
- throw new Error('Token jwtId 无效');
215
- }
216
-
217
- return payload as T;
218
- }
219
-
220
- /**
221
- * 解码 JWT token (不验证签名)
222
- * @param token - JWT token字符串
223
- * @param complete - 是否返回完整信息(包含header)
224
- * @returns 解码后的内容
225
- */
226
- static decode(token: string, complete?: false): JwtPayload;
227
- static decode(token: string, complete: true): JwtDecoded;
228
- static decode(token: string, complete: boolean = false): JwtPayload | JwtDecoded {
46
+ const verifier = createVerifier({
47
+ key: options.secret || this.config.secret,
48
+ algorithms: options.algorithms || [this.config.algorithm || 'HS256'],
49
+ allowedIss: options.issuer,
50
+ allowedAud: options.audience,
51
+ allowedSub: options.subject,
52
+ ignoreExpiration: options.ignoreExpiration,
53
+ ignoreNotBefore: options.ignoreNotBefore,
54
+ cache: true,
55
+ cacheTTL: 600000
56
+ });
57
+ return verifier(token) as T;
58
+ }
59
+
60
+ decode(token: string, complete?: false): JwtPayload;
61
+ decode(token: string, complete: true): JwtDecoded;
62
+ decode(token: string, complete: boolean = false): JwtPayload | JwtDecoded {
229
63
  if (!token || typeof token !== 'string') {
230
64
  throw new Error('Token必须是非空字符串');
231
65
  }
232
-
233
- const parts = token.split('.');
234
- if (parts.length !== 3) {
235
- throw new Error('JWT格式无效');
236
- }
237
-
238
- try {
239
- const header = JSON.parse(Jwt.base64UrlDecode(parts[0])) as JwtHeader;
240
- const payload = JSON.parse(Jwt.base64UrlDecode(parts[1])) as JwtPayload;
241
-
242
- return complete ? { header, payload, signature: parts[2] } : payload;
243
- } catch (error: any) {
244
- throw new Error('JWT解码失败: ' + error.message);
245
- }
246
- }
247
-
248
- /**
249
- * 获取 token 剩余有效时间
250
- * @param token - JWT token字符串
251
- * @returns 剩余秒数,-1表示已过期或无过期时间
252
- */
253
- static getTimeToExpiry(token: string): number {
254
- try {
255
- const payload = this.decode(token);
256
- if (!payload.exp) return -1;
257
- const remaining = payload.exp - Math.floor(Date.now() / 1000);
258
- return remaining > 0 ? remaining : -1;
259
- } catch {
260
- return -1;
261
- }
262
- }
263
-
264
- /**
265
- * 检查 token 是否已过期
266
- * @param token - JWT token字符串
267
- * @returns 是否已过期
268
- */
269
- static isExpired(token: string): boolean {
270
- return this.getTimeToExpiry(token) <= 0;
271
- }
272
-
273
- /**
274
- * 检查 token 是否即将过期
275
- * @param token - JWT token字符串
276
- * @param thresholdSeconds - 过期阈值(秒),默认300秒(5分钟)
277
- * @returns 是否即将过期
278
- */
279
- static isNearExpiry(token: string, thresholdSeconds: number = 300): boolean {
280
- const timeToExpiry = this.getTimeToExpiry(token);
281
- return timeToExpiry > 0 && timeToExpiry <= thresholdSeconds;
282
- }
283
-
284
- /**
285
- * 检查载荷是否包含特定角色
286
- * @param payload - JWT载荷
287
- * @param role - 要检查的角色
288
- * @returns 是否包含该角色
289
- */
290
- static hasRole(payload: JwtPayload, role: string): boolean {
291
- if (!payload) return false;
292
- if (payload.role === role) return true;
293
- if (Array.isArray(payload.roles) && payload.roles.includes(role)) return true;
294
- return false;
295
- }
296
-
297
- /**
298
- * 检查载荷是否包含任意一个角色
299
- * @param payload - JWT载荷
300
- * @param roles - 要检查的角色列表
301
- * @returns 是否包含任意一个角色
302
- */
303
- static hasAnyRole(payload: JwtPayload, roles: string[]): boolean {
304
- if (!payload) return false;
305
- return roles.some((role) => this.hasRole(payload, role));
306
- }
307
-
308
- /**
309
- * 检查载荷是否包含特定权限
310
- * @param payload - JWT载荷
311
- * @param permission - 要检查的权限
312
- * @returns 是否包含该权限
313
- */
314
- static hasPermission(payload: JwtPayload, permission: string): boolean {
315
- if (!payload || !payload.permissions) return false;
316
- return Array.isArray(payload.permissions) && payload.permissions.includes(permission);
317
- }
318
-
319
- /**
320
- * 检查载荷是否包含所有权限
321
- * @param payload - JWT载荷
322
- * @param permissions - 要检查的权限列表
323
- * @returns 是否包含所有权限
324
- */
325
- static hasAllPermissions(payload: JwtPayload, permissions: string[]): boolean {
326
- if (!payload || !payload.permissions) return false;
327
- return permissions.every((permission) => this.hasPermission(payload, permission));
328
- }
329
-
330
- // ==================== 便捷方法(自动使用环境变量) ====================
331
-
332
- /**
333
- * 创建用户 token(快捷方法)
334
- */
335
- static async create(payload: JwtPayload): Promise<string> {
336
- return this.sign(payload);
337
- }
338
-
339
- /**
340
- * 检查 token(快捷方法)
341
- */
342
- static async check(token: string): Promise<JwtPayload> {
343
- return this.verify(token);
344
- }
345
-
346
- /**
347
- * 解析 token(快捷方法,不验证签名)
348
- */
349
- static parse(token: string): JwtPayload {
350
- return this.decode(token);
351
- }
352
-
353
- /**
354
- * 签名用户认证 token
355
- */
356
- static async signUserToken(userInfo: JwtPayload, options?: JwtSignOptions): Promise<string> {
357
- return this.sign(userInfo, options);
358
- }
359
-
360
- /**
361
- * 签名 API 访问 token
362
- */
363
- static async signAPIToken(payload: JwtPayload, options?: JwtSignOptions): Promise<string> {
364
- return this.sign(payload, { audience: 'api', expiresIn: '1h', ...options });
365
- }
366
-
367
- /**
368
- * 签名刷新 token
369
- */
370
- static async signRefreshToken(payload: JwtPayload, options?: JwtSignOptions): Promise<string> {
371
- return this.sign(payload, { audience: 'refresh', expiresIn: '30d', ...options });
372
- }
373
-
374
- /**
375
- * 签名临时 token (用于重置密码等)
376
- */
377
- static async signTempToken(payload: JwtPayload, options?: JwtSignOptions): Promise<string> {
378
- return this.sign(payload, { audience: 'temporary', expiresIn: '15m', ...options });
379
- }
380
-
381
- /**
382
- * 验证用户认证 token
383
- */
384
- static async verifyUserToken(token: string, options?: JwtVerifyOptions): Promise<JwtPayload> {
385
- return this.verify(token, options);
386
- }
387
-
388
- /**
389
- * 验证 API 访问 token
390
- */
391
- static async verifyAPIToken(token: string, options?: JwtVerifyOptions): Promise<JwtPayload> {
392
- return this.verify(token, { audience: 'api', ...options });
393
- }
394
-
395
- /**
396
- * 验证刷新 token
397
- */
398
- static async verifyRefreshToken(token: string, options?: JwtVerifyOptions): Promise<JwtPayload> {
399
- return this.verify(token, { audience: 'refresh', ...options });
400
- }
401
-
402
- /**
403
- * 验证临时 token
404
- */
405
- static async verifyTempToken(token: string, options?: JwtVerifyOptions): Promise<JwtPayload> {
406
- return this.verify(token, { audience: 'temporary', ...options });
407
- }
408
-
409
- /**
410
- * 验证 token 并检查权限
411
- */
412
- static async verifyWithPermissions(token: string, requiredPermissions: string | string[], options?: JwtVerifyOptions): Promise<JwtPayload> {
413
- const payload = await this.verify(token, options);
414
-
415
- if (!payload.permissions) {
416
- throw new Error('Token中不包含权限信息');
417
- }
418
-
419
- const permissions = Array.isArray(requiredPermissions) ? requiredPermissions : [requiredPermissions];
420
- const hasPermission = permissions.every((permission) => payload.permissions.includes(permission));
421
-
422
- if (!hasPermission) {
423
- throw new Error('权限不足');
424
- }
425
-
426
- return payload;
427
- }
428
-
429
- /**
430
- * 验证 token 并检查角色
431
- */
432
- static async verifyWithRoles(token: string, requiredRoles: string | string[], options?: JwtVerifyOptions): Promise<JwtPayload> {
433
- const payload = await this.verify(token, options);
434
-
435
- if (!payload.role && !payload.roles) {
436
- throw new Error('Token中不包含角色信息');
437
- }
438
-
439
- const userRoles = payload.roles || [payload.role];
440
- const roles = Array.isArray(requiredRoles) ? requiredRoles : [requiredRoles];
441
-
442
- const hasRole = roles.some((role) => userRoles.includes(role));
443
-
444
- if (!hasRole) {
445
- throw new Error('角色权限不足');
446
- }
447
-
448
- return payload;
449
- }
450
-
451
- /**
452
- * 软验证 token (忽略过期时间)
453
- */
454
- static async verifySoft(token: string, options?: JwtVerifyOptions): Promise<JwtPayload> {
455
- return this.verify(token, { ignoreExpiration: true, ...options });
66
+ if (complete) {
67
+ const decoder = createDecoder({ complete: true });
68
+ const result = decoder(token) as FastJwtComplete;
69
+ return {
70
+ header: result.header,
71
+ payload: result.payload,
72
+ signature: result.signature
73
+ };
74
+ }
75
+ const decoder = createDecoder();
76
+ return decoder(token) as JwtPayload;
456
77
  }
457
78
  }