@dangao/bun-server 1.7.0 → 1.8.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/README.md +196 -19
- package/dist/cache/cache-module.d.ts +18 -0
- package/dist/cache/cache-module.d.ts.map +1 -1
- package/dist/cache/index.d.ts +3 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/interceptors.d.ts +41 -0
- package/dist/cache/interceptors.d.ts.map +1 -0
- package/dist/cache/service-proxy.d.ts +62 -0
- package/dist/cache/service-proxy.d.ts.map +1 -0
- package/dist/controller/controller.d.ts +8 -0
- package/dist/controller/controller.d.ts.map +1 -1
- package/dist/core/application.d.ts +5 -0
- package/dist/core/application.d.ts.map +1 -1
- package/dist/di/container.d.ts +18 -1
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/decorators.d.ts +37 -0
- package/dist/di/decorators.d.ts.map +1 -1
- package/dist/di/index.d.ts +2 -2
- package/dist/di/index.d.ts.map +1 -1
- package/dist/di/module-registry.d.ts +17 -0
- package/dist/di/module-registry.d.ts.map +1 -1
- package/dist/di/types.d.ts +22 -0
- package/dist/di/types.d.ts.map +1 -1
- package/dist/events/decorators.d.ts +52 -0
- package/dist/events/decorators.d.ts.map +1 -0
- package/dist/events/event-module.d.ts +97 -0
- package/dist/events/event-module.d.ts.map +1 -0
- package/dist/events/index.d.ts +5 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/service.d.ts +76 -0
- package/dist/events/service.d.ts.map +1 -0
- package/dist/events/types.d.ts +184 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4641 -2840
- package/dist/security/filter.d.ts +23 -0
- package/dist/security/filter.d.ts.map +1 -1
- package/dist/security/guards/builtin/auth-guard.d.ts +44 -0
- package/dist/security/guards/builtin/auth-guard.d.ts.map +1 -0
- package/dist/security/guards/builtin/index.d.ts +3 -0
- package/dist/security/guards/builtin/index.d.ts.map +1 -0
- package/dist/security/guards/builtin/roles-guard.d.ts +66 -0
- package/dist/security/guards/builtin/roles-guard.d.ts.map +1 -0
- package/dist/security/guards/decorators.d.ts +50 -0
- package/dist/security/guards/decorators.d.ts.map +1 -0
- package/dist/security/guards/execution-context.d.ts +56 -0
- package/dist/security/guards/execution-context.d.ts.map +1 -0
- package/dist/security/guards/guard-registry.d.ts +67 -0
- package/dist/security/guards/guard-registry.d.ts.map +1 -0
- package/dist/security/guards/index.d.ts +7 -0
- package/dist/security/guards/index.d.ts.map +1 -0
- package/dist/security/guards/reflector.d.ts +57 -0
- package/dist/security/guards/reflector.d.ts.map +1 -0
- package/dist/security/guards/types.d.ts +126 -0
- package/dist/security/guards/types.d.ts.map +1 -0
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/security-module.d.ts +20 -0
- package/dist/security/security-module.d.ts.map +1 -1
- package/dist/validation/class-validator.d.ts +108 -0
- package/dist/validation/class-validator.d.ts.map +1 -0
- package/dist/validation/custom-validator.d.ts +130 -0
- package/dist/validation/custom-validator.d.ts.map +1 -0
- package/dist/validation/errors.d.ts +22 -2
- package/dist/validation/errors.d.ts.map +1 -1
- package/dist/validation/index.d.ts +7 -1
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/rules/array.d.ts +33 -0
- package/dist/validation/rules/array.d.ts.map +1 -0
- package/dist/validation/rules/common.d.ts +90 -0
- package/dist/validation/rules/common.d.ts.map +1 -0
- package/dist/validation/rules/conditional.d.ts +30 -0
- package/dist/validation/rules/conditional.d.ts.map +1 -0
- package/dist/validation/rules/index.d.ts +5 -0
- package/dist/validation/rules/index.d.ts.map +1 -0
- package/dist/validation/rules/object.d.ts +30 -0
- package/dist/validation/rules/object.d.ts.map +1 -0
- package/dist/validation/types.d.ts +52 -1
- package/dist/validation/types.d.ts.map +1 -1
- package/docs/events.md +494 -0
- package/docs/guards.md +376 -0
- package/docs/guide.md +309 -1
- package/docs/request-lifecycle.md +444 -0
- package/docs/symbol-interface-pattern.md +431 -0
- package/docs/validation.md +407 -0
- package/docs/zh/events.md +494 -0
- package/docs/zh/guards.md +376 -0
- package/docs/zh/guide.md +309 -1
- package/docs/zh/request-lifecycle.md +444 -0
- package/docs/zh/symbol-interface-pattern.md +431 -0
- package/docs/zh/validation.md +407 -0
- package/package.json +1 -1
- package/src/cache/cache-module.ts +37 -0
- package/src/cache/index.ts +16 -1
- package/src/cache/interceptors.ts +295 -0
- package/src/cache/service-proxy.ts +219 -0
- package/src/controller/controller.ts +30 -6
- package/src/core/application.ts +25 -1
- package/src/di/container.ts +57 -7
- package/src/di/decorators.ts +46 -0
- package/src/di/index.ts +17 -2
- package/src/di/module-registry.ts +39 -0
- package/src/di/types.ts +29 -0
- package/src/events/decorators.ts +103 -0
- package/src/events/event-module.ts +272 -0
- package/src/events/index.ts +32 -0
- package/src/events/service.ts +352 -0
- package/src/events/types.ts +223 -0
- package/src/index.ts +140 -1
- package/src/security/filter.ts +88 -8
- package/src/security/guards/builtin/auth-guard.ts +68 -0
- package/src/security/guards/builtin/index.ts +3 -0
- package/src/security/guards/builtin/roles-guard.ts +165 -0
- package/src/security/guards/decorators.ts +124 -0
- package/src/security/guards/execution-context.ts +152 -0
- package/src/security/guards/guard-registry.ts +164 -0
- package/src/security/guards/index.ts +7 -0
- package/src/security/guards/reflector.ts +99 -0
- package/src/security/guards/types.ts +144 -0
- package/src/security/index.ts +1 -0
- package/src/security/security-module.ts +72 -2
- package/src/validation/class-validator.ts +322 -0
- package/src/validation/custom-validator.ts +289 -0
- package/src/validation/errors.ts +50 -2
- package/src/validation/index.ts +103 -1
- package/src/validation/rules/array.ts +118 -0
- package/src/validation/rules/common.ts +286 -0
- package/src/validation/rules/conditional.ts +52 -0
- package/src/validation/rules/index.ts +51 -0
- package/src/validation/rules/object.ts +86 -0
- package/src/validation/types.ts +61 -1
- package/tests/cache/cache-decorators.test.ts +284 -0
- package/tests/controller/path-combination.test.ts +353 -0
- package/tests/di/global-module.test.ts +487 -0
- package/tests/events/event-decorators.test.ts +173 -0
- package/tests/events/event-emitter.test.ts +373 -0
- package/tests/events/event-module.test.ts +373 -0
- package/tests/security/guards/guards-integration.test.ts +371 -0
- package/tests/security/guards/guards.test.ts +775 -0
- package/tests/security/security-module.test.ts +2 -2
- package/tests/validation/class-validator.test.ts +349 -0
- package/tests/validation/custom-validator.test.ts +335 -0
- package/tests/validation/rules.test.ts +543 -0
package/src/index.ts
CHANGED
|
@@ -25,7 +25,7 @@ export type {
|
|
|
25
25
|
ControllerMetadata,
|
|
26
26
|
} from './controller';
|
|
27
27
|
export { Container } from './di/container';
|
|
28
|
-
export { Injectable, Inject } from './di/decorators';
|
|
28
|
+
export { Injectable, Inject, Global, isGlobalModule, GLOBAL_MODULE_METADATA_KEY } from './di/decorators';
|
|
29
29
|
export { Lifecycle, type ProviderConfig, type DependencyMetadata } from './di/types';
|
|
30
30
|
export {
|
|
31
31
|
Module,
|
|
@@ -71,15 +71,97 @@ export {
|
|
|
71
71
|
type RateLimitOptions,
|
|
72
72
|
type RateLimitStore,
|
|
73
73
|
} from './middleware/builtin';
|
|
74
|
+
// Validation 模块
|
|
74
75
|
export {
|
|
76
|
+
// 基础装饰器
|
|
75
77
|
Validate,
|
|
76
78
|
IsString,
|
|
77
79
|
IsNumber,
|
|
78
80
|
IsEmail,
|
|
79
81
|
IsOptional,
|
|
80
82
|
MinLength,
|
|
83
|
+
getValidationMetadata,
|
|
84
|
+
// 类型
|
|
85
|
+
type ValidationRuleDefinition,
|
|
86
|
+
type ValidationMetadata,
|
|
87
|
+
type ClassValidationMetadata,
|
|
88
|
+
type ValidationOptions,
|
|
89
|
+
// 验证器
|
|
90
|
+
validateParameters,
|
|
91
|
+
// 错误处理
|
|
81
92
|
ValidationError,
|
|
82
93
|
type ValidationIssue,
|
|
94
|
+
// 对象规则
|
|
95
|
+
IsObject,
|
|
96
|
+
IsNotEmpty,
|
|
97
|
+
IsNotEmptyObject,
|
|
98
|
+
ValidateNested,
|
|
99
|
+
type ObjectRuleOptions,
|
|
100
|
+
type ValidateNestedOptions,
|
|
101
|
+
// 数组规则
|
|
102
|
+
IsArray,
|
|
103
|
+
ArrayMinSize,
|
|
104
|
+
ArrayMaxSize,
|
|
105
|
+
ArrayUnique,
|
|
106
|
+
ArrayContains,
|
|
107
|
+
ArrayNotContains,
|
|
108
|
+
ArrayNotEmpty,
|
|
109
|
+
type ArrayRuleOptions,
|
|
110
|
+
// 通用规则
|
|
111
|
+
IsBoolean,
|
|
112
|
+
IsInt,
|
|
113
|
+
IsPositive,
|
|
114
|
+
IsNegative,
|
|
115
|
+
Min,
|
|
116
|
+
Max,
|
|
117
|
+
IsDate,
|
|
118
|
+
IsUUID,
|
|
119
|
+
Length,
|
|
120
|
+
MaxLength,
|
|
121
|
+
Matches,
|
|
122
|
+
IsIn,
|
|
123
|
+
IsNotIn,
|
|
124
|
+
IsUrl,
|
|
125
|
+
IsJSON,
|
|
126
|
+
Equals,
|
|
127
|
+
NotEquals,
|
|
128
|
+
IsDefined,
|
|
129
|
+
IsAlphanumeric,
|
|
130
|
+
IsAlpha,
|
|
131
|
+
IsNumberString,
|
|
132
|
+
type RuleOptions,
|
|
133
|
+
type UUIDVersion,
|
|
134
|
+
// 条件和转换规则
|
|
135
|
+
ValidateIf,
|
|
136
|
+
Transform,
|
|
137
|
+
type ConditionalRuleOptions,
|
|
138
|
+
// 自定义验证器
|
|
139
|
+
createCustomValidator,
|
|
140
|
+
createSimpleValidator,
|
|
141
|
+
createRegexValidator,
|
|
142
|
+
IsPhoneNumber,
|
|
143
|
+
IsIdCard,
|
|
144
|
+
IsIPv4,
|
|
145
|
+
IsPort,
|
|
146
|
+
IsPostalCode,
|
|
147
|
+
IsCreditCard,
|
|
148
|
+
IsHexColor,
|
|
149
|
+
IsMacAddress,
|
|
150
|
+
IsSemVer,
|
|
151
|
+
IsDivisibleBy,
|
|
152
|
+
IsBetween,
|
|
153
|
+
Contains,
|
|
154
|
+
NotContains,
|
|
155
|
+
type CustomValidatorOptions,
|
|
156
|
+
// 类级别验证
|
|
157
|
+
ValidateClass,
|
|
158
|
+
Property,
|
|
159
|
+
NestedProperty,
|
|
160
|
+
ArrayNestedProperty,
|
|
161
|
+
validateObject,
|
|
162
|
+
validateObjectSync,
|
|
163
|
+
getClassValidationMetadata,
|
|
164
|
+
isValidateClass,
|
|
83
165
|
} from './validation';
|
|
84
166
|
export {
|
|
85
167
|
HttpException,
|
|
@@ -136,6 +218,8 @@ export {
|
|
|
136
218
|
JwtAuthenticationProvider,
|
|
137
219
|
OAuth2AuthenticationProvider,
|
|
138
220
|
createSecurityFilter,
|
|
221
|
+
getGuardRegistry,
|
|
222
|
+
registerReflector,
|
|
139
223
|
type SecurityModuleConfig,
|
|
140
224
|
type SecurityConfig,
|
|
141
225
|
type SecurityContext,
|
|
@@ -146,6 +230,32 @@ export {
|
|
|
146
230
|
type Credentials,
|
|
147
231
|
type AccessDecisionManager,
|
|
148
232
|
} from './security';
|
|
233
|
+
// Guards 子模块
|
|
234
|
+
export {
|
|
235
|
+
UseGuards,
|
|
236
|
+
Roles,
|
|
237
|
+
getGuardsMetadata,
|
|
238
|
+
getRolesMetadata,
|
|
239
|
+
AuthGuard,
|
|
240
|
+
OptionalAuthGuard,
|
|
241
|
+
RolesGuard,
|
|
242
|
+
createRolesGuard,
|
|
243
|
+
GuardRegistry,
|
|
244
|
+
ExecutionContextImpl,
|
|
245
|
+
Reflector,
|
|
246
|
+
GUARDS_METADATA_KEY,
|
|
247
|
+
GUARD_REGISTRY_TOKEN,
|
|
248
|
+
ROLES_METADATA_KEY,
|
|
249
|
+
REFLECTOR_TOKEN,
|
|
250
|
+
type CanActivate,
|
|
251
|
+
type ExecutionContext,
|
|
252
|
+
type HttpArgumentsHost,
|
|
253
|
+
type WsArgumentsHost,
|
|
254
|
+
type GuardType,
|
|
255
|
+
type GuardMetadata,
|
|
256
|
+
type GuardOptions,
|
|
257
|
+
type RolesGuardOptions,
|
|
258
|
+
} from './security/guards';
|
|
149
259
|
export {
|
|
150
260
|
ConfigModule,
|
|
151
261
|
ConfigService,
|
|
@@ -254,10 +364,17 @@ export {
|
|
|
254
364
|
Cacheable,
|
|
255
365
|
CacheEvict,
|
|
256
366
|
CachePut,
|
|
367
|
+
EnableCacheProxy,
|
|
368
|
+
CacheServiceProxy,
|
|
369
|
+
CachePostProcessor,
|
|
370
|
+
CacheableInterceptor,
|
|
371
|
+
CacheEvictInterceptor,
|
|
372
|
+
CachePutInterceptor,
|
|
257
373
|
MemoryCacheStore,
|
|
258
374
|
RedisCacheStore,
|
|
259
375
|
CACHE_SERVICE_TOKEN,
|
|
260
376
|
CACHE_OPTIONS_TOKEN,
|
|
377
|
+
CACHE_POST_PROCESSOR_TOKEN,
|
|
261
378
|
} from './cache';
|
|
262
379
|
export type {
|
|
263
380
|
CacheModuleOptions,
|
|
@@ -387,4 +504,26 @@ export {
|
|
|
387
504
|
type ServiceInstanceHealth,
|
|
388
505
|
type MonitoringOptions,
|
|
389
506
|
} from './microservice';
|
|
507
|
+
// Events 模块
|
|
508
|
+
export {
|
|
509
|
+
EventModule,
|
|
510
|
+
EventEmitterService,
|
|
511
|
+
EventListenerScanner,
|
|
512
|
+
OnEvent,
|
|
513
|
+
getOnEventMetadata,
|
|
514
|
+
isEventListenerClass,
|
|
515
|
+
EVENT_EMITTER_TOKEN,
|
|
516
|
+
EVENT_OPTIONS_TOKEN,
|
|
517
|
+
EVENT_LISTENER_SCANNER_TOKEN,
|
|
518
|
+
ON_EVENT_METADATA_KEY,
|
|
519
|
+
EVENT_LISTENER_CLASS_METADATA_KEY,
|
|
520
|
+
type EventEmitter,
|
|
521
|
+
type EventListener,
|
|
522
|
+
type EventMetadata,
|
|
523
|
+
type EventModuleOptions,
|
|
524
|
+
type ListenerOptions,
|
|
525
|
+
type OnEventMethodMetadata,
|
|
526
|
+
type OnEventOptions,
|
|
527
|
+
type RegisteredListener,
|
|
528
|
+
} from './events';
|
|
390
529
|
|
package/src/security/filter.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Context } from '../core/context';
|
|
2
2
|
import type { Middleware } from '../middleware';
|
|
3
|
+
import type { Container } from '../di/container';
|
|
3
4
|
import { SecurityContextHolder } from './context';
|
|
4
5
|
import { AuthenticationManager } from './authentication-manager';
|
|
5
6
|
import { RoleBasedAccessDecisionManager } from './access-decision-manager';
|
|
@@ -10,6 +11,10 @@ import {
|
|
|
10
11
|
} from '../error/http-exception';
|
|
11
12
|
import { ErrorCode } from '../error/error-codes';
|
|
12
13
|
import { requiresAuth, getAuthMetadata } from '../auth/decorators';
|
|
14
|
+
import { GuardRegistry } from './guards/guard-registry';
|
|
15
|
+
import { ExecutionContextImpl } from './guards/execution-context';
|
|
16
|
+
import { Reflector, REFLECTOR_TOKEN } from './guards/reflector';
|
|
17
|
+
import { GUARD_REGISTRY_TOKEN } from './guards/types';
|
|
13
18
|
|
|
14
19
|
/**
|
|
15
20
|
* 安全过滤器配置
|
|
@@ -27,6 +32,14 @@ export interface SecurityFilterConfig extends SecurityConfig {
|
|
|
27
32
|
* 令牌提取函数
|
|
28
33
|
*/
|
|
29
34
|
extractToken?: (ctx: Context) => string | null;
|
|
35
|
+
/**
|
|
36
|
+
* DI 容器(用于解析守卫)
|
|
37
|
+
*/
|
|
38
|
+
container?: Container;
|
|
39
|
+
/**
|
|
40
|
+
* 守卫注册表
|
|
41
|
+
*/
|
|
42
|
+
guardRegistry?: GuardRegistry;
|
|
30
43
|
}
|
|
31
44
|
|
|
32
45
|
/**
|
|
@@ -39,8 +52,30 @@ export function createSecurityFilter(config: SecurityFilterConfig): Middleware {
|
|
|
39
52
|
excludePaths = [],
|
|
40
53
|
defaultAuthRequired = true,
|
|
41
54
|
extractToken,
|
|
55
|
+
container: initialContainer,
|
|
56
|
+
guardRegistry,
|
|
42
57
|
} = config;
|
|
43
58
|
|
|
59
|
+
// 创建或使用传入的守卫注册表
|
|
60
|
+
const registry = guardRegistry || new GuardRegistry();
|
|
61
|
+
|
|
62
|
+
// 延迟获取容器的函数
|
|
63
|
+
let cachedContainer: Container | null = initialContainer || null;
|
|
64
|
+
const getContainer = (): Container | null => {
|
|
65
|
+
if (cachedContainer) {
|
|
66
|
+
return cachedContainer;
|
|
67
|
+
}
|
|
68
|
+
// 尝试从 ControllerRegistry 获取容器
|
|
69
|
+
try {
|
|
70
|
+
// 动态导入避免循环依赖
|
|
71
|
+
const { ControllerRegistry } = require('../controller/controller');
|
|
72
|
+
cachedContainer = ControllerRegistry.getInstance().getContainer();
|
|
73
|
+
return cachedContainer;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
44
79
|
return async (ctx: Context, next) => {
|
|
45
80
|
return SecurityContextHolder.runWithContext(async () => {
|
|
46
81
|
// 检查是否在排除列表中
|
|
@@ -74,6 +109,14 @@ export function createSecurityFilter(config: SecurityFilterConfig): Middleware {
|
|
|
74
109
|
}
|
|
75
110
|
}
|
|
76
111
|
|
|
112
|
+
// 将安全上下文附加到 Context(在守卫执行前)
|
|
113
|
+
(ctx as any).security = securityContext;
|
|
114
|
+
(ctx as any).auth = {
|
|
115
|
+
isAuthenticated: securityContext.isAuthenticated(),
|
|
116
|
+
user: securityContext.getPrincipal(),
|
|
117
|
+
payload: (securityContext.authentication?.details as any),
|
|
118
|
+
};
|
|
119
|
+
|
|
77
120
|
// 检查是否需要认证
|
|
78
121
|
const handler = (ctx as any).routeHandler;
|
|
79
122
|
if (handler) {
|
|
@@ -82,6 +125,23 @@ export function createSecurityFilter(config: SecurityFilterConfig): Middleware {
|
|
|
82
125
|
(controllerClass && controllerClass.prototype) || controllerClass;
|
|
83
126
|
const method = handler.method;
|
|
84
127
|
|
|
128
|
+
// 执行守卫链(在 @Auth 检查之前)
|
|
129
|
+
// Guards 在中间件之后、拦截器之前执行
|
|
130
|
+
const container = getContainer();
|
|
131
|
+
// 只有当 controllerClass 是一个有效的类(构造函数)时才执行守卫
|
|
132
|
+
// 测试中可能传入原型而不是类,这种情况跳过守卫执行
|
|
133
|
+
if (container && typeof controllerClass === 'function') {
|
|
134
|
+
const methodHandler = controllerTarget[method];
|
|
135
|
+
const executionContext = new ExecutionContextImpl(
|
|
136
|
+
ctx,
|
|
137
|
+
controllerClass,
|
|
138
|
+
method,
|
|
139
|
+
methodHandler || (() => {}),
|
|
140
|
+
);
|
|
141
|
+
await registry.executeGuards(executionContext, container);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 传统的 @Auth 装饰器检查(向后兼容)
|
|
85
145
|
if (requiresAuth(controllerTarget, method)) {
|
|
86
146
|
const authentication = securityContext.authentication;
|
|
87
147
|
if (!authentication || !authentication.authenticated) {
|
|
@@ -118,14 +178,6 @@ export function createSecurityFilter(config: SecurityFilterConfig): Middleware {
|
|
|
118
178
|
);
|
|
119
179
|
}
|
|
120
180
|
|
|
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
181
|
return await next();
|
|
130
182
|
} finally {
|
|
131
183
|
// 清理当前请求内的认证信息,防止泄漏到下一个请求
|
|
@@ -135,6 +187,34 @@ export function createSecurityFilter(config: SecurityFilterConfig): Middleware {
|
|
|
135
187
|
};
|
|
136
188
|
}
|
|
137
189
|
|
|
190
|
+
/**
|
|
191
|
+
* 获取守卫注册表
|
|
192
|
+
* @param container - DI 容器
|
|
193
|
+
* @returns 守卫注册表实例
|
|
194
|
+
*/
|
|
195
|
+
export function getGuardRegistry(container: Container): GuardRegistry {
|
|
196
|
+
if (container.isRegistered(GUARD_REGISTRY_TOKEN)) {
|
|
197
|
+
return container.resolve<GuardRegistry>(GUARD_REGISTRY_TOKEN);
|
|
198
|
+
}
|
|
199
|
+
const registry = new GuardRegistry();
|
|
200
|
+
container.registerInstance(GUARD_REGISTRY_TOKEN, registry);
|
|
201
|
+
return registry;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 注册 Reflector 到 DI 容器
|
|
206
|
+
* @param container - DI 容器
|
|
207
|
+
* @returns Reflector 实例
|
|
208
|
+
*/
|
|
209
|
+
export function registerReflector(container: Container): Reflector {
|
|
210
|
+
if (container.isRegistered(REFLECTOR_TOKEN)) {
|
|
211
|
+
return container.resolve<Reflector>(REFLECTOR_TOKEN);
|
|
212
|
+
}
|
|
213
|
+
const reflector = new Reflector();
|
|
214
|
+
container.registerInstance(REFLECTOR_TOKEN, reflector);
|
|
215
|
+
return reflector;
|
|
216
|
+
}
|
|
217
|
+
|
|
138
218
|
/**
|
|
139
219
|
* 从请求头提取令牌
|
|
140
220
|
*/
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Injectable } from '../../../di/decorators';
|
|
2
|
+
import type { CanActivate, ExecutionContext } from '../types';
|
|
3
|
+
import { SecurityContextHolder } from '../../context';
|
|
4
|
+
import { UnauthorizedException } from '../../../error/http-exception';
|
|
5
|
+
import { ErrorCode } from '../../../error/error-codes';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 认证守卫
|
|
9
|
+
* 检查请求是否已认证
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* @Controller('/api/users')
|
|
13
|
+
* @UseGuards(AuthGuard)
|
|
14
|
+
* class UserController {
|
|
15
|
+
* @GET('/profile')
|
|
16
|
+
* getProfile() {
|
|
17
|
+
* // 只有已认证的用户才能访问
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
@Injectable()
|
|
22
|
+
export class AuthGuard implements CanActivate {
|
|
23
|
+
/**
|
|
24
|
+
* 判断是否允许访问
|
|
25
|
+
* @param context - 执行上下文
|
|
26
|
+
* @returns 如果已认证则返回 true,否则抛出 UnauthorizedException
|
|
27
|
+
*/
|
|
28
|
+
public canActivate(context: ExecutionContext): boolean {
|
|
29
|
+
const securityContext = SecurityContextHolder.getContext();
|
|
30
|
+
|
|
31
|
+
if (!securityContext.isAuthenticated()) {
|
|
32
|
+
throw new UnauthorizedException(
|
|
33
|
+
'Authentication required',
|
|
34
|
+
undefined,
|
|
35
|
+
ErrorCode.AUTH_REQUIRED,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 可选认证守卫
|
|
45
|
+
* 如果有 token 则验证,没有 token 也允许访问
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* @GET('/public')
|
|
49
|
+
* @UseGuards(OptionalAuthGuard)
|
|
50
|
+
* publicEndpoint() {
|
|
51
|
+
* // 未认证也可以访问,但如果有 token 会被验证
|
|
52
|
+
* }
|
|
53
|
+
*/
|
|
54
|
+
@Injectable()
|
|
55
|
+
export class OptionalAuthGuard implements CanActivate {
|
|
56
|
+
/**
|
|
57
|
+
* 判断是否允许访问
|
|
58
|
+
* 总是返回 true,但会检查认证状态
|
|
59
|
+
* @param context - 执行上下文
|
|
60
|
+
* @returns 总是返回 true
|
|
61
|
+
*/
|
|
62
|
+
public canActivate(context: ExecutionContext): boolean {
|
|
63
|
+
// 可选认证:不强制要求认证,但如果有认证信息会被使用
|
|
64
|
+
// SecurityFilter 已经处理了 token 解析和认证
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { Injectable, Inject } from '../../../di/decorators';
|
|
2
|
+
import type { CanActivate, ExecutionContext } from '../types';
|
|
3
|
+
import { ROLES_METADATA_KEY } from '../types';
|
|
4
|
+
import { SecurityContextHolder } from '../../context';
|
|
5
|
+
import { ForbiddenException } from '../../../error/http-exception';
|
|
6
|
+
import { ErrorCode } from '../../../error/error-codes';
|
|
7
|
+
import { Reflector, REFLECTOR_TOKEN } from '../reflector';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 角色守卫
|
|
11
|
+
* 检查用户是否具有所需角色
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* @Controller('/api/admin')
|
|
15
|
+
* @UseGuards(AuthGuard, RolesGuard)
|
|
16
|
+
* class AdminController {
|
|
17
|
+
* @GET('/dashboard')
|
|
18
|
+
* @Roles('admin')
|
|
19
|
+
* dashboard() {
|
|
20
|
+
* // 只有 admin 角色才能访问
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* @GET('/super')
|
|
24
|
+
* @Roles('admin', 'superadmin')
|
|
25
|
+
* superAdmin() {
|
|
26
|
+
* // admin 或 superadmin 角色可以访问
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
*/
|
|
30
|
+
@Injectable()
|
|
31
|
+
export class RolesGuard implements CanActivate {
|
|
32
|
+
private readonly reflector: Reflector;
|
|
33
|
+
|
|
34
|
+
public constructor(
|
|
35
|
+
@Inject(REFLECTOR_TOKEN) reflector?: Reflector,
|
|
36
|
+
) {
|
|
37
|
+
this.reflector = reflector || new Reflector();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 判断是否允许访问
|
|
42
|
+
* @param context - 执行上下文
|
|
43
|
+
* @returns 如果用户具有所需角色则返回 true
|
|
44
|
+
*/
|
|
45
|
+
public canActivate(context: ExecutionContext): boolean {
|
|
46
|
+
// 获取方法和类上定义的角色
|
|
47
|
+
const requiredRoles = this.reflector.getAllAndMerge<string[]>(
|
|
48
|
+
ROLES_METADATA_KEY,
|
|
49
|
+
context.getClass(),
|
|
50
|
+
context.getMethodName(),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// 如果没有定义角色要求,则允许访问
|
|
54
|
+
if (!requiredRoles || requiredRoles.length === 0) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 获取当前用户的角色
|
|
59
|
+
const securityContext = SecurityContextHolder.getContext();
|
|
60
|
+
const authentication = securityContext.authentication;
|
|
61
|
+
|
|
62
|
+
if (!authentication || !authentication.authenticated) {
|
|
63
|
+
throw new ForbiddenException(
|
|
64
|
+
'Access denied: authentication required for role check',
|
|
65
|
+
{ requiredRoles },
|
|
66
|
+
ErrorCode.AUTH_INSUFFICIENT_PERMISSIONS,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const userRoles = authentication.authorities || [];
|
|
71
|
+
|
|
72
|
+
// 检查用户是否具有任一所需角色
|
|
73
|
+
const hasRole = requiredRoles.some((role) => userRoles.includes(role));
|
|
74
|
+
|
|
75
|
+
if (!hasRole) {
|
|
76
|
+
throw new ForbiddenException(
|
|
77
|
+
`Access denied: required roles [${requiredRoles.join(', ')}], but user has [${userRoles.join(', ')}]`,
|
|
78
|
+
{ requiredRoles, userRoles },
|
|
79
|
+
ErrorCode.AUTH_INSUFFICIENT_PERMISSIONS,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 创建自定义角色守卫
|
|
89
|
+
* 支持自定义角色验证逻辑
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* const CustomRolesGuard = createRolesGuard({
|
|
93
|
+
* // 所有角色都必须匹配
|
|
94
|
+
* matchAll: true,
|
|
95
|
+
* // 自定义角色获取逻辑
|
|
96
|
+
* getRoles: (context) => {
|
|
97
|
+
* const user = context.switchToHttp().getRequest().auth?.user;
|
|
98
|
+
* return user?.roles || [];
|
|
99
|
+
* },
|
|
100
|
+
* });
|
|
101
|
+
*/
|
|
102
|
+
export interface RolesGuardOptions {
|
|
103
|
+
/**
|
|
104
|
+
* 是否需要匹配所有角色
|
|
105
|
+
* @default false (只需匹配其中一个)
|
|
106
|
+
*/
|
|
107
|
+
matchAll?: boolean;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 自定义获取用户角色的函数
|
|
111
|
+
*/
|
|
112
|
+
getRoles?: (context: ExecutionContext) => string[];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 创建自定义角色守卫工厂
|
|
117
|
+
* @param options - 配置选项
|
|
118
|
+
* @returns 自定义角色守卫类
|
|
119
|
+
*/
|
|
120
|
+
export function createRolesGuard(options: RolesGuardOptions = {}): new () => CanActivate {
|
|
121
|
+
const { matchAll = false, getRoles } = options;
|
|
122
|
+
|
|
123
|
+
@Injectable()
|
|
124
|
+
class CustomRolesGuard implements CanActivate {
|
|
125
|
+
private readonly reflector = new Reflector();
|
|
126
|
+
|
|
127
|
+
public canActivate(context: ExecutionContext): boolean {
|
|
128
|
+
const requiredRoles = this.reflector.getAllAndMerge<string[]>(
|
|
129
|
+
ROLES_METADATA_KEY,
|
|
130
|
+
context.getClass(),
|
|
131
|
+
context.getMethodName(),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (!requiredRoles || requiredRoles.length === 0) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let userRoles: string[];
|
|
139
|
+
|
|
140
|
+
if (getRoles) {
|
|
141
|
+
userRoles = getRoles(context);
|
|
142
|
+
} else {
|
|
143
|
+
const securityContext = SecurityContextHolder.getContext();
|
|
144
|
+
userRoles = securityContext.authentication?.authorities || [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const hasRole = matchAll
|
|
148
|
+
? requiredRoles.every((role) => userRoles.includes(role))
|
|
149
|
+
: requiredRoles.some((role) => userRoles.includes(role));
|
|
150
|
+
|
|
151
|
+
if (!hasRole) {
|
|
152
|
+
throw new ForbiddenException(
|
|
153
|
+
`Access denied: required roles [${requiredRoles.join(', ')}], user has [${userRoles.join(', ')}]`,
|
|
154
|
+
{ requiredRoles, userRoles, matchAll },
|
|
155
|
+
ErrorCode.AUTH_INSUFFICIENT_PERMISSIONS,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return CustomRolesGuard;
|
|
164
|
+
}
|
|
165
|
+
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import type { GuardType } from './types';
|
|
3
|
+
import { GUARDS_METADATA_KEY, ROLES_METADATA_KEY } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 守卫装饰器
|
|
7
|
+
* 可用于控制器或方法级别
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // 控制器级别
|
|
11
|
+
* @Controller('/api/admin')
|
|
12
|
+
* @UseGuards(AuthGuard, RolesGuard)
|
|
13
|
+
* class AdminController {}
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // 方法级别
|
|
17
|
+
* @GET('/profile')
|
|
18
|
+
* @UseGuards(AuthGuard)
|
|
19
|
+
* getProfile() {}
|
|
20
|
+
*
|
|
21
|
+
* @param guards - 守卫类或守卫实例
|
|
22
|
+
* @returns 类或方法装饰器
|
|
23
|
+
*/
|
|
24
|
+
export function UseGuards(
|
|
25
|
+
...guards: GuardType[]
|
|
26
|
+
): ClassDecorator & MethodDecorator {
|
|
27
|
+
return (
|
|
28
|
+
target: Object | Function,
|
|
29
|
+
propertyKey?: string | symbol,
|
|
30
|
+
descriptor?: PropertyDescriptor,
|
|
31
|
+
) => {
|
|
32
|
+
if (propertyKey !== undefined) {
|
|
33
|
+
// 方法装饰器
|
|
34
|
+
const existingGuards: GuardType[] =
|
|
35
|
+
Reflect.getMetadata(GUARDS_METADATA_KEY, target, propertyKey) || [];
|
|
36
|
+
Reflect.defineMetadata(
|
|
37
|
+
GUARDS_METADATA_KEY,
|
|
38
|
+
[...existingGuards, ...guards],
|
|
39
|
+
target,
|
|
40
|
+
propertyKey,
|
|
41
|
+
);
|
|
42
|
+
} else {
|
|
43
|
+
// 类装饰器
|
|
44
|
+
const existingGuards: GuardType[] =
|
|
45
|
+
Reflect.getMetadata(GUARDS_METADATA_KEY, target) || [];
|
|
46
|
+
Reflect.defineMetadata(
|
|
47
|
+
GUARDS_METADATA_KEY,
|
|
48
|
+
[...existingGuards, ...guards],
|
|
49
|
+
target,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 角色装饰器
|
|
57
|
+
* 用于标记需要特定角色的方法或控制器
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* @GET('/admin')
|
|
61
|
+
* @Roles('admin', 'superadmin')
|
|
62
|
+
* adminOnly() {}
|
|
63
|
+
*
|
|
64
|
+
* @param roles - 允许访问的角色列表
|
|
65
|
+
* @returns 类或方法装饰器
|
|
66
|
+
*/
|
|
67
|
+
export function Roles(
|
|
68
|
+
...roles: string[]
|
|
69
|
+
): ClassDecorator & MethodDecorator {
|
|
70
|
+
return (
|
|
71
|
+
target: Object | Function,
|
|
72
|
+
propertyKey?: string | symbol,
|
|
73
|
+
descriptor?: PropertyDescriptor,
|
|
74
|
+
) => {
|
|
75
|
+
if (propertyKey !== undefined) {
|
|
76
|
+
// 方法装饰器
|
|
77
|
+
Reflect.defineMetadata(ROLES_METADATA_KEY, roles, target, propertyKey);
|
|
78
|
+
} else {
|
|
79
|
+
// 类装饰器
|
|
80
|
+
Reflect.defineMetadata(ROLES_METADATA_KEY, roles, target);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 获取守卫元数据
|
|
87
|
+
* @param target - 目标对象(类或原型)
|
|
88
|
+
* @param propertyKey - 方法名(可选)
|
|
89
|
+
* @returns 守卫列表
|
|
90
|
+
*/
|
|
91
|
+
export function getGuardsMetadata(
|
|
92
|
+
target: Object | Function,
|
|
93
|
+
propertyKey?: string | symbol,
|
|
94
|
+
): GuardType[] {
|
|
95
|
+
// 安全检查:确保 target 是有效的对象或函数
|
|
96
|
+
if (target === null || target === undefined) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
if (typeof target !== 'object' && typeof target !== 'function') {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (propertyKey !== undefined) {
|
|
104
|
+
return Reflect.getMetadata(GUARDS_METADATA_KEY, target, propertyKey) || [];
|
|
105
|
+
}
|
|
106
|
+
return Reflect.getMetadata(GUARDS_METADATA_KEY, target) || [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 获取角色元数据
|
|
111
|
+
* @param target - 目标对象(类或原型)
|
|
112
|
+
* @param propertyKey - 方法名(可选)
|
|
113
|
+
* @returns 角色列表
|
|
114
|
+
*/
|
|
115
|
+
export function getRolesMetadata(
|
|
116
|
+
target: Object | Function,
|
|
117
|
+
propertyKey?: string | symbol,
|
|
118
|
+
): string[] {
|
|
119
|
+
if (propertyKey !== undefined) {
|
|
120
|
+
return Reflect.getMetadata(ROLES_METADATA_KEY, target, propertyKey) || [];
|
|
121
|
+
}
|
|
122
|
+
return Reflect.getMetadata(ROLES_METADATA_KEY, target) || [];
|
|
123
|
+
}
|
|
124
|
+
|