@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dangao/bun-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
"files": [
|
|
27
27
|
"dist",
|
|
28
28
|
"readme.md",
|
|
29
|
-
"LICENSE"
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"src",
|
|
31
|
+
"tests"
|
|
30
32
|
],
|
|
31
33
|
"author": "dangaogit",
|
|
32
34
|
"license": "MIT",
|
package/readme.md
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
- [Benchmark Suite](#benchmark-suite)
|
|
15
15
|
- [Docs & Localization](#docs--localization)
|
|
16
16
|
- [Roadmap](#roadmap)
|
|
17
|
+
- [AI-Assisted Development](#ai-assisted-development)
|
|
17
18
|
- [Engineering Guidelines](#engineering-guidelines)
|
|
18
19
|
- [Contributing](#contributing)
|
|
19
20
|
- [License](#license)
|
|
@@ -29,6 +30,9 @@
|
|
|
29
30
|
provider that scales from MVP to enterprise.
|
|
30
31
|
- **Well-tested**: unit, integration, stress and benchmark suites ship with the
|
|
31
32
|
repo.
|
|
33
|
+
- **AI-friendly**: source code and tests are included in the npm package,
|
|
34
|
+
enabling AI tools (like Cursor) to provide better code analysis, suggestions,
|
|
35
|
+
and understanding of the framework internals.
|
|
32
36
|
|
|
33
37
|
## Features
|
|
34
38
|
|
|
@@ -70,7 +74,6 @@ bun install
|
|
|
70
74
|
### Hello World
|
|
71
75
|
|
|
72
76
|
```ts
|
|
73
|
-
import "reflect-metadata";
|
|
74
77
|
import { Application, Controller, GET, Injectable } from "@dangao/bun-server";
|
|
75
78
|
|
|
76
79
|
@Injectable()
|
|
@@ -108,6 +111,127 @@ bun --cwd=packages/@dangao/bun-server run bench:di
|
|
|
108
111
|
> Running `bun test` from the repo root fails because Bun only scans the current
|
|
109
112
|
> workspace. Use the commands above or `cd packages/@dangao/bun-server` first.
|
|
110
113
|
|
|
114
|
+
### Advanced Example: Interface + Symbol + Module
|
|
115
|
+
|
|
116
|
+
This example demonstrates using interfaces with Symbol tokens and module-based
|
|
117
|
+
dependency injection:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import {
|
|
121
|
+
Application,
|
|
122
|
+
Body,
|
|
123
|
+
CONFIG_SERVICE_TOKEN,
|
|
124
|
+
ConfigModule,
|
|
125
|
+
ConfigService,
|
|
126
|
+
Controller,
|
|
127
|
+
GET,
|
|
128
|
+
Inject,
|
|
129
|
+
Injectable,
|
|
130
|
+
Module,
|
|
131
|
+
Param,
|
|
132
|
+
POST,
|
|
133
|
+
} from "@dangao/bun-server";
|
|
134
|
+
|
|
135
|
+
// Define service interface
|
|
136
|
+
interface UserService {
|
|
137
|
+
find(id: string): Promise<{ id: string; name: string } | undefined>;
|
|
138
|
+
create(name: string): { id: string; name: string };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Create Symbol token for DI
|
|
142
|
+
const UserService = Symbol("UserService");
|
|
143
|
+
|
|
144
|
+
// Implement the interface
|
|
145
|
+
@Injectable()
|
|
146
|
+
class UserServiceImpl implements UserService {
|
|
147
|
+
private readonly users = new Map<string, { id: string; name: string }>([
|
|
148
|
+
["1", { id: "1", name: "Alice" }],
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
public async find(id: string) {
|
|
152
|
+
return this.users.get(id);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public create(name: string) {
|
|
156
|
+
const id = String(this.users.size + 1);
|
|
157
|
+
const user = { id, name };
|
|
158
|
+
this.users.set(id, user);
|
|
159
|
+
return user;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@Controller("/api/users")
|
|
164
|
+
class UserController {
|
|
165
|
+
public constructor(
|
|
166
|
+
private readonly service: UserService,
|
|
167
|
+
@Inject(CONFIG_SERVICE_TOKEN) private readonly config: ConfigService,
|
|
168
|
+
) {}
|
|
169
|
+
|
|
170
|
+
@GET("/:id")
|
|
171
|
+
public async getUser(@Param("id") id: string) {
|
|
172
|
+
const user = await this.service.find(id);
|
|
173
|
+
if (!user) {
|
|
174
|
+
return { error: "Not Found" };
|
|
175
|
+
}
|
|
176
|
+
return user;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@POST("/")
|
|
180
|
+
public createUser(@Body("name") name: string) {
|
|
181
|
+
return this.service.create(name);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Define module with Symbol-based provider
|
|
186
|
+
@Module({
|
|
187
|
+
controllers: [UserController],
|
|
188
|
+
providers: [
|
|
189
|
+
{
|
|
190
|
+
provide: UserService,
|
|
191
|
+
useClass: UserServiceImpl,
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
exports: [UserService],
|
|
195
|
+
})
|
|
196
|
+
class UserModule {}
|
|
197
|
+
|
|
198
|
+
// Configure modules
|
|
199
|
+
ConfigModule.forRoot({
|
|
200
|
+
defaultConfig: {
|
|
201
|
+
app: {
|
|
202
|
+
name: "Advanced App",
|
|
203
|
+
port: 3100,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Register module and start application
|
|
209
|
+
@Module({
|
|
210
|
+
imports: [ConfigModule],
|
|
211
|
+
controllers: [UserController],
|
|
212
|
+
providers: [
|
|
213
|
+
{
|
|
214
|
+
provide: UserService,
|
|
215
|
+
useClass: UserServiceImpl,
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
})
|
|
219
|
+
class AppModule {}
|
|
220
|
+
|
|
221
|
+
const app = new Application({ port: 3100 });
|
|
222
|
+
app.registerModule(AppModule);
|
|
223
|
+
app.listen();
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Key points:**
|
|
227
|
+
|
|
228
|
+
- **Interface-based design**: Define contracts with TypeScript interfaces
|
|
229
|
+
- **Symbol tokens**: Use `Symbol()` for type-safe dependency injection tokens
|
|
230
|
+
- **Module providers**: Register providers using
|
|
231
|
+
`provide: Symbol, useClass: Implementation`
|
|
232
|
+
- **Type-safe injection**: Inject services using `@Inject(Symbol)` with
|
|
233
|
+
interface types
|
|
234
|
+
|
|
111
235
|
## Examples & Extensions
|
|
112
236
|
|
|
113
237
|
- `examples/basic-app.ts`: minimal DI + Logger + Middleware showcase.
|
|
@@ -137,7 +261,9 @@ Or use `bun run bench*` scripts for convenience.
|
|
|
137
261
|
## Docs & Localization
|
|
138
262
|
|
|
139
263
|
- **English** (default): `docs/api.md`, `docs/guide.md`,
|
|
140
|
-
`docs/best-practices.md`, `docs/migration.md`, `docs/extensions.md
|
|
264
|
+
`docs/best-practices.md`, `docs/migration.md`, `docs/extensions.md`,
|
|
265
|
+
`docs/deployment.md`, `docs/performance.md`, `docs/troubleshooting.md`,
|
|
266
|
+
`docs/error-handling.md`.
|
|
141
267
|
- **Chinese**: mirrored under `docs/zh/`. If something is missing, please fall
|
|
142
268
|
back to the English source.
|
|
143
269
|
|
|
@@ -146,6 +272,41 @@ Or use `bun run bench*` scripts for convenience.
|
|
|
146
272
|
Detailed milestones and history are tracked in
|
|
147
273
|
[`.roadmap/v0.3.0.md`](./.roadmap/v0.3.0.md).
|
|
148
274
|
|
|
275
|
+
## AI-Assisted Development
|
|
276
|
+
|
|
277
|
+
Bun Server is designed to work seamlessly with AI coding assistants like Cursor,
|
|
278
|
+
GitHub Copilot, and others. The framework includes source code and tests in the
|
|
279
|
+
npm package distribution, enabling AI tools to:
|
|
280
|
+
|
|
281
|
+
- **Understand framework internals**: AI can analyze the actual implementation
|
|
282
|
+
code, not just type definitions, providing more accurate suggestions.
|
|
283
|
+
- **Provide context-aware help**: When you ask about framework features, AI can
|
|
284
|
+
reference the actual source code to give precise answers.
|
|
285
|
+
- **Suggest best practices**: AI can learn from the framework's patterns and
|
|
286
|
+
suggest similar approaches in your code.
|
|
287
|
+
- **Debug more effectively**: AI can trace through the framework code to help
|
|
288
|
+
diagnose issues.
|
|
289
|
+
|
|
290
|
+
### Best Practices for AI-Assisted Development
|
|
291
|
+
|
|
292
|
+
1. **Reference framework source**: When working with Bun Server, AI tools can
|
|
293
|
+
access the source code at `node_modules/@dangao/bun-server/src/` to
|
|
294
|
+
understand implementation details.
|
|
295
|
+
|
|
296
|
+
2. **Use type hints**: The framework provides comprehensive TypeScript types.
|
|
297
|
+
Leverage these in your code to help AI understand your intent better.
|
|
298
|
+
|
|
299
|
+
3. **Follow framework patterns**: The included source code serves as a reference
|
|
300
|
+
for framework patterns. Ask AI to suggest code that follows similar patterns.
|
|
301
|
+
|
|
302
|
+
4. **Leverage test examples**: The included test files demonstrate usage
|
|
303
|
+
patterns and edge cases. Reference these when asking AI for implementation
|
|
304
|
+
help.
|
|
305
|
+
|
|
306
|
+
5. **Ask specific questions**: Since AI can access the framework source, you can
|
|
307
|
+
ask specific questions like "How does the DI container resolve dependencies?"
|
|
308
|
+
and get accurate answers based on the actual code.
|
|
309
|
+
|
|
149
310
|
## Engineering Guidelines
|
|
150
311
|
|
|
151
312
|
- Comments & log messages **must be in English** to keep the codebase
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Controller, Query, Body } from '../controller';
|
|
2
|
+
import { GET, POST } from '../router/decorators';
|
|
3
|
+
import { Injectable, Inject } from '../di';
|
|
4
|
+
import { ResponseBuilder } from '../request';
|
|
5
|
+
import { OAuth2Service } from './oauth2';
|
|
6
|
+
import { Auth } from './decorators';
|
|
7
|
+
import type {
|
|
8
|
+
OAuth2AuthorizationRequest,
|
|
9
|
+
OAuth2TokenRequest,
|
|
10
|
+
OAuth2TokenResponse,
|
|
11
|
+
} from './types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* OAuth2 令牌 Token
|
|
15
|
+
*/
|
|
16
|
+
export const OAUTH2_SERVICE_TOKEN = Symbol('OAUTH2_SERVICE');
|
|
17
|
+
export const JWT_UTIL_TOKEN = Symbol('JWT_UTIL');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* OAuth2 控制器
|
|
21
|
+
*/
|
|
22
|
+
@Controller('/oauth2')
|
|
23
|
+
@Injectable()
|
|
24
|
+
export class OAuth2Controller {
|
|
25
|
+
public constructor(
|
|
26
|
+
@Inject(OAUTH2_SERVICE_TOKEN) private readonly oauth2Service: OAuth2Service,
|
|
27
|
+
) {}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 授权端点
|
|
31
|
+
* GET /oauth2/authorize?client_id=...&redirect_uri=...&response_type=code&state=...
|
|
32
|
+
*/
|
|
33
|
+
@GET('/authorize')
|
|
34
|
+
public authorize(
|
|
35
|
+
@Query('client_id') clientId: string | null,
|
|
36
|
+
@Query('redirect_uri') redirectUri: string | null,
|
|
37
|
+
@Query('response_type') responseType: string | null,
|
|
38
|
+
@Query('state') state?: string | null,
|
|
39
|
+
@Query('scope') scope?: string | null,
|
|
40
|
+
) {
|
|
41
|
+
// 验证必需参数
|
|
42
|
+
if (!clientId || !redirectUri) {
|
|
43
|
+
throw new Error('Missing required parameters: client_id and redirect_uri are required');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (responseType !== 'code') {
|
|
47
|
+
throw new Error(`Unsupported response_type: ${responseType}. Only 'code' is supported.`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const request: OAuth2AuthorizationRequest = {
|
|
51
|
+
clientId,
|
|
52
|
+
redirectUri,
|
|
53
|
+
responseType: 'code',
|
|
54
|
+
scope: scope || undefined,
|
|
55
|
+
state: state || undefined,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// 验证请求
|
|
59
|
+
const validation = this.oauth2Service.validateAuthorizationRequest(request);
|
|
60
|
+
if (!validation.valid) {
|
|
61
|
+
throw new Error(`Invalid authorization request: ${validation.error}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 这里应该显示授权页面,让用户登录并授权
|
|
65
|
+
// 为了简化,我们假设用户已经登录,使用默认用户 ID
|
|
66
|
+
const userId = 'user-1';
|
|
67
|
+
|
|
68
|
+
// 生成授权码
|
|
69
|
+
const code = this.oauth2Service.generateAuthorizationCode(
|
|
70
|
+
request.clientId,
|
|
71
|
+
request.redirectUri,
|
|
72
|
+
userId,
|
|
73
|
+
request.scope,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// 构建重定向 URL
|
|
77
|
+
const redirectUrl = new URL(request.redirectUri);
|
|
78
|
+
redirectUrl.searchParams.set('code', code);
|
|
79
|
+
if (request.state) {
|
|
80
|
+
redirectUrl.searchParams.set('state', request.state);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 返回重定向 URL(框架会处理)
|
|
84
|
+
// 注意:实际实现中可能需要使用 ResponseBuilder.redirect()
|
|
85
|
+
return ResponseBuilder.redirect(redirectUrl.toString());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 令牌端点
|
|
90
|
+
* POST /oauth2/token
|
|
91
|
+
*/
|
|
92
|
+
@POST('/token')
|
|
93
|
+
public async token(@Body() body: Record<string, string>): Promise<OAuth2TokenResponse | any> {
|
|
94
|
+
const request: OAuth2TokenRequest = {
|
|
95
|
+
code: body.code || '',
|
|
96
|
+
clientId: body.client_id || '',
|
|
97
|
+
clientSecret: body.client_secret || '',
|
|
98
|
+
redirectUri: body.redirect_uri || '',
|
|
99
|
+
grantType: (body.grant_type as 'authorization_code' | 'refresh_token') || 'authorization_code',
|
|
100
|
+
refreshToken: body.refresh_token,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
if (request.grantType === 'authorization_code') {
|
|
104
|
+
const tokenResponse = await this.oauth2Service.exchangeCodeForToken(request);
|
|
105
|
+
if (!tokenResponse) {
|
|
106
|
+
return {
|
|
107
|
+
error: 'invalid_grant',
|
|
108
|
+
error_description: 'Invalid authorization code',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return tokenResponse;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (request.grantType === 'refresh_token' && request.refreshToken) {
|
|
115
|
+
const tokenResponse = await this.oauth2Service.refreshToken(request.refreshToken);
|
|
116
|
+
if (!tokenResponse) {
|
|
117
|
+
return {
|
|
118
|
+
error: 'invalid_grant',
|
|
119
|
+
error_description: 'Invalid refresh token',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return tokenResponse;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
error: 'unsupported_grant_type',
|
|
127
|
+
error_description: 'Unsupported grant type',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 用户信息端点
|
|
133
|
+
* GET /oauth2/userinfo
|
|
134
|
+
* 注意:此端点需要认证,应该通过认证中间件保护
|
|
135
|
+
*/
|
|
136
|
+
@GET('/userinfo')
|
|
137
|
+
@Auth()
|
|
138
|
+
public userinfo() {
|
|
139
|
+
// 用户信息应该通过认证中间件注入到 Context
|
|
140
|
+
// 这里简化处理,返回占位符
|
|
141
|
+
return {
|
|
142
|
+
sub: 'user-1',
|
|
143
|
+
username: 'alice',
|
|
144
|
+
roles: ['user'],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 认证元数据键
|
|
5
|
+
*/
|
|
6
|
+
const AUTH_METADATA_KEY = Symbol('@dangao/bun-server:auth');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 认证配置
|
|
10
|
+
*/
|
|
11
|
+
export interface AuthConfig {
|
|
12
|
+
/**
|
|
13
|
+
* 是否需要认证
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
required?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* 允许的角色列表
|
|
19
|
+
*/
|
|
20
|
+
roles?: string[];
|
|
21
|
+
/**
|
|
22
|
+
* 是否允许匿名访问
|
|
23
|
+
* @default false
|
|
24
|
+
*/
|
|
25
|
+
allowAnonymous?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 认证装饰器
|
|
30
|
+
* 用于标记需要认证的路由
|
|
31
|
+
*/
|
|
32
|
+
export function Auth(config: AuthConfig = {}): MethodDecorator {
|
|
33
|
+
return (target: Object, propertyKey: string | symbol) => {
|
|
34
|
+
const metadata = Reflect.getMetadata(AUTH_METADATA_KEY, target) || {};
|
|
35
|
+
metadata[propertyKey] = {
|
|
36
|
+
required: config.required !== false,
|
|
37
|
+
roles: config.roles || [],
|
|
38
|
+
allowAnonymous: config.allowAnonymous || false,
|
|
39
|
+
};
|
|
40
|
+
Reflect.defineMetadata(AUTH_METADATA_KEY, metadata, target);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 获取认证元数据
|
|
46
|
+
*/
|
|
47
|
+
export function getAuthMetadata(
|
|
48
|
+
target: Object,
|
|
49
|
+
propertyKey: string | symbol,
|
|
50
|
+
): AuthConfig | undefined {
|
|
51
|
+
const metadata = Reflect.getMetadata(AUTH_METADATA_KEY, target);
|
|
52
|
+
return metadata?.[propertyKey];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 检查是否需要认证
|
|
57
|
+
*/
|
|
58
|
+
export function requiresAuth(target: Object, propertyKey: string | symbol): boolean {
|
|
59
|
+
const config = getAuthMetadata(target, propertyKey);
|
|
60
|
+
// 如果没有配置,默认不需要认证;只有显式标注 @Auth 时才进入认证流程
|
|
61
|
+
if (!config) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return config.required !== false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 检查角色权限
|
|
69
|
+
*/
|
|
70
|
+
export function checkRoles(
|
|
71
|
+
target: Object,
|
|
72
|
+
propertyKey: string | symbol,
|
|
73
|
+
userRoles: string[] = [],
|
|
74
|
+
): boolean {
|
|
75
|
+
const config = getAuthMetadata(target, propertyKey);
|
|
76
|
+
if (!config?.roles || config.roles.length === 0) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return config.roles.some((role) => userRoles.includes(role));
|
|
80
|
+
}
|
|
81
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// 核心实现(保留)
|
|
2
|
+
export * from './types';
|
|
3
|
+
export * from './jwt';
|
|
4
|
+
export * from './oauth2';
|
|
5
|
+
export * from './decorators';
|
|
6
|
+
export * from './controller';
|
|
7
|
+
|
|
8
|
+
// 已废弃,请使用 SecurityModule
|
|
9
|
+
// export * from './middleware';
|
|
10
|
+
// export { AuthModule } from './auth-module';
|
|
11
|
+
// export { AuthExtension } from './auth-extension';
|
|
12
|
+
|
package/src/auth/jwt.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { JWTConfig, JWTPayload } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* JWT 工具类
|
|
5
|
+
*/
|
|
6
|
+
export class JWTUtil {
|
|
7
|
+
private readonly config: Required<JWTConfig>;
|
|
8
|
+
|
|
9
|
+
public constructor(config: JWTConfig) {
|
|
10
|
+
this.config = {
|
|
11
|
+
secret: config.secret,
|
|
12
|
+
accessTokenExpiresIn: config.accessTokenExpiresIn ?? 3600,
|
|
13
|
+
refreshTokenExpiresIn: config.refreshTokenExpiresIn ?? 86400 * 7,
|
|
14
|
+
algorithm: config.algorithm ?? 'HS256',
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 生成访问令牌
|
|
20
|
+
* @param payload - JWT 载荷
|
|
21
|
+
* @returns 访问令牌
|
|
22
|
+
*/
|
|
23
|
+
public generateAccessToken(payload: Omit<JWTPayload, 'exp' | 'iat'>): string {
|
|
24
|
+
const now = Math.floor(Date.now() / 1000);
|
|
25
|
+
const tokenPayload: JWTPayload = {
|
|
26
|
+
...payload,
|
|
27
|
+
sub: payload.sub as string,
|
|
28
|
+
iat: now,
|
|
29
|
+
exp: now + this.config.accessTokenExpiresIn,
|
|
30
|
+
};
|
|
31
|
+
return this.sign(tokenPayload);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 生成刷新令牌
|
|
36
|
+
* @param payload - JWT 载荷
|
|
37
|
+
* @returns 刷新令牌
|
|
38
|
+
*/
|
|
39
|
+
public generateRefreshToken(payload: Omit<JWTPayload, 'exp' | 'iat'>): string {
|
|
40
|
+
const now = Math.floor(Date.now() / 1000);
|
|
41
|
+
const tokenPayload: JWTPayload = {
|
|
42
|
+
...payload,
|
|
43
|
+
sub: payload.sub as string,
|
|
44
|
+
iat: now,
|
|
45
|
+
exp: now + this.config.refreshTokenExpiresIn,
|
|
46
|
+
};
|
|
47
|
+
return this.sign(tokenPayload);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 验证令牌
|
|
52
|
+
* @param token - JWT 令牌
|
|
53
|
+
* @returns 载荷或 null(如果无效)
|
|
54
|
+
*/
|
|
55
|
+
public verify(token: string): JWTPayload | null {
|
|
56
|
+
try {
|
|
57
|
+
const parts = token.split('.');
|
|
58
|
+
if (parts.length !== 3) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 解析载荷
|
|
63
|
+
const payload = JSON.parse(
|
|
64
|
+
Buffer.from(parts[1], 'base64url').toString('utf-8'),
|
|
65
|
+
) as JWTPayload;
|
|
66
|
+
|
|
67
|
+
// 验证签名
|
|
68
|
+
const signature = this.signature(parts[0] + '.' + parts[1]);
|
|
69
|
+
if (signature !== parts[2]) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 验证过期时间
|
|
74
|
+
if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return payload;
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 签名 JWT
|
|
86
|
+
*/
|
|
87
|
+
private sign(payload: JWTPayload): string {
|
|
88
|
+
const header = {
|
|
89
|
+
alg: this.config.algorithm,
|
|
90
|
+
typ: 'JWT',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const encodedHeader = this.base64UrlEncode(JSON.stringify(header));
|
|
94
|
+
const encodedPayload = this.base64UrlEncode(JSON.stringify(payload));
|
|
95
|
+
const signature = this.signature(encodedHeader + '.' + encodedPayload);
|
|
96
|
+
|
|
97
|
+
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 生成签名
|
|
102
|
+
*/
|
|
103
|
+
private signature(data: string): string {
|
|
104
|
+
// 使用 Bun 的 CryptoHasher
|
|
105
|
+
const encoder = new TextEncoder();
|
|
106
|
+
const keyData = encoder.encode(this.config.secret);
|
|
107
|
+
const messageData = encoder.encode(data);
|
|
108
|
+
|
|
109
|
+
// 使用 HMAC-SHA256
|
|
110
|
+
const hash = this.hmacSha256(keyData, messageData);
|
|
111
|
+
return this.base64UrlEncode(Buffer.from(hash).toString('base64'));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* HMAC-SHA256 实现
|
|
116
|
+
*/
|
|
117
|
+
private hmacSha256(key: Uint8Array, data: Uint8Array): Uint8Array {
|
|
118
|
+
const blockSize = 64;
|
|
119
|
+
let keyBuffer: Uint8Array;
|
|
120
|
+
|
|
121
|
+
if (key.length > blockSize) {
|
|
122
|
+
// 如果密钥长度超过块大小,先哈希
|
|
123
|
+
const hasher = new Bun.CryptoHasher('sha256');
|
|
124
|
+
hasher.update(key);
|
|
125
|
+
keyBuffer = new Uint8Array(hasher.digest());
|
|
126
|
+
} else {
|
|
127
|
+
keyBuffer = new Uint8Array(blockSize);
|
|
128
|
+
keyBuffer.set(key);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 创建 o_key_pad 和 i_key_pad
|
|
132
|
+
const oKeyPad = new Uint8Array(blockSize);
|
|
133
|
+
const iKeyPad = new Uint8Array(blockSize);
|
|
134
|
+
|
|
135
|
+
for (let i = 0; i < blockSize; i++) {
|
|
136
|
+
oKeyPad[i] = keyBuffer[i] ^ 0x5c;
|
|
137
|
+
iKeyPad[i] = keyBuffer[i] ^ 0x36;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 计算 inner hash
|
|
141
|
+
const innerData = new Uint8Array(iKeyPad.length + data.length);
|
|
142
|
+
innerData.set(iKeyPad);
|
|
143
|
+
innerData.set(data, iKeyPad.length);
|
|
144
|
+
const innerHasher = new Bun.CryptoHasher('sha256');
|
|
145
|
+
innerHasher.update(innerData);
|
|
146
|
+
const innerHash = new Uint8Array(innerHasher.digest());
|
|
147
|
+
|
|
148
|
+
// 计算 outer hash
|
|
149
|
+
const outerData = new Uint8Array(oKeyPad.length + innerHash.length);
|
|
150
|
+
outerData.set(oKeyPad);
|
|
151
|
+
outerData.set(innerHash, oKeyPad.length);
|
|
152
|
+
const outerHasher = new Bun.CryptoHasher('sha256');
|
|
153
|
+
outerHasher.update(outerData);
|
|
154
|
+
|
|
155
|
+
return new Uint8Array(outerHasher.digest());
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Base64URL 编码
|
|
160
|
+
*/
|
|
161
|
+
private base64UrlEncode(str: string): string {
|
|
162
|
+
return Buffer.from(str)
|
|
163
|
+
.toString('base64')
|
|
164
|
+
.replace(/\+/g, '-')
|
|
165
|
+
.replace(/\//g, '_')
|
|
166
|
+
.replace(/=/g, '');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|