@onebots/core 0.5.0 → 1.0.4
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/README.md +3 -3
- package/lib/__tests__/config-validator.test.d.ts +4 -0
- package/lib/__tests__/config-validator.test.js +152 -0
- package/lib/__tests__/di-container.test.d.ts +4 -0
- package/lib/__tests__/di-container.test.js +114 -0
- package/lib/__tests__/errors.test.d.ts +4 -0
- package/lib/__tests__/errors.test.js +111 -0
- package/lib/__tests__/integration.test.d.ts +5 -0
- package/lib/__tests__/integration.test.js +112 -0
- package/lib/__tests__/lifecycle.test.d.ts +4 -0
- package/lib/__tests__/lifecycle.test.js +163 -0
- package/lib/account.d.ts +4 -1
- package/lib/account.js +6 -3
- package/lib/adapter.d.ts +67 -1
- package/lib/adapter.js +31 -4
- package/lib/base-app.d.ts +30 -3
- package/lib/base-app.js +295 -142
- package/lib/circuit-breaker.d.ts +94 -0
- package/lib/circuit-breaker.js +206 -0
- package/lib/config-validator.d.ts +51 -0
- package/lib/config-validator.js +184 -0
- package/lib/connection-pool.d.ts +68 -0
- package/lib/connection-pool.js +202 -0
- package/lib/db.d.ts +2 -1
- package/lib/db.js +11 -2
- package/lib/di-container.d.ts +60 -0
- package/lib/di-container.js +103 -0
- package/lib/errors.d.ts +157 -0
- package/lib/errors.js +257 -0
- package/lib/index.d.ts +13 -4
- package/lib/index.js +17 -3
- package/lib/lifecycle.d.ts +75 -0
- package/lib/lifecycle.js +175 -0
- package/lib/logger.d.ts +76 -0
- package/lib/logger.js +156 -0
- package/lib/metrics.d.ts +80 -0
- package/lib/metrics.js +201 -0
- package/lib/middleware/index.d.ts +8 -0
- package/lib/middleware/index.js +8 -0
- package/lib/middleware/metrics-collector.d.ts +9 -0
- package/lib/middleware/metrics-collector.js +64 -0
- package/lib/middleware/rate-limit.d.ts +32 -0
- package/lib/middleware/rate-limit.js +149 -0
- package/lib/middleware/security-audit.d.ts +33 -0
- package/lib/middleware/security-audit.js +223 -0
- package/lib/middleware/token-manager.d.ts +73 -0
- package/lib/middleware/token-manager.js +186 -0
- package/lib/middleware/token-validator.d.ts +42 -0
- package/lib/middleware/token-validator.js +198 -0
- package/lib/protocol.d.ts +2 -3
- package/lib/protocol.js +4 -0
- package/lib/rate-limiter.d.ts +88 -0
- package/lib/rate-limiter.js +196 -0
- package/lib/registry.d.ts +27 -0
- package/lib/registry.js +40 -5
- package/lib/retry.d.ts +87 -0
- package/lib/retry.js +205 -0
- package/lib/router.d.ts +43 -6
- package/lib/router.js +139 -12
- package/lib/timestamp.d.ts +42 -0
- package/lib/timestamp.js +72 -0
- package/lib/types.d.ts +1 -0
- package/lib/types.js +2 -1
- package/lib/utils.d.ts +2 -1
- package/lib/utils.js +11 -19
- package/package.json +24 -9
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 安全审计日志中间件
|
|
3
|
+
* 记录所有安全相关事件
|
|
4
|
+
*/
|
|
5
|
+
import { createLogger } from '../logger.js';
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
class SecurityAuditLogger {
|
|
9
|
+
logger = createLogger('SecurityAudit');
|
|
10
|
+
auditLogFile;
|
|
11
|
+
writeStream;
|
|
12
|
+
constructor(auditLogDir) {
|
|
13
|
+
// 确保目录存在
|
|
14
|
+
if (!fs.existsSync(auditLogDir)) {
|
|
15
|
+
fs.mkdirSync(auditLogDir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
// 使用日期作为日志文件名
|
|
18
|
+
const today = new Date().toISOString().split('T')[0];
|
|
19
|
+
this.auditLogFile = path.join(auditLogDir, `security-audit-${today}.log`);
|
|
20
|
+
// 创建写入流
|
|
21
|
+
this.writeStream = fs.createWriteStream(this.auditLogFile, { flags: 'a', encoding: 'utf-8' });
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 记录安全事件
|
|
25
|
+
*/
|
|
26
|
+
log(event) {
|
|
27
|
+
const logEntry = JSON.stringify(event) + '\n';
|
|
28
|
+
// 写入文件
|
|
29
|
+
if (this.writeStream) {
|
|
30
|
+
this.writeStream.write(logEntry);
|
|
31
|
+
}
|
|
32
|
+
// 根据事件类型选择日志级别
|
|
33
|
+
const logData = {
|
|
34
|
+
type: event.type,
|
|
35
|
+
ip: event.ip,
|
|
36
|
+
path: event.path,
|
|
37
|
+
method: event.method,
|
|
38
|
+
...event.details,
|
|
39
|
+
};
|
|
40
|
+
switch (event.type) {
|
|
41
|
+
case 'auth_failure':
|
|
42
|
+
case 'invalid_token':
|
|
43
|
+
case 'suspicious_request':
|
|
44
|
+
this.logger.warn('Security event', logData);
|
|
45
|
+
break;
|
|
46
|
+
case 'rate_limit':
|
|
47
|
+
this.logger.warn('Rate limit triggered', logData);
|
|
48
|
+
break;
|
|
49
|
+
case 'auth_success':
|
|
50
|
+
this.logger.debug('Security event', logData);
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
this.logger.info('Security event', logData);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 关闭写入流
|
|
58
|
+
*/
|
|
59
|
+
close() {
|
|
60
|
+
if (this.writeStream) {
|
|
61
|
+
this.writeStream.end();
|
|
62
|
+
this.writeStream = undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
let auditLogger = null;
|
|
67
|
+
/**
|
|
68
|
+
* 初始化安全审计日志
|
|
69
|
+
*/
|
|
70
|
+
export function initSecurityAudit(auditLogDir) {
|
|
71
|
+
auditLogger = new SecurityAuditLogger(auditLogDir);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 安全审计中间件
|
|
75
|
+
*/
|
|
76
|
+
export function securityAudit() {
|
|
77
|
+
return async (ctx, next) => {
|
|
78
|
+
if (!auditLogger) {
|
|
79
|
+
return next();
|
|
80
|
+
}
|
|
81
|
+
const startTime = Date.now();
|
|
82
|
+
const ip = ctx.ip || ctx.request.ip || 'unknown';
|
|
83
|
+
const userAgent = ctx.get('user-agent');
|
|
84
|
+
try {
|
|
85
|
+
await next();
|
|
86
|
+
const duration = Date.now() - startTime;
|
|
87
|
+
// 记录认证成功
|
|
88
|
+
if (ctx.status === 200 && ctx.path.includes('/api/')) {
|
|
89
|
+
auditLogger.log({
|
|
90
|
+
timestamp: Date.now(),
|
|
91
|
+
type: 'auth_success',
|
|
92
|
+
ip,
|
|
93
|
+
path: ctx.path,
|
|
94
|
+
method: ctx.method,
|
|
95
|
+
userAgent,
|
|
96
|
+
details: {
|
|
97
|
+
status: ctx.status,
|
|
98
|
+
duration,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
// 记录错误
|
|
103
|
+
if (ctx.status >= 400) {
|
|
104
|
+
auditLogger.log({
|
|
105
|
+
timestamp: Date.now(),
|
|
106
|
+
type: 'error',
|
|
107
|
+
ip,
|
|
108
|
+
path: ctx.path,
|
|
109
|
+
method: ctx.method,
|
|
110
|
+
userAgent,
|
|
111
|
+
details: {
|
|
112
|
+
status: ctx.status,
|
|
113
|
+
error: (ctx.body && typeof ctx.body === 'object' && 'error' in ctx.body)
|
|
114
|
+
? String(ctx.body.error)
|
|
115
|
+
: ctx.message || 'Unknown error',
|
|
116
|
+
duration,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
// 记录异常
|
|
123
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
124
|
+
auditLogger.log({
|
|
125
|
+
timestamp: Date.now(),
|
|
126
|
+
type: 'error',
|
|
127
|
+
ip,
|
|
128
|
+
path: ctx.path,
|
|
129
|
+
method: ctx.method,
|
|
130
|
+
userAgent,
|
|
131
|
+
details: {
|
|
132
|
+
error: err.message,
|
|
133
|
+
stack: err.stack,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 记录认证失败
|
|
142
|
+
*/
|
|
143
|
+
export function logAuthFailure(ctx, reason) {
|
|
144
|
+
if (!auditLogger)
|
|
145
|
+
return;
|
|
146
|
+
auditLogger.log({
|
|
147
|
+
timestamp: Date.now(),
|
|
148
|
+
type: 'auth_failure',
|
|
149
|
+
ip: ctx.ip || ctx.request.ip || 'unknown',
|
|
150
|
+
path: ctx.path,
|
|
151
|
+
method: ctx.method,
|
|
152
|
+
userAgent: ctx.get('user-agent'),
|
|
153
|
+
details: {
|
|
154
|
+
reason,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 记录无效令牌
|
|
160
|
+
*/
|
|
161
|
+
export function logInvalidToken(ctx, token) {
|
|
162
|
+
if (!auditLogger)
|
|
163
|
+
return;
|
|
164
|
+
auditLogger.log({
|
|
165
|
+
timestamp: Date.now(),
|
|
166
|
+
type: 'invalid_token',
|
|
167
|
+
ip: ctx.ip || ctx.request.ip || 'unknown',
|
|
168
|
+
path: ctx.path,
|
|
169
|
+
method: ctx.method,
|
|
170
|
+
userAgent: ctx.get('user-agent'),
|
|
171
|
+
details: {
|
|
172
|
+
tokenPrefix: token ? token.substring(0, 10) + '...' : 'missing',
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* 记录可疑请求
|
|
178
|
+
*/
|
|
179
|
+
export function logSuspiciousRequest(ctx, reason, details) {
|
|
180
|
+
if (!auditLogger)
|
|
181
|
+
return;
|
|
182
|
+
auditLogger.log({
|
|
183
|
+
timestamp: Date.now(),
|
|
184
|
+
type: 'suspicious_request',
|
|
185
|
+
ip: ctx.ip || ctx.request.ip || 'unknown',
|
|
186
|
+
path: ctx.path,
|
|
187
|
+
method: ctx.method,
|
|
188
|
+
userAgent: ctx.get('user-agent'),
|
|
189
|
+
details: {
|
|
190
|
+
reason,
|
|
191
|
+
...details,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* 记录速率限制触发
|
|
197
|
+
*/
|
|
198
|
+
export function logRateLimit(ctx, key, count, max) {
|
|
199
|
+
if (!auditLogger)
|
|
200
|
+
return;
|
|
201
|
+
auditLogger.log({
|
|
202
|
+
timestamp: Date.now(),
|
|
203
|
+
type: 'rate_limit',
|
|
204
|
+
ip: ctx.ip || ctx.request.ip || 'unknown',
|
|
205
|
+
path: ctx.path,
|
|
206
|
+
method: ctx.method,
|
|
207
|
+
userAgent: ctx.get('user-agent'),
|
|
208
|
+
details: {
|
|
209
|
+
key,
|
|
210
|
+
count,
|
|
211
|
+
max,
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* 关闭审计日志
|
|
217
|
+
*/
|
|
218
|
+
export function closeSecurityAudit() {
|
|
219
|
+
if (auditLogger) {
|
|
220
|
+
auditLogger.close();
|
|
221
|
+
auditLogger = null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 令牌管理器
|
|
3
|
+
* 支持令牌过期、刷新、轮换等功能
|
|
4
|
+
*/
|
|
5
|
+
export interface TokenInfo {
|
|
6
|
+
token: string;
|
|
7
|
+
expiresAt?: number;
|
|
8
|
+
refreshToken?: string;
|
|
9
|
+
refreshExpiresAt?: number;
|
|
10
|
+
metadata?: Record<string, any>;
|
|
11
|
+
}
|
|
12
|
+
export interface TokenManagerOptions {
|
|
13
|
+
/** 默认令牌过期时间(毫秒) */
|
|
14
|
+
defaultExpiration?: number;
|
|
15
|
+
/** 刷新令牌过期时间(毫秒) */
|
|
16
|
+
refreshExpiration?: number;
|
|
17
|
+
/** 是否自动刷新 */
|
|
18
|
+
autoRefresh?: boolean;
|
|
19
|
+
/** 刷新阈值(在过期前多久刷新,毫秒) */
|
|
20
|
+
refreshThreshold?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 令牌管理器
|
|
24
|
+
*/
|
|
25
|
+
export declare class TokenManager {
|
|
26
|
+
private tokens;
|
|
27
|
+
private options;
|
|
28
|
+
constructor(options?: TokenManagerOptions);
|
|
29
|
+
/**
|
|
30
|
+
* 生成新令牌
|
|
31
|
+
*/
|
|
32
|
+
generateToken(metadata?: Record<string, any>): TokenInfo;
|
|
33
|
+
/**
|
|
34
|
+
* 验证令牌
|
|
35
|
+
*/
|
|
36
|
+
validateToken(token: string): {
|
|
37
|
+
valid: boolean;
|
|
38
|
+
expired?: boolean;
|
|
39
|
+
info?: TokenInfo;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* 刷新令牌
|
|
43
|
+
*/
|
|
44
|
+
refreshToken(refreshToken: string): TokenInfo | null;
|
|
45
|
+
/**
|
|
46
|
+
* 撤销令牌
|
|
47
|
+
*/
|
|
48
|
+
revokeToken(token: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* 获取令牌信息
|
|
51
|
+
*/
|
|
52
|
+
getTokenInfo(token: string): TokenInfo | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* 清理过期令牌
|
|
55
|
+
*/
|
|
56
|
+
cleanup(): number;
|
|
57
|
+
/**
|
|
58
|
+
* 获取统计信息
|
|
59
|
+
*/
|
|
60
|
+
getStats(): {
|
|
61
|
+
total: number;
|
|
62
|
+
active: number;
|
|
63
|
+
expired: number;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 初始化全局令牌管理器
|
|
68
|
+
*/
|
|
69
|
+
export declare function initTokenManager(options?: TokenManagerOptions): TokenManager;
|
|
70
|
+
/**
|
|
71
|
+
* 获取全局令牌管理器
|
|
72
|
+
*/
|
|
73
|
+
export declare function getTokenManager(): TokenManager | null;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 令牌管理器
|
|
3
|
+
* 支持令牌过期、刷新、轮换等功能
|
|
4
|
+
*/
|
|
5
|
+
import { createLogger } from '../logger.js';
|
|
6
|
+
import crypto from 'crypto';
|
|
7
|
+
const logger = createLogger('TokenManager');
|
|
8
|
+
/**
|
|
9
|
+
* 令牌管理器
|
|
10
|
+
*/
|
|
11
|
+
export class TokenManager {
|
|
12
|
+
tokens = new Map();
|
|
13
|
+
options;
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.options = {
|
|
16
|
+
defaultExpiration: options.defaultExpiration || 3600000, // 默认 1 小时
|
|
17
|
+
refreshExpiration: options.refreshExpiration || 86400000 * 7, // 默认 7 天
|
|
18
|
+
autoRefresh: options.autoRefresh ?? false,
|
|
19
|
+
refreshThreshold: options.refreshThreshold || 300000, // 默认 5 分钟
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 生成新令牌
|
|
24
|
+
*/
|
|
25
|
+
generateToken(metadata) {
|
|
26
|
+
const token = crypto.randomBytes(32).toString('hex');
|
|
27
|
+
const refreshToken = crypto.randomBytes(32).toString('hex');
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
const tokenInfo = {
|
|
30
|
+
token,
|
|
31
|
+
expiresAt: now + this.options.defaultExpiration,
|
|
32
|
+
refreshToken,
|
|
33
|
+
refreshExpiresAt: now + this.options.refreshExpiration,
|
|
34
|
+
metadata,
|
|
35
|
+
};
|
|
36
|
+
this.tokens.set(token, tokenInfo);
|
|
37
|
+
// 如果设置了刷新令牌,也存储
|
|
38
|
+
if (refreshToken) {
|
|
39
|
+
this.tokens.set(`refresh:${refreshToken}`, tokenInfo);
|
|
40
|
+
}
|
|
41
|
+
logger.debug('Token generated', {
|
|
42
|
+
tokenPrefix: token.substring(0, 10) + '...',
|
|
43
|
+
expiresAt: new Date(tokenInfo.expiresAt).toISOString(),
|
|
44
|
+
});
|
|
45
|
+
return tokenInfo;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 验证令牌
|
|
49
|
+
*/
|
|
50
|
+
validateToken(token) {
|
|
51
|
+
const info = this.tokens.get(token);
|
|
52
|
+
if (!info) {
|
|
53
|
+
return { valid: false };
|
|
54
|
+
}
|
|
55
|
+
// 检查是否过期
|
|
56
|
+
if (info.expiresAt && Date.now() > info.expiresAt) {
|
|
57
|
+
// 清理过期令牌
|
|
58
|
+
this.tokens.delete(token);
|
|
59
|
+
if (info.refreshToken) {
|
|
60
|
+
this.tokens.delete(`refresh:${info.refreshToken}`);
|
|
61
|
+
}
|
|
62
|
+
return { valid: false, expired: true };
|
|
63
|
+
}
|
|
64
|
+
// 检查是否需要刷新
|
|
65
|
+
if (this.options.autoRefresh && info.expiresAt) {
|
|
66
|
+
const timeUntilExpiry = info.expiresAt - Date.now();
|
|
67
|
+
if (timeUntilExpiry < this.options.refreshThreshold) {
|
|
68
|
+
logger.debug('Token needs refresh', {
|
|
69
|
+
tokenPrefix: token.substring(0, 10) + '...',
|
|
70
|
+
timeUntilExpiry,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { valid: true, info };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 刷新令牌
|
|
78
|
+
*/
|
|
79
|
+
refreshToken(refreshToken) {
|
|
80
|
+
const key = `refresh:${refreshToken}`;
|
|
81
|
+
const info = this.tokens.get(key);
|
|
82
|
+
if (!info) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
// 检查刷新令牌是否过期
|
|
86
|
+
if (info.refreshExpiresAt && Date.now() > info.refreshExpiresAt) {
|
|
87
|
+
this.tokens.delete(key);
|
|
88
|
+
if (info.token) {
|
|
89
|
+
this.tokens.delete(info.token);
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
// 生成新令牌
|
|
94
|
+
const newToken = this.generateToken(info.metadata);
|
|
95
|
+
// 删除旧令牌
|
|
96
|
+
this.tokens.delete(info.token);
|
|
97
|
+
this.tokens.delete(key);
|
|
98
|
+
logger.info('Token refreshed', {
|
|
99
|
+
oldTokenPrefix: info.token.substring(0, 10) + '...',
|
|
100
|
+
newTokenPrefix: newToken.token.substring(0, 10) + '...',
|
|
101
|
+
});
|
|
102
|
+
return newToken;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 撤销令牌
|
|
106
|
+
*/
|
|
107
|
+
revokeToken(token) {
|
|
108
|
+
const info = this.tokens.get(token);
|
|
109
|
+
if (!info) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
this.tokens.delete(token);
|
|
113
|
+
if (info.refreshToken) {
|
|
114
|
+
this.tokens.delete(`refresh:${info.refreshToken}`);
|
|
115
|
+
}
|
|
116
|
+
logger.info('Token revoked', {
|
|
117
|
+
tokenPrefix: token.substring(0, 10) + '...',
|
|
118
|
+
});
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 获取令牌信息
|
|
123
|
+
*/
|
|
124
|
+
getTokenInfo(token) {
|
|
125
|
+
return this.tokens.get(token);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 清理过期令牌
|
|
129
|
+
*/
|
|
130
|
+
cleanup() {
|
|
131
|
+
const now = Date.now();
|
|
132
|
+
let cleaned = 0;
|
|
133
|
+
for (const [key, info] of this.tokens.entries()) {
|
|
134
|
+
const expired = (info.expiresAt && now > info.expiresAt) ||
|
|
135
|
+
(info.refreshExpiresAt && now > info.refreshExpiresAt);
|
|
136
|
+
if (expired) {
|
|
137
|
+
this.tokens.delete(key);
|
|
138
|
+
cleaned++;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (cleaned > 0) {
|
|
142
|
+
logger.debug(`Cleaned up ${cleaned} expired tokens`);
|
|
143
|
+
}
|
|
144
|
+
return cleaned;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 获取统计信息
|
|
148
|
+
*/
|
|
149
|
+
getStats() {
|
|
150
|
+
const now = Date.now();
|
|
151
|
+
let active = 0;
|
|
152
|
+
let expired = 0;
|
|
153
|
+
for (const info of this.tokens.values()) {
|
|
154
|
+
if (info.expiresAt && now > info.expiresAt) {
|
|
155
|
+
expired++;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
active++;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
total: this.tokens.size,
|
|
163
|
+
active,
|
|
164
|
+
expired,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// 全局令牌管理器实例(可选)
|
|
169
|
+
let globalTokenManager = null;
|
|
170
|
+
/**
|
|
171
|
+
* 初始化全局令牌管理器
|
|
172
|
+
*/
|
|
173
|
+
export function initTokenManager(options) {
|
|
174
|
+
globalTokenManager = new TokenManager(options);
|
|
175
|
+
// 定期清理过期令牌(每 5 分钟)
|
|
176
|
+
setInterval(() => {
|
|
177
|
+
globalTokenManager?.cleanup();
|
|
178
|
+
}, 5 * 60 * 1000);
|
|
179
|
+
return globalTokenManager;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* 获取全局令牌管理器
|
|
183
|
+
*/
|
|
184
|
+
export function getTokenManager() {
|
|
185
|
+
return globalTokenManager;
|
|
186
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 访问令牌验证中间件
|
|
3
|
+
* 支持多种令牌格式和验证方式
|
|
4
|
+
*/
|
|
5
|
+
import type { Context, Next } from 'koa';
|
|
6
|
+
interface TokenConfig {
|
|
7
|
+
/** 令牌名称(用于从 header 或 query 中获取) */
|
|
8
|
+
tokenName?: string;
|
|
9
|
+
/** 是否从 Authorization header 获取 */
|
|
10
|
+
fromHeader?: boolean;
|
|
11
|
+
/** 是否从 query 参数获取 */
|
|
12
|
+
fromQuery?: boolean;
|
|
13
|
+
/** 验证函数 */
|
|
14
|
+
validator?: (token: string, ctx: Context) => boolean | Promise<boolean>;
|
|
15
|
+
/** 是否必需 */
|
|
16
|
+
required?: boolean;
|
|
17
|
+
/** 自定义错误消息 */
|
|
18
|
+
errorMessage?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 创建令牌验证中间件
|
|
22
|
+
*/
|
|
23
|
+
export declare function createTokenValidator(config?: TokenConfig): (ctx: Context, next: Next) => Promise<any>;
|
|
24
|
+
/**
|
|
25
|
+
* 创建基于配置的令牌验证器
|
|
26
|
+
* 从配置中读取 access_token 列表进行验证
|
|
27
|
+
*/
|
|
28
|
+
export declare function createConfigTokenValidator(expectedTokens: string[]): (ctx: Context, next: Next) => Promise<any>;
|
|
29
|
+
/**
|
|
30
|
+
* HMAC 签名验证
|
|
31
|
+
*/
|
|
32
|
+
export declare function createHMACValidator(secret: string, algorithm?: string): (ctx: Context, next: Next) => Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* 创建带令牌管理的验证器
|
|
35
|
+
* 支持令牌过期检查和自动刷新
|
|
36
|
+
*/
|
|
37
|
+
export declare function createManagedTokenValidator(tokenManager: import('./token-manager.js').TokenManager, config?: TokenConfig): (ctx: Context, next: Next) => Promise<any>;
|
|
38
|
+
/**
|
|
39
|
+
* 组合多个验证器
|
|
40
|
+
*/
|
|
41
|
+
export declare function combineValidators(...validators: Array<(ctx: Context, next: Next) => Promise<void>>): (ctx: Context, next: Next) => Promise<void>;
|
|
42
|
+
export {};
|