@dangao/bun-server 1.0.0 → 1.0.3
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/package.json +4 -2
- package/readme.md +163 -2
- package/src/auth/controller.ts +148 -0
- package/src/auth/decorators.ts +81 -0
- package/src/auth/index.ts +12 -0
- package/src/auth/jwt.ts +169 -0
- package/src/auth/oauth2.ts +244 -0
- package/src/auth/types.ts +248 -0
- package/src/cache/cache-module.ts +67 -0
- package/src/cache/decorators.ts +202 -0
- package/src/cache/index.ts +27 -0
- package/src/cache/service.ts +151 -0
- package/src/cache/types.ts +420 -0
- package/src/config/config-module.ts +76 -0
- package/src/config/index.ts +8 -0
- package/src/config/service.ts +93 -0
- package/src/config/types.ts +27 -0
- package/src/controller/controller.ts +251 -0
- package/src/controller/decorators.ts +84 -0
- package/src/controller/index.ts +7 -0
- package/src/controller/metadata.ts +27 -0
- package/src/controller/param-binder.ts +157 -0
- package/src/core/application.ts +233 -0
- package/src/core/context.ts +228 -0
- package/src/core/index.ts +4 -0
- package/src/core/server.ts +128 -0
- package/src/core/types.ts +2 -0
- package/src/database/connection-manager.ts +239 -0
- package/src/database/connection-pool.ts +322 -0
- package/src/database/database-extension.ts +62 -0
- package/src/database/database-module.ts +115 -0
- package/src/database/health-indicator.ts +51 -0
- package/src/database/index.ts +47 -0
- package/src/database/orm/decorators.ts +155 -0
- package/src/database/orm/drizzle-repository.ts +39 -0
- package/src/database/orm/index.ts +23 -0
- package/src/database/orm/repository-decorator.ts +39 -0
- package/src/database/orm/repository.ts +103 -0
- package/src/database/orm/service.ts +49 -0
- package/src/database/orm/transaction-decorator.ts +45 -0
- package/src/database/orm/transaction-interceptor.ts +243 -0
- package/src/database/orm/transaction-manager.ts +276 -0
- package/src/database/orm/transaction-types.ts +140 -0
- package/src/database/orm/types.ts +99 -0
- package/src/database/service.ts +221 -0
- package/src/database/types.ts +171 -0
- package/src/di/container.ts +398 -0
- package/src/di/decorators.ts +228 -0
- package/src/di/index.ts +4 -0
- package/src/di/module-registry.ts +188 -0
- package/src/di/module.ts +65 -0
- package/src/di/types.ts +67 -0
- package/src/error/error-codes.ts +222 -0
- package/src/error/filter.ts +43 -0
- package/src/error/handler.ts +66 -0
- package/src/error/http-exception.ts +115 -0
- package/src/error/i18n.ts +217 -0
- package/src/error/index.ts +16 -0
- package/src/extensions/index.ts +5 -0
- package/src/extensions/logger-extension.ts +31 -0
- package/src/extensions/logger-module.ts +69 -0
- package/src/extensions/types.ts +14 -0
- package/src/files/index.ts +5 -0
- package/src/files/static-middleware.ts +53 -0
- package/src/files/storage.ts +67 -0
- package/src/files/types.ts +33 -0
- package/src/files/upload-middleware.ts +45 -0
- package/src/health/controller.ts +76 -0
- package/src/health/health-module.ts +51 -0
- package/src/health/index.ts +12 -0
- package/src/health/types.ts +28 -0
- package/src/index.ts +270 -0
- package/src/metrics/collector.ts +209 -0
- package/src/metrics/controller.ts +40 -0
- package/src/metrics/index.ts +15 -0
- package/src/metrics/metrics-module.ts +58 -0
- package/src/metrics/middleware.ts +46 -0
- package/src/metrics/prometheus.ts +79 -0
- package/src/metrics/types.ts +103 -0
- package/src/middleware/builtin/cors.ts +60 -0
- package/src/middleware/builtin/error-handler.ts +90 -0
- package/src/middleware/builtin/file-upload.ts +42 -0
- package/src/middleware/builtin/index.ts +14 -0
- package/src/middleware/builtin/logger.ts +91 -0
- package/src/middleware/builtin/rate-limit.ts +252 -0
- package/src/middleware/builtin/static-file.ts +88 -0
- package/src/middleware/decorators.ts +91 -0
- package/src/middleware/index.ts +11 -0
- package/src/middleware/middleware.ts +13 -0
- package/src/middleware/pipeline.ts +93 -0
- package/src/queue/decorators.ts +110 -0
- package/src/queue/index.ts +26 -0
- package/src/queue/queue-module.ts +64 -0
- package/src/queue/service.ts +302 -0
- package/src/queue/types.ts +341 -0
- package/src/request/body-parser.ts +133 -0
- package/src/request/file-handler.ts +46 -0
- package/src/request/index.ts +5 -0
- package/src/request/request.ts +107 -0
- package/src/request/response.ts +150 -0
- package/src/router/decorators.ts +122 -0
- package/src/router/index.ts +6 -0
- package/src/router/registry.ts +98 -0
- package/src/router/route.ts +140 -0
- package/src/router/router.ts +241 -0
- package/src/router/types.ts +27 -0
- package/src/security/access-decision-manager.ts +34 -0
- package/src/security/authentication-manager.ts +47 -0
- package/src/security/context.ts +92 -0
- package/src/security/filter.ts +162 -0
- package/src/security/index.ts +8 -0
- package/src/security/providers/index.ts +3 -0
- package/src/security/providers/jwt-provider.ts +60 -0
- package/src/security/providers/oauth2-provider.ts +70 -0
- package/src/security/security-module.ts +145 -0
- package/src/security/types.ts +165 -0
- package/src/session/decorators.ts +45 -0
- package/src/session/index.ts +19 -0
- package/src/session/middleware.ts +143 -0
- package/src/session/service.ts +218 -0
- package/src/session/session-module.ts +69 -0
- package/src/session/types.ts +373 -0
- package/src/swagger/decorators.ts +133 -0
- package/src/swagger/generator.ts +234 -0
- package/src/swagger/index.ts +7 -0
- package/src/swagger/swagger-extension.ts +41 -0
- package/src/swagger/swagger-module.ts +83 -0
- package/src/swagger/types.ts +188 -0
- package/src/swagger/ui.ts +98 -0
- package/src/testing/harness.ts +96 -0
- package/src/validation/decorators.ts +95 -0
- package/src/validation/errors.ts +28 -0
- package/src/validation/index.ts +14 -0
- package/src/validation/types.ts +35 -0
- package/src/validation/validator.ts +63 -0
- package/src/websocket/decorators.ts +51 -0
- package/src/websocket/index.ts +12 -0
- package/src/websocket/registry.ts +133 -0
- package/tests/cache/cache-module.test.ts +212 -0
- package/tests/config/config-module.test.ts +151 -0
- package/tests/controller/controller.test.ts +189 -0
- package/tests/core/application.test.ts +57 -0
- package/tests/core/context-body.test.ts +44 -0
- package/tests/core/context.test.ts +86 -0
- package/tests/core/edge-cases.test.ts +432 -0
- package/tests/database/database-module.test.ts +385 -0
- package/tests/database/orm.test.ts +164 -0
- package/tests/database/postgres-mysql-integration.test.ts +395 -0
- package/tests/database/transaction.test.ts +238 -0
- package/tests/di/container.test.ts +264 -0
- package/tests/di/module.test.ts +128 -0
- package/tests/error/error-codes.test.ts +121 -0
- package/tests/error/error-handler.test.ts +68 -0
- package/tests/error/error-handling.test.ts +254 -0
- package/tests/error/http-exception.test.ts +37 -0
- package/tests/error/i18n-integration.test.ts +175 -0
- package/tests/extensions/logger-extension.test.ts +40 -0
- package/tests/files/static-middleware.test.ts +67 -0
- package/tests/files/upload-middleware.test.ts +43 -0
- package/tests/health/health-module.test.ts +116 -0
- package/tests/integration/application-router.test.ts +85 -0
- package/tests/integration/body-parsing.test.ts +88 -0
- package/tests/integration/cache-e2e.test.ts +114 -0
- package/tests/integration/oauth2-e2e.test.ts +615 -0
- package/tests/integration/session-e2e.test.ts +207 -0
- package/tests/metrics/metrics-module.test.ts +178 -0
- package/tests/middleware/builtin.test.ts +206 -0
- package/tests/middleware/file-upload.test.ts +41 -0
- package/tests/middleware/middleware.test.ts +120 -0
- package/tests/middleware/pipeline.test.ts +72 -0
- package/tests/middleware/rate-limit.test.ts +314 -0
- package/tests/middleware/static-file.test.ts +62 -0
- package/tests/perf/harness.test.ts +48 -0
- package/tests/perf/optimization.test.ts +183 -0
- package/tests/perf/regression.test.ts +120 -0
- package/tests/queue/queue-module.test.ts +217 -0
- package/tests/request/body-parser.test.ts +96 -0
- package/tests/request/response.test.ts +99 -0
- package/tests/router/decorators.test.ts +48 -0
- package/tests/router/registry.test.ts +51 -0
- package/tests/router/route.test.ts +71 -0
- package/tests/router/router-normalization.test.ts +106 -0
- package/tests/router/router.test.ts +133 -0
- package/tests/security/access-decision-manager.test.ts +84 -0
- package/tests/security/authentication-manager.test.ts +81 -0
- package/tests/security/context.test.ts +302 -0
- package/tests/security/filter.test.ts +225 -0
- package/tests/security/jwt-provider.test.ts +106 -0
- package/tests/security/oauth2-provider.test.ts +269 -0
- package/tests/security/security-module.test.ts +143 -0
- package/tests/session/session-module.test.ts +307 -0
- package/tests/stress/di-stress.test.ts +30 -0
- package/tests/swagger/decorators.test.ts +153 -0
- package/tests/swagger/generator.test.ts +202 -0
- package/tests/swagger/swagger-extension.test.ts +72 -0
- package/tests/swagger/swagger-module.test.ts +79 -0
- package/tests/utils/test-port.ts +10 -0
- package/tests/validation/controller-validation.test.ts +64 -0
- package/tests/validation/validation.test.ts +42 -0
- package/tests/websocket/gateway.test.ts +68 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { Context } from '../core/context';
|
|
2
|
+
import type { Middleware } from '../middleware';
|
|
3
|
+
import { SecurityContextHolder } from './context';
|
|
4
|
+
import { AuthenticationManager } from './authentication-manager';
|
|
5
|
+
import { RoleBasedAccessDecisionManager } from './access-decision-manager';
|
|
6
|
+
import type { SecurityConfig, AuthenticationRequest } from './types';
|
|
7
|
+
import {
|
|
8
|
+
UnauthorizedException,
|
|
9
|
+
ForbiddenException,
|
|
10
|
+
} from '../error/http-exception';
|
|
11
|
+
import { ErrorCode } from '../error/error-codes';
|
|
12
|
+
import { requiresAuth, getAuthMetadata } from '../auth/decorators';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 安全过滤器配置
|
|
16
|
+
*/
|
|
17
|
+
export interface SecurityFilterConfig extends SecurityConfig {
|
|
18
|
+
/**
|
|
19
|
+
* 认证管理器
|
|
20
|
+
*/
|
|
21
|
+
authenticationManager: AuthenticationManager;
|
|
22
|
+
/**
|
|
23
|
+
* 访问决策器
|
|
24
|
+
*/
|
|
25
|
+
accessDecisionManager?: RoleBasedAccessDecisionManager;
|
|
26
|
+
/**
|
|
27
|
+
* 令牌提取函数
|
|
28
|
+
*/
|
|
29
|
+
extractToken?: (ctx: Context) => string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 创建安全过滤器
|
|
34
|
+
*/
|
|
35
|
+
export function createSecurityFilter(config: SecurityFilterConfig): Middleware {
|
|
36
|
+
const {
|
|
37
|
+
authenticationManager,
|
|
38
|
+
accessDecisionManager = new RoleBasedAccessDecisionManager(),
|
|
39
|
+
excludePaths = [],
|
|
40
|
+
defaultAuthRequired = true,
|
|
41
|
+
extractToken,
|
|
42
|
+
} = config;
|
|
43
|
+
|
|
44
|
+
return async (ctx: Context, next) => {
|
|
45
|
+
return SecurityContextHolder.runWithContext(async () => {
|
|
46
|
+
// 检查是否在排除列表中
|
|
47
|
+
// 使用 ctx.path 而不是 ctx.request.url,因为 Context 已经解析了路径
|
|
48
|
+
const path =
|
|
49
|
+
ctx.path || ctx.request.url.split('?')[0].replace(/^https?:\/\/[^/]+/, '');
|
|
50
|
+
if (excludePaths.some((exclude) => path.startsWith(exclude))) {
|
|
51
|
+
return await next();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 获取安全上下文(绑定到当前 AsyncLocalStorage 上下文)
|
|
55
|
+
const securityContext = SecurityContextHolder.getContext();
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
// 提取令牌
|
|
59
|
+
const token = extractToken
|
|
60
|
+
? extractToken(ctx)
|
|
61
|
+
: extractTokenFromHeader(ctx);
|
|
62
|
+
|
|
63
|
+
// 如果有令牌,尝试认证
|
|
64
|
+
if (token) {
|
|
65
|
+
const request: AuthenticationRequest = {
|
|
66
|
+
principal: '',
|
|
67
|
+
credentials: token,
|
|
68
|
+
type: 'jwt',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const authentication = await authenticationManager.authenticate(request);
|
|
72
|
+
if (authentication) {
|
|
73
|
+
securityContext.setAuthentication(authentication);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 检查是否需要认证
|
|
78
|
+
const handler = (ctx as any).routeHandler;
|
|
79
|
+
if (handler) {
|
|
80
|
+
const controllerClass = handler.controller;
|
|
81
|
+
const controllerTarget =
|
|
82
|
+
(controllerClass && controllerClass.prototype) || controllerClass;
|
|
83
|
+
const method = handler.method;
|
|
84
|
+
|
|
85
|
+
if (requiresAuth(controllerTarget, method)) {
|
|
86
|
+
const authentication = securityContext.authentication;
|
|
87
|
+
if (!authentication || !authentication.authenticated) {
|
|
88
|
+
throw new UnauthorizedException(
|
|
89
|
+
'Authentication required',
|
|
90
|
+
undefined,
|
|
91
|
+
ErrorCode.AUTH_REQUIRED,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 检查角色权限
|
|
96
|
+
const requiredRoles = getRequiredRoles(controllerTarget, method);
|
|
97
|
+
if (requiredRoles.length > 0) {
|
|
98
|
+
const hasAccess = accessDecisionManager.decide(
|
|
99
|
+
authentication,
|
|
100
|
+
requiredRoles,
|
|
101
|
+
);
|
|
102
|
+
if (!hasAccess) {
|
|
103
|
+
// 调试信息:输出权限检查详情
|
|
104
|
+
const userRoles = authentication.authorities || [];
|
|
105
|
+
throw new ForbiddenException(
|
|
106
|
+
`Insufficient permissions. Required roles: ${requiredRoles.join(', ')}, User roles: ${userRoles.join(', ')}`,
|
|
107
|
+
{ requiredRoles, userRoles },
|
|
108
|
+
ErrorCode.AUTH_INSUFFICIENT_PERMISSIONS,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} else if (defaultAuthRequired && !securityContext.isAuthenticated()) {
|
|
114
|
+
throw new UnauthorizedException(
|
|
115
|
+
'Authentication required',
|
|
116
|
+
undefined,
|
|
117
|
+
ErrorCode.AUTH_REQUIRED,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 将安全上下文附加到 Context
|
|
122
|
+
(ctx as any).security = securityContext;
|
|
123
|
+
(ctx as any).auth = {
|
|
124
|
+
isAuthenticated: securityContext.isAuthenticated(),
|
|
125
|
+
user: securityContext.getPrincipal(),
|
|
126
|
+
payload: (securityContext.authentication?.details as any),
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return await next();
|
|
130
|
+
} finally {
|
|
131
|
+
// 清理当前请求内的认证信息,防止泄漏到下一个请求
|
|
132
|
+
SecurityContextHolder.clearContext();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 从请求头提取令牌
|
|
140
|
+
*/
|
|
141
|
+
function extractTokenFromHeader(ctx: Context): string | null {
|
|
142
|
+
const authHeader = ctx.getHeader('authorization');
|
|
143
|
+
if (!authHeader) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const parts = authHeader.split(' ');
|
|
148
|
+
if (parts.length !== 2 || parts[0].toLowerCase() !== 'bearer') {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return parts[1];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 获取需要的角色
|
|
157
|
+
*/
|
|
158
|
+
function getRequiredRoles(target: any, propertyKey: string | symbol): string[] {
|
|
159
|
+
const metadata = getAuthMetadata(target, propertyKey);
|
|
160
|
+
return metadata?.roles || [];
|
|
161
|
+
}
|
|
162
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Authentication,
|
|
3
|
+
AuthenticationProvider,
|
|
4
|
+
AuthenticationRequest,
|
|
5
|
+
Principal,
|
|
6
|
+
} from '../types';
|
|
7
|
+
import { JWTUtil } from '../../auth/jwt';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* JWT 认证提供者
|
|
11
|
+
*/
|
|
12
|
+
export class JwtAuthenticationProvider implements AuthenticationProvider {
|
|
13
|
+
public readonly supportedTypes = ['jwt', 'bearer'];
|
|
14
|
+
|
|
15
|
+
public constructor(private readonly jwtUtil: JWTUtil) {}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 是否支持该认证类型
|
|
19
|
+
*/
|
|
20
|
+
public supports(type: string): boolean {
|
|
21
|
+
return this.supportedTypes.includes(type.toLowerCase());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 认证
|
|
26
|
+
*/
|
|
27
|
+
public async authenticate(
|
|
28
|
+
request: AuthenticationRequest,
|
|
29
|
+
): Promise<Authentication | null> {
|
|
30
|
+
const token = request.credentials as string;
|
|
31
|
+
if (!token) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 验证 JWT 令牌
|
|
36
|
+
const payload = this.jwtUtil.verify(token);
|
|
37
|
+
if (!payload) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 构建主体
|
|
42
|
+
const principal: Principal = {
|
|
43
|
+
id: payload.sub,
|
|
44
|
+
username: (payload.username as string) || payload.sub,
|
|
45
|
+
roles: (payload.roles as string[]) || [],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// 构建权限列表(从角色转换)
|
|
49
|
+
const authorities = principal.roles || [];
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
authenticated: true,
|
|
53
|
+
principal,
|
|
54
|
+
credentials: { type: 'jwt', data: token },
|
|
55
|
+
authorities,
|
|
56
|
+
details: payload,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Authentication,
|
|
3
|
+
AuthenticationProvider,
|
|
4
|
+
AuthenticationRequest,
|
|
5
|
+
Principal,
|
|
6
|
+
} from '../types';
|
|
7
|
+
import { OAuth2Service } from '../../auth/oauth2';
|
|
8
|
+
import { JWTUtil } from '../../auth/jwt';
|
|
9
|
+
import type { OAuth2TokenRequest } from '../../auth/types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* OAuth2 认证提供者
|
|
13
|
+
*/
|
|
14
|
+
export class OAuth2AuthenticationProvider implements AuthenticationProvider {
|
|
15
|
+
public readonly supportedTypes = ['oauth2', 'authorization_code'];
|
|
16
|
+
|
|
17
|
+
public constructor(
|
|
18
|
+
private readonly oauth2Service: OAuth2Service,
|
|
19
|
+
private readonly jwtUtil: JWTUtil,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 是否支持该认证类型
|
|
24
|
+
*/
|
|
25
|
+
public supports(type: string): boolean {
|
|
26
|
+
return this.supportedTypes.includes(type.toLowerCase());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 认证(通过授权码交换令牌)
|
|
31
|
+
*/
|
|
32
|
+
public async authenticate(
|
|
33
|
+
request: AuthenticationRequest,
|
|
34
|
+
): Promise<Authentication | null> {
|
|
35
|
+
const credentials = request.credentials as OAuth2TokenRequest;
|
|
36
|
+
if (!credentials || credentials.grantType !== 'authorization_code') {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 交换授权码获取令牌
|
|
41
|
+
const tokenResponse = await this.oauth2Service.exchangeCodeForToken(credentials);
|
|
42
|
+
if (!tokenResponse) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 从访问令牌中解析用户信息
|
|
47
|
+
const payload = this.jwtUtil.verify(tokenResponse.accessToken);
|
|
48
|
+
if (!payload || !payload.sub) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const principal: Principal = {
|
|
53
|
+
id: payload.sub,
|
|
54
|
+
username: (payload.username as string) || payload.sub,
|
|
55
|
+
roles: (payload.roles as string[]) || [],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
authenticated: true,
|
|
60
|
+
principal,
|
|
61
|
+
credentials: {
|
|
62
|
+
type: 'oauth2',
|
|
63
|
+
data: tokenResponse,
|
|
64
|
+
},
|
|
65
|
+
authorities: principal.roles || [],
|
|
66
|
+
details: tokenResponse,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { Module, MODULE_METADATA_KEY } from '../di/module';
|
|
2
|
+
import { AuthenticationManager } from './authentication-manager';
|
|
3
|
+
import { JwtAuthenticationProvider } from './providers/jwt-provider';
|
|
4
|
+
import { OAuth2AuthenticationProvider } from './providers/oauth2-provider';
|
|
5
|
+
import { createSecurityFilter } from './filter';
|
|
6
|
+
import { JWTUtil } from '../auth/jwt';
|
|
7
|
+
import { OAuth2Service } from '../auth/oauth2';
|
|
8
|
+
import { OAuth2Controller, OAUTH2_SERVICE_TOKEN, JWT_UTIL_TOKEN } from '../auth/controller';
|
|
9
|
+
import type { JWTConfig, OAuth2Client, UserInfo } from '../auth/types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 安全模块配置
|
|
13
|
+
*/
|
|
14
|
+
export interface SecurityModuleConfig {
|
|
15
|
+
/**
|
|
16
|
+
* JWT 配置
|
|
17
|
+
*/
|
|
18
|
+
jwt: JWTConfig;
|
|
19
|
+
/**
|
|
20
|
+
* OAuth2 客户端列表
|
|
21
|
+
*/
|
|
22
|
+
oauth2Clients?: OAuth2Client[];
|
|
23
|
+
/**
|
|
24
|
+
* 用户提供者(可选)
|
|
25
|
+
*/
|
|
26
|
+
userProvider?: {
|
|
27
|
+
findById(userId: string): Promise<UserInfo | null>;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* 是否启用 OAuth2 端点
|
|
31
|
+
* @default true
|
|
32
|
+
*/
|
|
33
|
+
enableOAuth2Endpoints?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* OAuth2 端点前缀
|
|
36
|
+
* @default '/oauth2'
|
|
37
|
+
*/
|
|
38
|
+
oauth2Prefix?: string;
|
|
39
|
+
/**
|
|
40
|
+
* 排除的路径列表(不需要认证)
|
|
41
|
+
*/
|
|
42
|
+
excludePaths?: string[];
|
|
43
|
+
/**
|
|
44
|
+
* 默认认证要求
|
|
45
|
+
* @default false
|
|
46
|
+
*/
|
|
47
|
+
defaultAuthRequired?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 安全模块
|
|
52
|
+
*/
|
|
53
|
+
@Module({
|
|
54
|
+
controllers: [],
|
|
55
|
+
providers: [],
|
|
56
|
+
middlewares: [],
|
|
57
|
+
})
|
|
58
|
+
export class SecurityModule {
|
|
59
|
+
/**
|
|
60
|
+
* 创建安全模块
|
|
61
|
+
* @param config - 模块配置
|
|
62
|
+
*/
|
|
63
|
+
public static forRoot(config: SecurityModuleConfig): typeof SecurityModule {
|
|
64
|
+
// 创建 JWT 工具
|
|
65
|
+
const jwtUtil = new JWTUtil(config.jwt);
|
|
66
|
+
|
|
67
|
+
// 创建 OAuth2 服务
|
|
68
|
+
const userProvider = config.userProvider
|
|
69
|
+
? async (userId: string) => config.userProvider!.findById(userId)
|
|
70
|
+
: undefined;
|
|
71
|
+
const oauth2Service = new OAuth2Service(
|
|
72
|
+
jwtUtil,
|
|
73
|
+
config.oauth2Clients || [],
|
|
74
|
+
{},
|
|
75
|
+
userProvider,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// 创建认证管理器
|
|
79
|
+
const authenticationManager = new AuthenticationManager();
|
|
80
|
+
authenticationManager.registerProvider(new JwtAuthenticationProvider(jwtUtil));
|
|
81
|
+
authenticationManager.registerProvider(
|
|
82
|
+
new OAuth2AuthenticationProvider(oauth2Service, jwtUtil),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// 创建安全过滤器
|
|
86
|
+
const securityFilter = createSecurityFilter({
|
|
87
|
+
authenticationManager,
|
|
88
|
+
excludePaths: [
|
|
89
|
+
...(config.excludePaths || []),
|
|
90
|
+
...(config.enableOAuth2Endpoints !== false
|
|
91
|
+
? [config.oauth2Prefix || '/oauth2']
|
|
92
|
+
: []),
|
|
93
|
+
],
|
|
94
|
+
defaultAuthRequired: config.defaultAuthRequired ?? false,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const controllers: any[] = [];
|
|
98
|
+
const providers: any[] = [];
|
|
99
|
+
const middlewares: any[] = [];
|
|
100
|
+
|
|
101
|
+
// 如果启用 OAuth2 端点,添加控制器
|
|
102
|
+
if (config.enableOAuth2Endpoints !== false) {
|
|
103
|
+
controllers.push(OAuth2Controller);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 注册服务提供者
|
|
107
|
+
providers.push(
|
|
108
|
+
{
|
|
109
|
+
provide: JWT_UTIL_TOKEN,
|
|
110
|
+
useValue: jwtUtil,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
provide: OAUTH2_SERVICE_TOKEN,
|
|
114
|
+
useValue: oauth2Service,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
provide: AuthenticationManager,
|
|
118
|
+
useValue: authenticationManager,
|
|
119
|
+
},
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// 添加安全过滤器中间件
|
|
123
|
+
middlewares.push(securityFilter);
|
|
124
|
+
|
|
125
|
+
// 动态更新模块元数据
|
|
126
|
+
const existingMetadata = Reflect.getMetadata(MODULE_METADATA_KEY, SecurityModule) || {};
|
|
127
|
+
const metadata = {
|
|
128
|
+
...existingMetadata,
|
|
129
|
+
controllers: [...(existingMetadata.controllers || []), ...controllers],
|
|
130
|
+
providers: [...(existingMetadata.providers || []), ...providers],
|
|
131
|
+
middlewares: [...(existingMetadata.middlewares || []), ...middlewares],
|
|
132
|
+
// 导出服务,让其他模块可以使用
|
|
133
|
+
exports: [
|
|
134
|
+
...(existingMetadata.exports || []),
|
|
135
|
+
JWT_UTIL_TOKEN,
|
|
136
|
+
OAUTH2_SERVICE_TOKEN,
|
|
137
|
+
AuthenticationManager,
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, SecurityModule);
|
|
141
|
+
|
|
142
|
+
return SecurityModule;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security 核心类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 认证主体(Principal)
|
|
7
|
+
*/
|
|
8
|
+
export interface Principal {
|
|
9
|
+
/**
|
|
10
|
+
* 用户 ID
|
|
11
|
+
*/
|
|
12
|
+
id: string;
|
|
13
|
+
/**
|
|
14
|
+
* 用户名
|
|
15
|
+
*/
|
|
16
|
+
username?: string;
|
|
17
|
+
/**
|
|
18
|
+
* 角色列表
|
|
19
|
+
*/
|
|
20
|
+
roles?: string[];
|
|
21
|
+
/**
|
|
22
|
+
* 其他属性
|
|
23
|
+
*/
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 认证凭据(Credentials)
|
|
29
|
+
*/
|
|
30
|
+
export interface Credentials {
|
|
31
|
+
/**
|
|
32
|
+
* 凭据类型
|
|
33
|
+
*/
|
|
34
|
+
type: string;
|
|
35
|
+
/**
|
|
36
|
+
* 凭据数据
|
|
37
|
+
*/
|
|
38
|
+
data: unknown;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 认证信息
|
|
43
|
+
*/
|
|
44
|
+
export interface Authentication {
|
|
45
|
+
/**
|
|
46
|
+
* 是否已认证
|
|
47
|
+
*/
|
|
48
|
+
authenticated: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* 主体
|
|
51
|
+
*/
|
|
52
|
+
principal: Principal | null;
|
|
53
|
+
/**
|
|
54
|
+
* 凭据
|
|
55
|
+
*/
|
|
56
|
+
credentials: Credentials | null;
|
|
57
|
+
/**
|
|
58
|
+
* 权限列表
|
|
59
|
+
*/
|
|
60
|
+
authorities: string[];
|
|
61
|
+
/**
|
|
62
|
+
* 详细信息
|
|
63
|
+
*/
|
|
64
|
+
details?: unknown;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 认证请求
|
|
69
|
+
*/
|
|
70
|
+
export interface AuthenticationRequest {
|
|
71
|
+
/**
|
|
72
|
+
* 主体标识(如用户名)
|
|
73
|
+
*/
|
|
74
|
+
principal: string;
|
|
75
|
+
/**
|
|
76
|
+
* 凭据(如密码)
|
|
77
|
+
*/
|
|
78
|
+
credentials: unknown;
|
|
79
|
+
/**
|
|
80
|
+
* 认证类型
|
|
81
|
+
*/
|
|
82
|
+
type?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 认证提供者接口
|
|
87
|
+
*/
|
|
88
|
+
export interface AuthenticationProvider {
|
|
89
|
+
/**
|
|
90
|
+
* 支持的认证类型
|
|
91
|
+
*/
|
|
92
|
+
readonly supportedTypes: string[];
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 认证
|
|
96
|
+
* @param request - 认证请求
|
|
97
|
+
* @returns 认证信息
|
|
98
|
+
*/
|
|
99
|
+
authenticate(request: AuthenticationRequest): Promise<Authentication | null>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 是否支持该认证类型
|
|
103
|
+
* @param type - 认证类型
|
|
104
|
+
*/
|
|
105
|
+
supports(type: string): boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 授权决策器接口
|
|
110
|
+
*/
|
|
111
|
+
export interface AccessDecisionManager {
|
|
112
|
+
/**
|
|
113
|
+
* 决定是否授权
|
|
114
|
+
* @param authentication - 认证信息
|
|
115
|
+
* @param requiredAuthorities - 需要的权限
|
|
116
|
+
* @returns 是否授权
|
|
117
|
+
*/
|
|
118
|
+
decide(
|
|
119
|
+
authentication: Authentication,
|
|
120
|
+
requiredAuthorities: string[],
|
|
121
|
+
): boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 安全配置
|
|
126
|
+
*/
|
|
127
|
+
export interface SecurityConfig {
|
|
128
|
+
/**
|
|
129
|
+
* 是否启用安全
|
|
130
|
+
* @default true
|
|
131
|
+
*/
|
|
132
|
+
enabled?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* 排除的路径列表
|
|
135
|
+
*/
|
|
136
|
+
excludePaths?: string[];
|
|
137
|
+
/**
|
|
138
|
+
* 默认认证要求
|
|
139
|
+
* @default true
|
|
140
|
+
*/
|
|
141
|
+
defaultAuthRequired?: boolean;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 安全上下文
|
|
146
|
+
*/
|
|
147
|
+
export interface SecurityContext {
|
|
148
|
+
/**
|
|
149
|
+
* 当前认证信息
|
|
150
|
+
*/
|
|
151
|
+
authentication: Authentication | null;
|
|
152
|
+
/**
|
|
153
|
+
* 是否已认证
|
|
154
|
+
*/
|
|
155
|
+
isAuthenticated(): boolean;
|
|
156
|
+
/**
|
|
157
|
+
* 获取主体
|
|
158
|
+
*/
|
|
159
|
+
getPrincipal(): Principal | null;
|
|
160
|
+
/**
|
|
161
|
+
* 获取权限
|
|
162
|
+
*/
|
|
163
|
+
getAuthorities(): string[];
|
|
164
|
+
}
|
|
165
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import type { Context } from '../core/context';
|
|
3
|
+
import { SessionService } from './service';
|
|
4
|
+
import { SESSION_SERVICE_TOKEN } from './types';
|
|
5
|
+
import { Container } from '../di/container';
|
|
6
|
+
import { ParamType, createParamDecorator } from '../controller/decorators';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Session 参数装饰器
|
|
10
|
+
* 用于在控制器方法中注入 Session 对象
|
|
11
|
+
*/
|
|
12
|
+
export function Session() {
|
|
13
|
+
return createParamDecorator(ParamType.SESSION);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 获取 Session 对象(用于参数绑定)
|
|
18
|
+
* @param context - 请求上下文
|
|
19
|
+
* @param container - DI 容器
|
|
20
|
+
* @returns Session 对象,如果不存在则返回 undefined
|
|
21
|
+
*/
|
|
22
|
+
export async function getSessionFromContext(
|
|
23
|
+
context: Context,
|
|
24
|
+
container: Container,
|
|
25
|
+
): Promise<unknown | undefined> {
|
|
26
|
+
// 从 Context 中获取 Session(由中间件设置)
|
|
27
|
+
const session = (context as unknown as { session?: unknown }).session;
|
|
28
|
+
if (session) {
|
|
29
|
+
return session;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 如果没有 Session,尝试创建新 Session
|
|
33
|
+
const sessionService = container.resolve<SessionService>(
|
|
34
|
+
SESSION_SERVICE_TOKEN,
|
|
35
|
+
);
|
|
36
|
+
if (sessionService) {
|
|
37
|
+
const newSession = await sessionService.create();
|
|
38
|
+
(context as unknown as { session: typeof newSession }).session =
|
|
39
|
+
newSession;
|
|
40
|
+
(context as unknown as { sessionId: string }).sessionId = newSession.id;
|
|
41
|
+
return newSession;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { SessionModule } from './session-module';
|
|
2
|
+
export { SessionService } from './service';
|
|
3
|
+
export { createSessionMiddleware } from './middleware';
|
|
4
|
+
export { Session as SessionDecorator, getSessionFromContext } from './decorators';
|
|
5
|
+
export {
|
|
6
|
+
MemorySessionStore,
|
|
7
|
+
RedisSessionStore,
|
|
8
|
+
SESSION_SERVICE_TOKEN,
|
|
9
|
+
SESSION_OPTIONS_TOKEN,
|
|
10
|
+
} from './types';
|
|
11
|
+
export type {
|
|
12
|
+
SessionStore,
|
|
13
|
+
} from './types';
|
|
14
|
+
export type {
|
|
15
|
+
SessionModuleOptions,
|
|
16
|
+
Session,
|
|
17
|
+
SessionData,
|
|
18
|
+
RedisSessionStoreOptions,
|
|
19
|
+
} from './types';
|