@dangao/bun-server 0.1.3 → 0.1.5
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/dist/auth/controller.d.ts +35 -0
- package/dist/auth/controller.d.ts.map +1 -0
- package/dist/auth/decorators.d.ts +38 -0
- package/dist/auth/decorators.d.ts.map +1 -0
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/jwt.d.ts +43 -0
- package/dist/auth/jwt.d.ts.map +1 -0
- package/dist/auth/oauth2.d.ts +45 -0
- package/dist/auth/oauth2.d.ts.map +1 -0
- package/dist/auth/types.d.ts +238 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/config/config-module.d.ts +14 -0
- package/dist/config/config-module.d.ts.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/service.d.ts +32 -0
- package/dist/config/service.d.ts.map +1 -0
- package/dist/config/types.d.ts +22 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/controller/controller.d.ts +5 -0
- package/dist/controller/controller.d.ts.map +1 -1
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/context.d.ts.map +1 -1
- package/dist/di/module-registry.d.ts +14 -2
- package/dist/di/module-registry.d.ts.map +1 -1
- package/dist/di/module.d.ts +12 -1
- package/dist/di/module.d.ts.map +1 -1
- package/dist/extensions/index.d.ts +1 -0
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/logger-module.d.ts +30 -0
- package/dist/extensions/logger-module.d.ts.map +1 -0
- package/dist/health/controller.d.ts +27 -0
- package/dist/health/controller.d.ts.map +1 -0
- package/dist/health/health-module.d.ts +9 -0
- package/dist/health/health-module.d.ts.map +1 -0
- package/dist/health/index.d.ts +4 -0
- package/dist/health/index.d.ts.map +1 -0
- package/dist/health/types.d.ts +22 -0
- package/dist/health/types.d.ts.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1388 -13
- package/dist/router/registry.d.ts +4 -1
- package/dist/router/registry.d.ts.map +1 -1
- package/dist/router/route.d.ts +9 -1
- package/dist/router/route.d.ts.map +1 -1
- package/dist/router/router.d.ts +14 -2
- package/dist/router/router.d.ts.map +1 -1
- package/dist/security/access-decision-manager.d.ts +14 -0
- package/dist/security/access-decision-manager.d.ts.map +1 -0
- package/dist/security/authentication-manager.d.ts +22 -0
- package/dist/security/authentication-manager.d.ts.map +1 -0
- package/dist/security/context.d.ts +51 -0
- package/dist/security/context.d.ts.map +1 -0
- package/dist/security/filter.d.ts +27 -0
- package/dist/security/filter.d.ts.map +1 -0
- package/dist/security/index.d.ts +8 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/providers/index.d.ts +3 -0
- package/dist/security/providers/index.d.ts.map +1 -0
- package/dist/security/providers/jwt-provider.d.ts +19 -0
- package/dist/security/providers/jwt-provider.d.ts.map +1 -0
- package/dist/security/providers/oauth2-provider.d.ts +19 -0
- package/dist/security/providers/oauth2-provider.d.ts.map +1 -0
- package/dist/security/security-module.d.ts +50 -0
- package/dist/security/security-module.d.ts.map +1 -0
- package/dist/security/types.d.ts +152 -0
- package/dist/security/types.d.ts.map +1 -0
- package/dist/swagger/decorators.d.ts +56 -0
- package/dist/swagger/decorators.d.ts.map +1 -0
- package/dist/swagger/generator.d.ts +17 -0
- package/dist/swagger/generator.d.ts.map +1 -0
- package/dist/swagger/index.d.ts +7 -0
- package/dist/swagger/index.d.ts.map +1 -0
- package/dist/swagger/swagger-extension.d.ts +25 -0
- package/dist/swagger/swagger-extension.d.ts.map +1 -0
- package/dist/swagger/swagger-module.d.ts +37 -0
- package/dist/swagger/swagger-module.d.ts.map +1 -0
- package/dist/swagger/types.d.ts +176 -0
- package/dist/swagger/types.d.ts.map +1 -0
- package/dist/swagger/ui.d.ts +13 -0
- package/dist/swagger/ui.d.ts.map +1 -0
- package/package.json +1 -1
- package/readme.md +14 -10
package/dist/index.js
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __legacyDecorateClassTS = function(decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
5
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
6
|
+
else
|
|
7
|
+
for (var i = decorators.length - 1;i >= 0; i--)
|
|
8
|
+
if (d = decorators[i])
|
|
9
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
10
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
11
|
+
};
|
|
12
|
+
var __legacyDecorateParamTS = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
13
|
+
var __legacyMetadataTS = (k, v) => {
|
|
14
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
|
|
15
|
+
return Reflect.metadata(k, v);
|
|
16
|
+
};
|
|
17
|
+
|
|
2
18
|
// src/request/body-parser.ts
|
|
3
19
|
class BodyParser {
|
|
4
20
|
static async parse(request) {
|
|
@@ -141,9 +157,12 @@ class Context {
|
|
|
141
157
|
this.statusCode = code;
|
|
142
158
|
}
|
|
143
159
|
createResponse(body, init) {
|
|
160
|
+
if (body instanceof Response) {
|
|
161
|
+
return body;
|
|
162
|
+
}
|
|
144
163
|
const headers = new Headers(this.responseHeaders);
|
|
145
164
|
if (body !== undefined && body !== null) {
|
|
146
|
-
if (typeof body === "object" && !(body instanceof
|
|
165
|
+
if (typeof body === "object" && !(body instanceof ArrayBuffer) && !(body instanceof Blob)) {
|
|
147
166
|
headers.set("Content-Type", "application/json");
|
|
148
167
|
const jsonString = JSON.stringify(body);
|
|
149
168
|
return new Response(jsonString, {
|
|
@@ -271,15 +290,19 @@ class Route {
|
|
|
271
290
|
method;
|
|
272
291
|
path;
|
|
273
292
|
handler;
|
|
293
|
+
controllerClass;
|
|
294
|
+
methodName;
|
|
274
295
|
pattern;
|
|
275
296
|
paramNames;
|
|
276
297
|
middlewarePipeline;
|
|
277
298
|
staticKey;
|
|
278
299
|
isStatic;
|
|
279
|
-
constructor(method, path, handler, middlewares = []) {
|
|
300
|
+
constructor(method, path, handler, middlewares = [], controllerClass, methodName) {
|
|
280
301
|
this.method = method;
|
|
281
302
|
this.path = path;
|
|
282
303
|
this.handler = handler;
|
|
304
|
+
this.controllerClass = controllerClass;
|
|
305
|
+
this.methodName = methodName;
|
|
283
306
|
const { pattern, paramNames } = this.parsePath(path);
|
|
284
307
|
this.pattern = pattern;
|
|
285
308
|
this.paramNames = paramNames;
|
|
@@ -328,8 +351,15 @@ class Router {
|
|
|
328
351
|
routes = [];
|
|
329
352
|
staticRoutes = new Map;
|
|
330
353
|
dynamicRoutes = [];
|
|
331
|
-
|
|
332
|
-
|
|
354
|
+
normalizePath(path) {
|
|
355
|
+
if (path.length > 1 && path.endsWith("/")) {
|
|
356
|
+
return path.slice(0, -1);
|
|
357
|
+
}
|
|
358
|
+
return path;
|
|
359
|
+
}
|
|
360
|
+
register(method, path, handler, middlewares = [], controllerClass, methodName) {
|
|
361
|
+
const normalizedPath = this.normalizePath(path);
|
|
362
|
+
const route = new Route(method, normalizedPath, handler, middlewares, controllerClass, methodName);
|
|
333
363
|
this.routes.push(route);
|
|
334
364
|
const staticKey = route.getStaticKey();
|
|
335
365
|
if (staticKey) {
|
|
@@ -366,16 +396,32 @@ class Router {
|
|
|
366
396
|
}
|
|
367
397
|
return;
|
|
368
398
|
}
|
|
369
|
-
async
|
|
399
|
+
async preHandle(context) {
|
|
370
400
|
const method = context.method;
|
|
371
|
-
const
|
|
401
|
+
const path = this.normalizePath(context.path);
|
|
402
|
+
const route = this.findRoute(method, path);
|
|
372
403
|
if (!route) {
|
|
373
404
|
return;
|
|
374
405
|
}
|
|
375
|
-
const match = route.match(method,
|
|
406
|
+
const match = route.match(method, path);
|
|
376
407
|
if (match.matched) {
|
|
377
408
|
context.params = match.params;
|
|
378
409
|
}
|
|
410
|
+
if (route.controllerClass && route.methodName) {
|
|
411
|
+
context.routeHandler = {
|
|
412
|
+
controller: route.controllerClass,
|
|
413
|
+
method: route.methodName
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
async handle(context) {
|
|
418
|
+
await this.preHandle(context);
|
|
419
|
+
const method = context.method;
|
|
420
|
+
const path = this.normalizePath(context.path);
|
|
421
|
+
const route = this.findRoute(method, path);
|
|
422
|
+
if (!route) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
379
425
|
return await route.execute(context);
|
|
380
426
|
}
|
|
381
427
|
getRoutes() {
|
|
@@ -401,8 +447,8 @@ class RouteRegistry {
|
|
|
401
447
|
}
|
|
402
448
|
return RouteRegistry.instance;
|
|
403
449
|
}
|
|
404
|
-
register(method, path, handler, middlewares = []) {
|
|
405
|
-
this.router.register(method, path, handler, middlewares);
|
|
450
|
+
register(method, path, handler, middlewares = [], controllerClass, methodName) {
|
|
451
|
+
this.router.register(method, path, handler, middlewares, controllerClass, methodName);
|
|
406
452
|
}
|
|
407
453
|
get(path, handler, middlewares = []) {
|
|
408
454
|
this.router.get(path, handler, middlewares);
|
|
@@ -1487,6 +1533,9 @@ class ControllerRegistry {
|
|
|
1487
1533
|
}
|
|
1488
1534
|
const result = method.apply(controllerInstance, params);
|
|
1489
1535
|
const responseData = await Promise.resolve(result);
|
|
1536
|
+
if (responseData instanceof Response) {
|
|
1537
|
+
return responseData;
|
|
1538
|
+
}
|
|
1490
1539
|
return context.createResponse(responseData);
|
|
1491
1540
|
} catch (error) {
|
|
1492
1541
|
if (error instanceof ValidationError) {
|
|
@@ -1512,7 +1561,7 @@ class ControllerRegistry {
|
|
|
1512
1561
|
if (propertyKey) {
|
|
1513
1562
|
middlewares.push(...getMethodMiddlewares(prototype, propertyKey));
|
|
1514
1563
|
}
|
|
1515
|
-
registry.register(route.method, fullPath, handler, middlewares);
|
|
1564
|
+
registry.register(route.method, fullPath, handler, middlewares, controllerClass, propertyKey);
|
|
1516
1565
|
}
|
|
1517
1566
|
}
|
|
1518
1567
|
combinePaths(basePath, methodPath) {
|
|
@@ -1526,6 +1575,9 @@ class ControllerRegistry {
|
|
|
1526
1575
|
getContainer() {
|
|
1527
1576
|
return this.container;
|
|
1528
1577
|
}
|
|
1578
|
+
getRegisteredControllers() {
|
|
1579
|
+
return Array.from(this.controllerContainers.keys());
|
|
1580
|
+
}
|
|
1529
1581
|
clear() {
|
|
1530
1582
|
this.controllers.clear();
|
|
1531
1583
|
this.container.clear();
|
|
@@ -1685,7 +1737,9 @@ function getModuleMetadata(moduleClass) {
|
|
|
1685
1737
|
imports: metadata.imports ?? [],
|
|
1686
1738
|
controllers: metadata.controllers ?? [],
|
|
1687
1739
|
providers: metadata.providers ?? [],
|
|
1688
|
-
exports: metadata.exports ?? []
|
|
1740
|
+
exports: metadata.exports ?? [],
|
|
1741
|
+
extensions: metadata.extensions ?? [],
|
|
1742
|
+
middlewares: metadata.middlewares ?? []
|
|
1689
1743
|
};
|
|
1690
1744
|
}
|
|
1691
1745
|
|
|
@@ -1744,7 +1798,9 @@ class ModuleRegistry {
|
|
|
1744
1798
|
metadata,
|
|
1745
1799
|
container,
|
|
1746
1800
|
controllersRegistered: false,
|
|
1747
|
-
attachedParents: new Set
|
|
1801
|
+
attachedParents: new Set,
|
|
1802
|
+
extensions: metadata.extensions ?? [],
|
|
1803
|
+
middlewares: metadata.middlewares ?? []
|
|
1748
1804
|
};
|
|
1749
1805
|
this.registerControllers(ref);
|
|
1750
1806
|
this.moduleRefs.set(moduleClass, ref);
|
|
@@ -1796,6 +1852,27 @@ class ModuleRegistry {
|
|
|
1796
1852
|
for (const exportedToken of moduleRef.metadata.exports) {
|
|
1797
1853
|
this.registerExport(parentContainer, moduleRef, exportedToken);
|
|
1798
1854
|
}
|
|
1855
|
+
for (const imported of moduleRef.metadata.imports) {
|
|
1856
|
+
const importedRef = this.moduleRefs.get(imported);
|
|
1857
|
+
if (importedRef) {
|
|
1858
|
+
moduleRef.extensions.push(...importedRef.extensions);
|
|
1859
|
+
moduleRef.middlewares.push(...importedRef.middlewares);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
getModuleExtensions(moduleClass) {
|
|
1864
|
+
const moduleRef = this.moduleRefs.get(moduleClass);
|
|
1865
|
+
if (!moduleRef) {
|
|
1866
|
+
return [];
|
|
1867
|
+
}
|
|
1868
|
+
return moduleRef.extensions;
|
|
1869
|
+
}
|
|
1870
|
+
getModuleMiddlewares(moduleClass) {
|
|
1871
|
+
const moduleRef = this.moduleRefs.get(moduleClass);
|
|
1872
|
+
if (!moduleRef) {
|
|
1873
|
+
return [];
|
|
1874
|
+
}
|
|
1875
|
+
return moduleRef.middlewares;
|
|
1799
1876
|
}
|
|
1800
1877
|
registerExport(parentContainer, moduleRef, token) {
|
|
1801
1878
|
if (!moduleRef.container.isRegistered(token)) {
|
|
@@ -1852,6 +1929,7 @@ class Application {
|
|
|
1852
1929
|
}
|
|
1853
1930
|
const registry = RouteRegistry.getInstance();
|
|
1854
1931
|
const router = registry.getRouter();
|
|
1932
|
+
await router.preHandle(context);
|
|
1855
1933
|
return await this.middlewarePipeline.run(context, async () => {
|
|
1856
1934
|
const response = await router.handle(context);
|
|
1857
1935
|
if (response) {
|
|
@@ -1868,6 +1946,14 @@ class Application {
|
|
|
1868
1946
|
registerModule(moduleClass) {
|
|
1869
1947
|
const registry = ModuleRegistry.getInstance();
|
|
1870
1948
|
registry.register(moduleClass, this.getContainer());
|
|
1949
|
+
const extensions = registry.getModuleExtensions(moduleClass);
|
|
1950
|
+
for (const extension of extensions) {
|
|
1951
|
+
this.registerExtension(extension);
|
|
1952
|
+
}
|
|
1953
|
+
const middlewares = registry.getModuleMiddlewares(moduleClass);
|
|
1954
|
+
for (const middleware of middlewares) {
|
|
1955
|
+
this.use(middleware);
|
|
1956
|
+
}
|
|
1871
1957
|
}
|
|
1872
1958
|
registerWebSocketGateway(gatewayClass) {
|
|
1873
1959
|
this.websocketRegistry.register(gatewayClass);
|
|
@@ -1993,8 +2079,1265 @@ class ResponseBuilder {
|
|
|
1993
2079
|
});
|
|
1994
2080
|
}
|
|
1995
2081
|
}
|
|
2082
|
+
// src/extensions/logger-module.ts
|
|
2083
|
+
class LoggerModule {
|
|
2084
|
+
static forRoot(options = {}) {
|
|
2085
|
+
const extensions = [];
|
|
2086
|
+
const middlewares = [];
|
|
2087
|
+
const loggerExtension = new LoggerExtension(options.logger);
|
|
2088
|
+
extensions.push(loggerExtension);
|
|
2089
|
+
if (options.enableRequestLogging !== false) {
|
|
2090
|
+
const requestLoggingMiddleware = createLoggerMiddleware({
|
|
2091
|
+
prefix: options.requestLoggingPrefix
|
|
2092
|
+
});
|
|
2093
|
+
middlewares.push(requestLoggingMiddleware);
|
|
2094
|
+
}
|
|
2095
|
+
const existingMetadata = Reflect.getMetadata(MODULE_METADATA_KEY, LoggerModule) || {};
|
|
2096
|
+
const metadata = {
|
|
2097
|
+
...existingMetadata,
|
|
2098
|
+
extensions,
|
|
2099
|
+
middlewares
|
|
2100
|
+
};
|
|
2101
|
+
Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, LoggerModule);
|
|
2102
|
+
return LoggerModule;
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
LoggerModule = __legacyDecorateClassTS([
|
|
2106
|
+
Module({
|
|
2107
|
+
extensions: [],
|
|
2108
|
+
middlewares: []
|
|
2109
|
+
})
|
|
2110
|
+
], LoggerModule);
|
|
2111
|
+
|
|
1996
2112
|
// src/extensions/index.ts
|
|
1997
2113
|
import { LoggerManager as LoggerManager7, LogLevel as LogLevel2, SimpleLogger as SimpleLogger2 } from "@dangao/logsmith";
|
|
2114
|
+
// src/swagger/decorators.ts
|
|
2115
|
+
import"reflect-metadata";
|
|
2116
|
+
var API_TAG_METADATA_KEY = Symbol("swagger:api-tag");
|
|
2117
|
+
var API_OPERATION_METADATA_KEY = Symbol("swagger:api-operation");
|
|
2118
|
+
var API_PARAM_METADATA_KEY = Symbol("swagger:api-param");
|
|
2119
|
+
var API_BODY_METADATA_KEY = Symbol("swagger:api-body");
|
|
2120
|
+
var API_RESPONSE_METADATA_KEY = Symbol("swagger:api-response");
|
|
2121
|
+
function ApiTags(...tags) {
|
|
2122
|
+
return function(target, propertyKey) {
|
|
2123
|
+
if (propertyKey === undefined) {
|
|
2124
|
+
Reflect.defineMetadata(API_TAG_METADATA_KEY, tags, target);
|
|
2125
|
+
} else {
|
|
2126
|
+
const existingTags = Reflect.getMetadata(API_TAG_METADATA_KEY, target, propertyKey) || [];
|
|
2127
|
+
Reflect.defineMetadata(API_TAG_METADATA_KEY, [...existingTags, ...tags], target, propertyKey);
|
|
2128
|
+
}
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
function ApiOperation(metadata) {
|
|
2132
|
+
return function(target, propertyKey) {
|
|
2133
|
+
Reflect.defineMetadata(API_OPERATION_METADATA_KEY, metadata, target, propertyKey);
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
function ApiParam(metadata) {
|
|
2137
|
+
return function(target, propertyKey, parameterIndex) {
|
|
2138
|
+
const existingParams = Reflect.getMetadata(API_PARAM_METADATA_KEY, target, propertyKey) || [];
|
|
2139
|
+
existingParams.push({ index: parameterIndex, metadata });
|
|
2140
|
+
Reflect.defineMetadata(API_PARAM_METADATA_KEY, existingParams, target, propertyKey);
|
|
2141
|
+
};
|
|
2142
|
+
}
|
|
2143
|
+
function ApiBody(metadata) {
|
|
2144
|
+
return function(target, propertyKey) {
|
|
2145
|
+
Reflect.defineMetadata(API_BODY_METADATA_KEY, metadata, target, propertyKey);
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
function ApiResponse(metadata) {
|
|
2149
|
+
return function(target, propertyKey) {
|
|
2150
|
+
const existingResponses = Reflect.getMetadata(API_RESPONSE_METADATA_KEY, target, propertyKey) || [];
|
|
2151
|
+
existingResponses.push(metadata);
|
|
2152
|
+
Reflect.defineMetadata(API_RESPONSE_METADATA_KEY, existingResponses, target, propertyKey);
|
|
2153
|
+
};
|
|
2154
|
+
}
|
|
2155
|
+
function getApiTags(target, propertyKey) {
|
|
2156
|
+
if (propertyKey === undefined) {
|
|
2157
|
+
return Reflect.getMetadata(API_TAG_METADATA_KEY, target) || [];
|
|
2158
|
+
}
|
|
2159
|
+
return Reflect.getMetadata(API_TAG_METADATA_KEY, target, propertyKey) || [];
|
|
2160
|
+
}
|
|
2161
|
+
function getApiOperation(target, propertyKey) {
|
|
2162
|
+
return Reflect.getMetadata(API_OPERATION_METADATA_KEY, target, propertyKey);
|
|
2163
|
+
}
|
|
2164
|
+
function getApiParams(target, propertyKey) {
|
|
2165
|
+
return Reflect.getMetadata(API_PARAM_METADATA_KEY, target, propertyKey) || [];
|
|
2166
|
+
}
|
|
2167
|
+
function getApiBody(target, propertyKey) {
|
|
2168
|
+
return Reflect.getMetadata(API_BODY_METADATA_KEY, target, propertyKey);
|
|
2169
|
+
}
|
|
2170
|
+
function getApiResponses(target, propertyKey) {
|
|
2171
|
+
return Reflect.getMetadata(API_RESPONSE_METADATA_KEY, target, propertyKey) || [];
|
|
2172
|
+
}
|
|
2173
|
+
// src/swagger/generator.ts
|
|
2174
|
+
class SwaggerGenerator {
|
|
2175
|
+
options;
|
|
2176
|
+
constructor(options) {
|
|
2177
|
+
this.options = options;
|
|
2178
|
+
}
|
|
2179
|
+
generate() {
|
|
2180
|
+
const document = {
|
|
2181
|
+
openapi: "3.0.0",
|
|
2182
|
+
info: {
|
|
2183
|
+
title: this.options.info.title,
|
|
2184
|
+
version: this.options.info.version,
|
|
2185
|
+
description: this.options.info.description,
|
|
2186
|
+
contact: this.options.info.contact,
|
|
2187
|
+
license: this.options.info.license
|
|
2188
|
+
},
|
|
2189
|
+
servers: this.options.servers,
|
|
2190
|
+
tags: this.options.tags,
|
|
2191
|
+
paths: {}
|
|
2192
|
+
};
|
|
2193
|
+
const controllerRegistry = ControllerRegistry.getInstance();
|
|
2194
|
+
const controllers = controllerRegistry.getRegisteredControllers();
|
|
2195
|
+
if (controllers.length === 0) {
|
|
2196
|
+
return document;
|
|
2197
|
+
}
|
|
2198
|
+
for (const controllerClass of controllers) {
|
|
2199
|
+
const controllerMetadata = getControllerMetadata(controllerClass);
|
|
2200
|
+
if (!controllerMetadata) {
|
|
2201
|
+
console.log(`[SwaggerGenerator] No metadata for controller: ${controllerClass.name}`);
|
|
2202
|
+
continue;
|
|
2203
|
+
}
|
|
2204
|
+
const basePath = controllerMetadata.path;
|
|
2205
|
+
const prototype = controllerClass.prototype;
|
|
2206
|
+
const routes = getRouteMetadata(prototype);
|
|
2207
|
+
if (!routes || routes.length === 0) {
|
|
2208
|
+
continue;
|
|
2209
|
+
}
|
|
2210
|
+
const controllerTags = getApiTags(controllerClass);
|
|
2211
|
+
for (const route of routes) {
|
|
2212
|
+
let propertyKey = route.propertyKey;
|
|
2213
|
+
if (!propertyKey && route.handler) {
|
|
2214
|
+
propertyKey = route.handler.name;
|
|
2215
|
+
if (!propertyKey || propertyKey === "") {
|
|
2216
|
+
const propertyNames = Object.getOwnPropertyNames(prototype);
|
|
2217
|
+
for (const key of propertyNames) {
|
|
2218
|
+
if (key === "constructor")
|
|
2219
|
+
continue;
|
|
2220
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
|
|
2221
|
+
if (descriptor && descriptor.value === route.handler) {
|
|
2222
|
+
propertyKey = key;
|
|
2223
|
+
break;
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
if (!propertyKey) {
|
|
2229
|
+
continue;
|
|
2230
|
+
}
|
|
2231
|
+
const method = route.method.toLowerCase();
|
|
2232
|
+
let methodPath = route.path;
|
|
2233
|
+
if (methodPath && !methodPath.startsWith("/")) {
|
|
2234
|
+
methodPath = "/" + methodPath;
|
|
2235
|
+
}
|
|
2236
|
+
const swaggerPath = (basePath + methodPath).replace(/:([^/]+)/g, "{$1}");
|
|
2237
|
+
const fullPath = this.normalizePath(swaggerPath);
|
|
2238
|
+
const operationMetadata = getApiOperation(prototype, propertyKey);
|
|
2239
|
+
const methodTags = getApiTags(prototype, propertyKey);
|
|
2240
|
+
const params = getApiParams(prototype, propertyKey);
|
|
2241
|
+
const body = getApiBody(prototype, propertyKey);
|
|
2242
|
+
const responses = getApiResponses(prototype, propertyKey);
|
|
2243
|
+
const tags = [...new Set([...controllerTags, ...methodTags])];
|
|
2244
|
+
const pathItem = {
|
|
2245
|
+
summary: operationMetadata?.summary,
|
|
2246
|
+
description: operationMetadata?.description,
|
|
2247
|
+
operationId: operationMetadata?.operationId || propertyKey,
|
|
2248
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
2249
|
+
deprecated: operationMetadata?.deprecated
|
|
2250
|
+
};
|
|
2251
|
+
const pathParams = [];
|
|
2252
|
+
const pathParamMatches = fullPath.matchAll(/\{([^}]+)\}/g);
|
|
2253
|
+
for (const match of pathParamMatches) {
|
|
2254
|
+
const paramName = match[1];
|
|
2255
|
+
const existingParam = params.find((p) => p.metadata.name === paramName && p.metadata.in === "path");
|
|
2256
|
+
if (!existingParam) {
|
|
2257
|
+
pathParams.push({
|
|
2258
|
+
name: paramName,
|
|
2259
|
+
in: "path",
|
|
2260
|
+
required: true,
|
|
2261
|
+
schema: { type: "string" }
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
for (const param of params) {
|
|
2266
|
+
pathParams.push({
|
|
2267
|
+
name: param.metadata.name,
|
|
2268
|
+
in: param.metadata.in,
|
|
2269
|
+
description: param.metadata.description,
|
|
2270
|
+
required: param.metadata.required,
|
|
2271
|
+
schema: param.metadata.schema
|
|
2272
|
+
});
|
|
2273
|
+
}
|
|
2274
|
+
if (pathParams.length > 0) {
|
|
2275
|
+
pathItem.parameters = pathParams;
|
|
2276
|
+
}
|
|
2277
|
+
if (body) {
|
|
2278
|
+
pathItem.requestBody = {
|
|
2279
|
+
description: body.description,
|
|
2280
|
+
required: body.required,
|
|
2281
|
+
content: {
|
|
2282
|
+
"application/json": {
|
|
2283
|
+
schema: body.schema,
|
|
2284
|
+
examples: body.examples
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
2289
|
+
if (responses.length > 0) {
|
|
2290
|
+
pathItem.responses = {};
|
|
2291
|
+
for (const response of responses) {
|
|
2292
|
+
pathItem.responses[String(response.status)] = {
|
|
2293
|
+
description: response.description,
|
|
2294
|
+
content: {
|
|
2295
|
+
"application/json": {
|
|
2296
|
+
schema: response.schema,
|
|
2297
|
+
examples: response.examples
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
} else {
|
|
2303
|
+
pathItem.responses = {
|
|
2304
|
+
"200": {
|
|
2305
|
+
description: "Success"
|
|
2306
|
+
}
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
if (!document.paths[fullPath]) {
|
|
2310
|
+
document.paths[fullPath] = {};
|
|
2311
|
+
}
|
|
2312
|
+
document.paths[fullPath][method] = pathItem;
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
return document;
|
|
2316
|
+
}
|
|
2317
|
+
normalizePath(path) {
|
|
2318
|
+
path = path.replace(/\/+/g, "/");
|
|
2319
|
+
if (!path.startsWith("/")) {
|
|
2320
|
+
path = "/" + path;
|
|
2321
|
+
}
|
|
2322
|
+
if (path.length > 1 && path.endsWith("/")) {
|
|
2323
|
+
path = path.slice(0, -1);
|
|
2324
|
+
}
|
|
2325
|
+
return path;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
// src/swagger/swagger-extension.ts
|
|
2329
|
+
class SwaggerExtension {
|
|
2330
|
+
options;
|
|
2331
|
+
generator;
|
|
2332
|
+
constructor(options) {
|
|
2333
|
+
this.options = options;
|
|
2334
|
+
}
|
|
2335
|
+
register(_container) {
|
|
2336
|
+
this.generator = new SwaggerGenerator(this.options);
|
|
2337
|
+
}
|
|
2338
|
+
getGenerator() {
|
|
2339
|
+
if (!this.generator) {
|
|
2340
|
+
this.generator = new SwaggerGenerator(this.options);
|
|
2341
|
+
}
|
|
2342
|
+
return this.generator;
|
|
2343
|
+
}
|
|
2344
|
+
generateJSON() {
|
|
2345
|
+
return JSON.stringify(this.getGenerator().generate(), null, 2);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
// src/swagger/ui.ts
|
|
2349
|
+
var SWAGGER_UI_HTML = (jsonUrl, title) => `<!DOCTYPE html>
|
|
2350
|
+
<html lang="en">
|
|
2351
|
+
<head>
|
|
2352
|
+
<meta charset="UTF-8">
|
|
2353
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2354
|
+
<title>${title} - Swagger UI</title>
|
|
2355
|
+
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui.css" />
|
|
2356
|
+
<style>
|
|
2357
|
+
html {
|
|
2358
|
+
box-sizing: border-box;
|
|
2359
|
+
overflow: -moz-scrollbars-vertical;
|
|
2360
|
+
overflow-y: scroll;
|
|
2361
|
+
}
|
|
2362
|
+
*, *:before, *:after {
|
|
2363
|
+
box-sizing: inherit;
|
|
2364
|
+
}
|
|
2365
|
+
body {
|
|
2366
|
+
margin:0;
|
|
2367
|
+
background: #fafafa;
|
|
2368
|
+
}
|
|
2369
|
+
</style>
|
|
2370
|
+
</head>
|
|
2371
|
+
<body>
|
|
2372
|
+
<div id="swagger-ui"></div>
|
|
2373
|
+
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-bundle.js"></script>
|
|
2374
|
+
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-standalone-preset.js"></script>
|
|
2375
|
+
<script>
|
|
2376
|
+
window.onload = function() {
|
|
2377
|
+
const ui = SwaggerUIBundle({
|
|
2378
|
+
url: "${jsonUrl}",
|
|
2379
|
+
dom_id: '#swagger-ui',
|
|
2380
|
+
deepLinking: true,
|
|
2381
|
+
presets: [
|
|
2382
|
+
SwaggerUIBundle.presets.apis,
|
|
2383
|
+
SwaggerUIStandalonePreset
|
|
2384
|
+
],
|
|
2385
|
+
plugins: [
|
|
2386
|
+
SwaggerUIBundle.plugins.DownloadUrl
|
|
2387
|
+
],
|
|
2388
|
+
layout: "StandaloneLayout"
|
|
2389
|
+
});
|
|
2390
|
+
};
|
|
2391
|
+
</script>
|
|
2392
|
+
</body>
|
|
2393
|
+
</html>`;
|
|
2394
|
+
function createSwaggerUIMiddleware(extension, options = {}) {
|
|
2395
|
+
const uiPath = options.uiPath || "/swagger";
|
|
2396
|
+
const jsonPath = options.jsonPath || "/swagger.json";
|
|
2397
|
+
const title = options.title || "API Documentation";
|
|
2398
|
+
return async (ctx, next) => {
|
|
2399
|
+
const url = new URL(ctx.request.url);
|
|
2400
|
+
const pathname = url.pathname;
|
|
2401
|
+
if (pathname === uiPath || pathname === `${uiPath}/`) {
|
|
2402
|
+
const html = SWAGGER_UI_HTML(jsonPath, title);
|
|
2403
|
+
return new Response(html, {
|
|
2404
|
+
headers: {
|
|
2405
|
+
"Content-Type": "text/html; charset=utf-8"
|
|
2406
|
+
}
|
|
2407
|
+
});
|
|
2408
|
+
}
|
|
2409
|
+
if (pathname === jsonPath) {
|
|
2410
|
+
const json = extension.generateJSON();
|
|
2411
|
+
return new Response(json, {
|
|
2412
|
+
headers: {
|
|
2413
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
2414
|
+
}
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
return await next();
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
// src/swagger/swagger-module.ts
|
|
2422
|
+
class SwaggerModule {
|
|
2423
|
+
static forRoot(options) {
|
|
2424
|
+
const extensions = [];
|
|
2425
|
+
const middlewares = [];
|
|
2426
|
+
const swaggerExtension = new SwaggerExtension({
|
|
2427
|
+
info: options.info,
|
|
2428
|
+
servers: options.servers,
|
|
2429
|
+
basePath: options.basePath,
|
|
2430
|
+
tags: options.tags
|
|
2431
|
+
});
|
|
2432
|
+
extensions.push(swaggerExtension);
|
|
2433
|
+
if (options.enableUI !== false) {
|
|
2434
|
+
const uiMiddleware = createSwaggerUIMiddleware(swaggerExtension, {
|
|
2435
|
+
uiPath: options.uiPath || "/swagger",
|
|
2436
|
+
jsonPath: options.jsonPath || "/swagger.json",
|
|
2437
|
+
title: options.uiTitle || options.info.title || "API Documentation"
|
|
2438
|
+
});
|
|
2439
|
+
middlewares.push(uiMiddleware);
|
|
2440
|
+
}
|
|
2441
|
+
const existingMetadata = Reflect.getMetadata(MODULE_METADATA_KEY, SwaggerModule) || {};
|
|
2442
|
+
const metadata = {
|
|
2443
|
+
...existingMetadata,
|
|
2444
|
+
extensions,
|
|
2445
|
+
middlewares
|
|
2446
|
+
};
|
|
2447
|
+
Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, SwaggerModule);
|
|
2448
|
+
return SwaggerModule;
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
SwaggerModule = __legacyDecorateClassTS([
|
|
2452
|
+
Module({
|
|
2453
|
+
extensions: [],
|
|
2454
|
+
middlewares: []
|
|
2455
|
+
})
|
|
2456
|
+
], SwaggerModule);
|
|
2457
|
+
// src/security/context.ts
|
|
2458
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2459
|
+
|
|
2460
|
+
class SecurityContextImpl {
|
|
2461
|
+
_authentication = null;
|
|
2462
|
+
get authentication() {
|
|
2463
|
+
return this._authentication;
|
|
2464
|
+
}
|
|
2465
|
+
setAuthentication(authentication) {
|
|
2466
|
+
this._authentication = authentication;
|
|
2467
|
+
}
|
|
2468
|
+
isAuthenticated() {
|
|
2469
|
+
return this._authentication?.authenticated ?? false;
|
|
2470
|
+
}
|
|
2471
|
+
getPrincipal() {
|
|
2472
|
+
return this._authentication?.principal ?? null;
|
|
2473
|
+
}
|
|
2474
|
+
getAuthorities() {
|
|
2475
|
+
return this._authentication?.authorities ?? [];
|
|
2476
|
+
}
|
|
2477
|
+
clear() {
|
|
2478
|
+
this._authentication = null;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
class SecurityContextHolder {
|
|
2483
|
+
static storage = new AsyncLocalStorage;
|
|
2484
|
+
static getContext() {
|
|
2485
|
+
let context = this.storage.getStore();
|
|
2486
|
+
if (!context) {
|
|
2487
|
+
context = new SecurityContextImpl;
|
|
2488
|
+
this.storage.enterWith(context);
|
|
2489
|
+
}
|
|
2490
|
+
return context;
|
|
2491
|
+
}
|
|
2492
|
+
static runWithContext(callback) {
|
|
2493
|
+
const existing = this.storage.getStore() ?? new SecurityContextImpl;
|
|
2494
|
+
return this.storage.run(existing, callback);
|
|
2495
|
+
}
|
|
2496
|
+
static clearContext() {
|
|
2497
|
+
const context = this.storage.getStore();
|
|
2498
|
+
if (context) {
|
|
2499
|
+
context.clear();
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
// src/security/authentication-manager.ts
|
|
2504
|
+
class AuthenticationManager {
|
|
2505
|
+
providers = [];
|
|
2506
|
+
registerProvider(provider) {
|
|
2507
|
+
this.providers.push(provider);
|
|
2508
|
+
}
|
|
2509
|
+
async authenticate(request) {
|
|
2510
|
+
const type = request.type || "default";
|
|
2511
|
+
const provider = this.providers.find((p) => p.supports(type));
|
|
2512
|
+
if (!provider) {
|
|
2513
|
+
throw new Error(`No authentication provider found for type: ${type}`);
|
|
2514
|
+
}
|
|
2515
|
+
return await provider.authenticate(request);
|
|
2516
|
+
}
|
|
2517
|
+
getProviders() {
|
|
2518
|
+
return [...this.providers];
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
// src/security/access-decision-manager.ts
|
|
2522
|
+
class RoleBasedAccessDecisionManager {
|
|
2523
|
+
decide(authentication, requiredAuthorities) {
|
|
2524
|
+
if (requiredAuthorities.length === 0) {
|
|
2525
|
+
return true;
|
|
2526
|
+
}
|
|
2527
|
+
if (!authentication.authenticated) {
|
|
2528
|
+
return false;
|
|
2529
|
+
}
|
|
2530
|
+
const userAuthorities = authentication.authorities || [];
|
|
2531
|
+
return requiredAuthorities.some((required) => userAuthorities.includes(required));
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
// src/auth/decorators.ts
|
|
2535
|
+
import"reflect-metadata";
|
|
2536
|
+
var AUTH_METADATA_KEY = Symbol("@dangao/bun-server:auth");
|
|
2537
|
+
function Auth(config = {}) {
|
|
2538
|
+
return (target, propertyKey) => {
|
|
2539
|
+
const metadata = Reflect.getMetadata(AUTH_METADATA_KEY, target) || {};
|
|
2540
|
+
metadata[propertyKey] = {
|
|
2541
|
+
required: config.required !== false,
|
|
2542
|
+
roles: config.roles || [],
|
|
2543
|
+
allowAnonymous: config.allowAnonymous || false
|
|
2544
|
+
};
|
|
2545
|
+
Reflect.defineMetadata(AUTH_METADATA_KEY, metadata, target);
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
function getAuthMetadata(target, propertyKey) {
|
|
2549
|
+
const metadata = Reflect.getMetadata(AUTH_METADATA_KEY, target);
|
|
2550
|
+
return metadata?.[propertyKey];
|
|
2551
|
+
}
|
|
2552
|
+
function requiresAuth(target, propertyKey) {
|
|
2553
|
+
const config = getAuthMetadata(target, propertyKey);
|
|
2554
|
+
if (!config) {
|
|
2555
|
+
return false;
|
|
2556
|
+
}
|
|
2557
|
+
return config.required !== false;
|
|
2558
|
+
}
|
|
2559
|
+
function checkRoles(target, propertyKey, userRoles = []) {
|
|
2560
|
+
const config = getAuthMetadata(target, propertyKey);
|
|
2561
|
+
if (!config?.roles || config.roles.length === 0) {
|
|
2562
|
+
return true;
|
|
2563
|
+
}
|
|
2564
|
+
return config.roles.some((role) => userRoles.includes(role));
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
// src/security/filter.ts
|
|
2568
|
+
function createSecurityFilter(config) {
|
|
2569
|
+
const {
|
|
2570
|
+
authenticationManager,
|
|
2571
|
+
accessDecisionManager = new RoleBasedAccessDecisionManager,
|
|
2572
|
+
excludePaths = [],
|
|
2573
|
+
defaultAuthRequired = true,
|
|
2574
|
+
extractToken
|
|
2575
|
+
} = config;
|
|
2576
|
+
return async (ctx, next) => {
|
|
2577
|
+
return SecurityContextHolder.runWithContext(async () => {
|
|
2578
|
+
const path = ctx.path || ctx.request.url.split("?")[0].replace(/^https?:\/\/[^/]+/, "");
|
|
2579
|
+
if (excludePaths.some((exclude) => path.startsWith(exclude))) {
|
|
2580
|
+
return await next();
|
|
2581
|
+
}
|
|
2582
|
+
const securityContext = SecurityContextHolder.getContext();
|
|
2583
|
+
try {
|
|
2584
|
+
const token = extractToken ? extractToken(ctx) : extractTokenFromHeader(ctx);
|
|
2585
|
+
if (token) {
|
|
2586
|
+
const request = {
|
|
2587
|
+
principal: "",
|
|
2588
|
+
credentials: token,
|
|
2589
|
+
type: "jwt"
|
|
2590
|
+
};
|
|
2591
|
+
const authentication = await authenticationManager.authenticate(request);
|
|
2592
|
+
if (authentication) {
|
|
2593
|
+
securityContext.setAuthentication(authentication);
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
const handler = ctx.routeHandler;
|
|
2597
|
+
if (handler) {
|
|
2598
|
+
const controllerClass = handler.controller;
|
|
2599
|
+
const controllerTarget = controllerClass && controllerClass.prototype || controllerClass;
|
|
2600
|
+
const method = handler.method;
|
|
2601
|
+
if (requiresAuth(controllerTarget, method)) {
|
|
2602
|
+
const authentication = securityContext.authentication;
|
|
2603
|
+
if (!authentication || !authentication.authenticated) {
|
|
2604
|
+
throw new UnauthorizedException("Authentication required");
|
|
2605
|
+
}
|
|
2606
|
+
const requiredRoles = getRequiredRoles(controllerTarget, method);
|
|
2607
|
+
if (requiredRoles.length > 0) {
|
|
2608
|
+
const hasAccess = accessDecisionManager.decide(authentication, requiredRoles);
|
|
2609
|
+
if (!hasAccess) {
|
|
2610
|
+
const userRoles = authentication.authorities || [];
|
|
2611
|
+
throw new ForbiddenException(`Insufficient permissions. Required roles: ${requiredRoles.join(", ")}, User roles: ${userRoles.join(", ")}`);
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
} else if (defaultAuthRequired && !securityContext.isAuthenticated()) {
|
|
2616
|
+
throw new UnauthorizedException("Authentication required");
|
|
2617
|
+
}
|
|
2618
|
+
ctx.security = securityContext;
|
|
2619
|
+
ctx.auth = {
|
|
2620
|
+
isAuthenticated: securityContext.isAuthenticated(),
|
|
2621
|
+
user: securityContext.getPrincipal(),
|
|
2622
|
+
payload: securityContext.authentication?.details
|
|
2623
|
+
};
|
|
2624
|
+
return await next();
|
|
2625
|
+
} finally {
|
|
2626
|
+
SecurityContextHolder.clearContext();
|
|
2627
|
+
}
|
|
2628
|
+
});
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
function extractTokenFromHeader(ctx) {
|
|
2632
|
+
const authHeader = ctx.getHeader("authorization");
|
|
2633
|
+
if (!authHeader) {
|
|
2634
|
+
return null;
|
|
2635
|
+
}
|
|
2636
|
+
const parts = authHeader.split(" ");
|
|
2637
|
+
if (parts.length !== 2 || parts[0] !== "Bearer") {
|
|
2638
|
+
return null;
|
|
2639
|
+
}
|
|
2640
|
+
return parts[1];
|
|
2641
|
+
}
|
|
2642
|
+
function getRequiredRoles(target, propertyKey) {
|
|
2643
|
+
const metadata = getAuthMetadata(target, propertyKey);
|
|
2644
|
+
return metadata?.roles || [];
|
|
2645
|
+
}
|
|
2646
|
+
// src/security/providers/jwt-provider.ts
|
|
2647
|
+
class JwtAuthenticationProvider {
|
|
2648
|
+
jwtUtil;
|
|
2649
|
+
supportedTypes = ["jwt", "bearer"];
|
|
2650
|
+
constructor(jwtUtil) {
|
|
2651
|
+
this.jwtUtil = jwtUtil;
|
|
2652
|
+
}
|
|
2653
|
+
supports(type) {
|
|
2654
|
+
return this.supportedTypes.includes(type.toLowerCase());
|
|
2655
|
+
}
|
|
2656
|
+
async authenticate(request) {
|
|
2657
|
+
const token = request.credentials;
|
|
2658
|
+
if (!token) {
|
|
2659
|
+
return null;
|
|
2660
|
+
}
|
|
2661
|
+
const payload = this.jwtUtil.verify(token);
|
|
2662
|
+
if (!payload) {
|
|
2663
|
+
return null;
|
|
2664
|
+
}
|
|
2665
|
+
const principal = {
|
|
2666
|
+
id: payload.sub,
|
|
2667
|
+
username: payload.username || payload.sub,
|
|
2668
|
+
roles: payload.roles || []
|
|
2669
|
+
};
|
|
2670
|
+
const authorities = principal.roles || [];
|
|
2671
|
+
return {
|
|
2672
|
+
authenticated: true,
|
|
2673
|
+
principal,
|
|
2674
|
+
credentials: { type: "jwt", data: token },
|
|
2675
|
+
authorities,
|
|
2676
|
+
details: payload
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
// src/security/providers/oauth2-provider.ts
|
|
2681
|
+
class OAuth2AuthenticationProvider {
|
|
2682
|
+
oauth2Service;
|
|
2683
|
+
supportedTypes = ["oauth2", "authorization_code"];
|
|
2684
|
+
constructor(oauth2Service) {
|
|
2685
|
+
this.oauth2Service = oauth2Service;
|
|
2686
|
+
}
|
|
2687
|
+
supports(type) {
|
|
2688
|
+
return this.supportedTypes.includes(type.toLowerCase());
|
|
2689
|
+
}
|
|
2690
|
+
async authenticate(request) {
|
|
2691
|
+
const credentials = request.credentials;
|
|
2692
|
+
if (!credentials || credentials.grantType !== "authorization_code") {
|
|
2693
|
+
return null;
|
|
2694
|
+
}
|
|
2695
|
+
const tokenResponse = await this.oauth2Service.exchangeCodeForToken(credentials);
|
|
2696
|
+
if (!tokenResponse) {
|
|
2697
|
+
return null;
|
|
2698
|
+
}
|
|
2699
|
+
const userInfo = {
|
|
2700
|
+
id: "user-1",
|
|
2701
|
+
username: "user",
|
|
2702
|
+
roles: ["user"]
|
|
2703
|
+
};
|
|
2704
|
+
const principal = {
|
|
2705
|
+
id: userInfo.id,
|
|
2706
|
+
username: userInfo.username,
|
|
2707
|
+
roles: userInfo.roles
|
|
2708
|
+
};
|
|
2709
|
+
return {
|
|
2710
|
+
authenticated: true,
|
|
2711
|
+
principal,
|
|
2712
|
+
credentials: {
|
|
2713
|
+
type: "oauth2",
|
|
2714
|
+
data: tokenResponse
|
|
2715
|
+
},
|
|
2716
|
+
authorities: principal.roles || [],
|
|
2717
|
+
details: tokenResponse
|
|
2718
|
+
};
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
// src/auth/jwt.ts
|
|
2722
|
+
class JWTUtil {
|
|
2723
|
+
config;
|
|
2724
|
+
constructor(config) {
|
|
2725
|
+
this.config = {
|
|
2726
|
+
secret: config.secret,
|
|
2727
|
+
accessTokenExpiresIn: config.accessTokenExpiresIn ?? 3600,
|
|
2728
|
+
refreshTokenExpiresIn: config.refreshTokenExpiresIn ?? 86400 * 7,
|
|
2729
|
+
algorithm: config.algorithm ?? "HS256"
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
generateAccessToken(payload) {
|
|
2733
|
+
const now = Math.floor(Date.now() / 1000);
|
|
2734
|
+
const tokenPayload = {
|
|
2735
|
+
...payload,
|
|
2736
|
+
sub: payload.sub,
|
|
2737
|
+
iat: now,
|
|
2738
|
+
exp: now + this.config.accessTokenExpiresIn
|
|
2739
|
+
};
|
|
2740
|
+
return this.sign(tokenPayload);
|
|
2741
|
+
}
|
|
2742
|
+
generateRefreshToken(payload) {
|
|
2743
|
+
const now = Math.floor(Date.now() / 1000);
|
|
2744
|
+
const tokenPayload = {
|
|
2745
|
+
...payload,
|
|
2746
|
+
sub: payload.sub,
|
|
2747
|
+
iat: now,
|
|
2748
|
+
exp: now + this.config.refreshTokenExpiresIn
|
|
2749
|
+
};
|
|
2750
|
+
return this.sign(tokenPayload);
|
|
2751
|
+
}
|
|
2752
|
+
verify(token) {
|
|
2753
|
+
try {
|
|
2754
|
+
const parts = token.split(".");
|
|
2755
|
+
if (parts.length !== 3) {
|
|
2756
|
+
return null;
|
|
2757
|
+
}
|
|
2758
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
|
|
2759
|
+
const signature = this.signature(parts[0] + "." + parts[1]);
|
|
2760
|
+
if (signature !== parts[2]) {
|
|
2761
|
+
return null;
|
|
2762
|
+
}
|
|
2763
|
+
if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
|
|
2764
|
+
return null;
|
|
2765
|
+
}
|
|
2766
|
+
return payload;
|
|
2767
|
+
} catch {
|
|
2768
|
+
return null;
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
sign(payload) {
|
|
2772
|
+
const header = {
|
|
2773
|
+
alg: this.config.algorithm,
|
|
2774
|
+
typ: "JWT"
|
|
2775
|
+
};
|
|
2776
|
+
const encodedHeader = this.base64UrlEncode(JSON.stringify(header));
|
|
2777
|
+
const encodedPayload = this.base64UrlEncode(JSON.stringify(payload));
|
|
2778
|
+
const signature = this.signature(encodedHeader + "." + encodedPayload);
|
|
2779
|
+
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
|
2780
|
+
}
|
|
2781
|
+
signature(data) {
|
|
2782
|
+
const encoder = new TextEncoder;
|
|
2783
|
+
const keyData = encoder.encode(this.config.secret);
|
|
2784
|
+
const messageData = encoder.encode(data);
|
|
2785
|
+
const hash = this.hmacSha256(keyData, messageData);
|
|
2786
|
+
return this.base64UrlEncode(Buffer.from(hash).toString("base64"));
|
|
2787
|
+
}
|
|
2788
|
+
hmacSha256(key, data) {
|
|
2789
|
+
const blockSize = 64;
|
|
2790
|
+
let keyBuffer;
|
|
2791
|
+
if (key.length > blockSize) {
|
|
2792
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
2793
|
+
hasher.update(key);
|
|
2794
|
+
keyBuffer = new Uint8Array(hasher.digest());
|
|
2795
|
+
} else {
|
|
2796
|
+
keyBuffer = new Uint8Array(blockSize);
|
|
2797
|
+
keyBuffer.set(key);
|
|
2798
|
+
}
|
|
2799
|
+
const oKeyPad = new Uint8Array(blockSize);
|
|
2800
|
+
const iKeyPad = new Uint8Array(blockSize);
|
|
2801
|
+
for (let i = 0;i < blockSize; i++) {
|
|
2802
|
+
oKeyPad[i] = keyBuffer[i] ^ 92;
|
|
2803
|
+
iKeyPad[i] = keyBuffer[i] ^ 54;
|
|
2804
|
+
}
|
|
2805
|
+
const innerData = new Uint8Array(iKeyPad.length + data.length);
|
|
2806
|
+
innerData.set(iKeyPad);
|
|
2807
|
+
innerData.set(data, iKeyPad.length);
|
|
2808
|
+
const innerHasher = new Bun.CryptoHasher("sha256");
|
|
2809
|
+
innerHasher.update(innerData);
|
|
2810
|
+
const innerHash = new Uint8Array(innerHasher.digest());
|
|
2811
|
+
const outerData = new Uint8Array(oKeyPad.length + innerHash.length);
|
|
2812
|
+
outerData.set(oKeyPad);
|
|
2813
|
+
outerData.set(innerHash, oKeyPad.length);
|
|
2814
|
+
const outerHasher = new Bun.CryptoHasher("sha256");
|
|
2815
|
+
outerHasher.update(outerData);
|
|
2816
|
+
return new Uint8Array(outerHasher.digest());
|
|
2817
|
+
}
|
|
2818
|
+
base64UrlEncode(str) {
|
|
2819
|
+
return Buffer.from(str).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
// src/auth/oauth2.ts
|
|
2824
|
+
class OAuth2Service {
|
|
2825
|
+
jwtUtil;
|
|
2826
|
+
clients;
|
|
2827
|
+
codes;
|
|
2828
|
+
codeConfig;
|
|
2829
|
+
userProvider;
|
|
2830
|
+
constructor(jwtUtil, clients = [], codeConfig = {}, userProvider) {
|
|
2831
|
+
this.jwtUtil = jwtUtil;
|
|
2832
|
+
this.clients = new Map;
|
|
2833
|
+
this.codes = new Map;
|
|
2834
|
+
this.codeConfig = {
|
|
2835
|
+
expiresIn: codeConfig.expiresIn ?? 600,
|
|
2836
|
+
length: codeConfig.length ?? 32
|
|
2837
|
+
};
|
|
2838
|
+
for (const client of clients) {
|
|
2839
|
+
this.clients.set(client.clientId, client);
|
|
2840
|
+
}
|
|
2841
|
+
this.userProvider = userProvider;
|
|
2842
|
+
setInterval(() => this.cleanupExpiredCodes(), 60000);
|
|
2843
|
+
}
|
|
2844
|
+
registerClient(client) {
|
|
2845
|
+
this.clients.set(client.clientId, client);
|
|
2846
|
+
}
|
|
2847
|
+
validateAuthorizationRequest(request) {
|
|
2848
|
+
const client = this.clients.get(request.clientId);
|
|
2849
|
+
if (!client) {
|
|
2850
|
+
return { valid: false, error: "invalid_client" };
|
|
2851
|
+
}
|
|
2852
|
+
if (request.responseType !== "code") {
|
|
2853
|
+
return { valid: false, error: "unsupported_response_type" };
|
|
2854
|
+
}
|
|
2855
|
+
if (!client.redirectUris.includes(request.redirectUri)) {
|
|
2856
|
+
return { valid: false, error: "invalid_redirect_uri" };
|
|
2857
|
+
}
|
|
2858
|
+
return { valid: true };
|
|
2859
|
+
}
|
|
2860
|
+
generateAuthorizationCode(clientId, redirectUri, userId, scope) {
|
|
2861
|
+
const code = this.generateRandomString(this.codeConfig.length);
|
|
2862
|
+
const expiresAt = Date.now() + this.codeConfig.expiresIn * 1000;
|
|
2863
|
+
this.codes.set(code, {
|
|
2864
|
+
code,
|
|
2865
|
+
clientId,
|
|
2866
|
+
redirectUri,
|
|
2867
|
+
userId,
|
|
2868
|
+
scope,
|
|
2869
|
+
expiresAt
|
|
2870
|
+
});
|
|
2871
|
+
return code;
|
|
2872
|
+
}
|
|
2873
|
+
async exchangeCodeForToken(request) {
|
|
2874
|
+
if (request.grantType !== "authorization_code") {
|
|
2875
|
+
return null;
|
|
2876
|
+
}
|
|
2877
|
+
const codeData = this.codes.get(request.code);
|
|
2878
|
+
if (!codeData) {
|
|
2879
|
+
return null;
|
|
2880
|
+
}
|
|
2881
|
+
if (codeData.expiresAt < Date.now()) {
|
|
2882
|
+
this.codes.delete(request.code);
|
|
2883
|
+
return null;
|
|
2884
|
+
}
|
|
2885
|
+
const client = this.clients.get(request.clientId);
|
|
2886
|
+
if (!client || client.clientSecret !== request.clientSecret) {
|
|
2887
|
+
return null;
|
|
2888
|
+
}
|
|
2889
|
+
if (codeData.redirectUri !== request.redirectUri) {
|
|
2890
|
+
return null;
|
|
2891
|
+
}
|
|
2892
|
+
this.codes.delete(request.code);
|
|
2893
|
+
let userInfo = null;
|
|
2894
|
+
if (this.userProvider) {
|
|
2895
|
+
userInfo = await this.userProvider(codeData.userId);
|
|
2896
|
+
}
|
|
2897
|
+
if (!userInfo) {
|
|
2898
|
+
userInfo = {
|
|
2899
|
+
id: codeData.userId,
|
|
2900
|
+
username: codeData.userId
|
|
2901
|
+
};
|
|
2902
|
+
}
|
|
2903
|
+
const payload = {
|
|
2904
|
+
sub: userInfo.id,
|
|
2905
|
+
username: userInfo.username,
|
|
2906
|
+
roles: userInfo.roles
|
|
2907
|
+
};
|
|
2908
|
+
const accessToken = this.jwtUtil.generateAccessToken(payload);
|
|
2909
|
+
const refreshToken = this.jwtUtil.generateRefreshToken(payload);
|
|
2910
|
+
return {
|
|
2911
|
+
accessToken,
|
|
2912
|
+
tokenType: "Bearer",
|
|
2913
|
+
expiresIn: this.jwtUtil["config"].accessTokenExpiresIn,
|
|
2914
|
+
refreshToken,
|
|
2915
|
+
scope: codeData.scope
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
async refreshToken(refreshToken) {
|
|
2919
|
+
const payload = this.jwtUtil.verify(refreshToken);
|
|
2920
|
+
if (!payload || !payload.sub) {
|
|
2921
|
+
return null;
|
|
2922
|
+
}
|
|
2923
|
+
let userInfo = null;
|
|
2924
|
+
if (this.userProvider) {
|
|
2925
|
+
userInfo = await this.userProvider(payload.sub);
|
|
2926
|
+
}
|
|
2927
|
+
if (!userInfo) {
|
|
2928
|
+
userInfo = {
|
|
2929
|
+
id: payload.sub,
|
|
2930
|
+
username: payload.username || payload.sub,
|
|
2931
|
+
roles: payload.roles
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
const newPayload = {
|
|
2935
|
+
sub: userInfo.id,
|
|
2936
|
+
username: userInfo.username,
|
|
2937
|
+
roles: userInfo.roles
|
|
2938
|
+
};
|
|
2939
|
+
const accessToken = this.jwtUtil.generateAccessToken(newPayload);
|
|
2940
|
+
const newRefreshToken = this.jwtUtil.generateRefreshToken(newPayload);
|
|
2941
|
+
return {
|
|
2942
|
+
accessToken,
|
|
2943
|
+
tokenType: "Bearer",
|
|
2944
|
+
expiresIn: this.jwtUtil["config"].accessTokenExpiresIn,
|
|
2945
|
+
refreshToken: newRefreshToken
|
|
2946
|
+
};
|
|
2947
|
+
}
|
|
2948
|
+
generateRandomString(length) {
|
|
2949
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
2950
|
+
let result = "";
|
|
2951
|
+
for (let i = 0;i < length; i++) {
|
|
2952
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
2953
|
+
}
|
|
2954
|
+
return result;
|
|
2955
|
+
}
|
|
2956
|
+
cleanupExpiredCodes() {
|
|
2957
|
+
const now = Date.now();
|
|
2958
|
+
for (const [code, data] of this.codes.entries()) {
|
|
2959
|
+
if (data.expiresAt < now) {
|
|
2960
|
+
this.codes.delete(code);
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
// src/auth/controller.ts
|
|
2966
|
+
var OAUTH2_SERVICE_TOKEN = Symbol("OAUTH2_SERVICE");
|
|
2967
|
+
var JWT_UTIL_TOKEN = Symbol("JWT_UTIL");
|
|
2968
|
+
|
|
2969
|
+
class OAuth2Controller {
|
|
2970
|
+
oauth2Service;
|
|
2971
|
+
constructor(oauth2Service) {
|
|
2972
|
+
this.oauth2Service = oauth2Service;
|
|
2973
|
+
}
|
|
2974
|
+
authorize(clientId, redirectUri, state, scope) {
|
|
2975
|
+
const query = {
|
|
2976
|
+
client_id: clientId,
|
|
2977
|
+
redirect_uri: redirectUri,
|
|
2978
|
+
...state && { state },
|
|
2979
|
+
...scope && { scope }
|
|
2980
|
+
};
|
|
2981
|
+
const request = {
|
|
2982
|
+
clientId: query.client_id || "",
|
|
2983
|
+
redirectUri: query.redirect_uri || "",
|
|
2984
|
+
responseType: "code",
|
|
2985
|
+
scope: query.scope,
|
|
2986
|
+
state: query.state
|
|
2987
|
+
};
|
|
2988
|
+
const validation = this.oauth2Service.validateAuthorizationRequest(request);
|
|
2989
|
+
if (!validation.valid) {
|
|
2990
|
+
throw new Error(`Invalid authorization request: ${validation.error}`);
|
|
2991
|
+
}
|
|
2992
|
+
const userId = "user-1";
|
|
2993
|
+
const code = this.oauth2Service.generateAuthorizationCode(request.clientId, request.redirectUri, userId, request.scope);
|
|
2994
|
+
const redirectUrl = new URL(request.redirectUri);
|
|
2995
|
+
redirectUrl.searchParams.set("code", code);
|
|
2996
|
+
if (request.state) {
|
|
2997
|
+
redirectUrl.searchParams.set("state", request.state);
|
|
2998
|
+
}
|
|
2999
|
+
return ResponseBuilder.redirect(redirectUrl.toString());
|
|
3000
|
+
}
|
|
3001
|
+
async token(body) {
|
|
3002
|
+
const request = {
|
|
3003
|
+
code: body.code || "",
|
|
3004
|
+
clientId: body.client_id || "",
|
|
3005
|
+
clientSecret: body.client_secret || "",
|
|
3006
|
+
redirectUri: body.redirect_uri || "",
|
|
3007
|
+
grantType: body.grant_type || "authorization_code",
|
|
3008
|
+
refreshToken: body.refresh_token
|
|
3009
|
+
};
|
|
3010
|
+
if (request.grantType === "authorization_code") {
|
|
3011
|
+
const tokenResponse = await this.oauth2Service.exchangeCodeForToken(request);
|
|
3012
|
+
if (!tokenResponse) {
|
|
3013
|
+
return {
|
|
3014
|
+
error: "invalid_grant",
|
|
3015
|
+
error_description: "Invalid authorization code"
|
|
3016
|
+
};
|
|
3017
|
+
}
|
|
3018
|
+
return tokenResponse;
|
|
3019
|
+
}
|
|
3020
|
+
if (request.grantType === "refresh_token" && request.refreshToken) {
|
|
3021
|
+
const tokenResponse = await this.oauth2Service.refreshToken(request.refreshToken);
|
|
3022
|
+
if (!tokenResponse) {
|
|
3023
|
+
return {
|
|
3024
|
+
error: "invalid_grant",
|
|
3025
|
+
error_description: "Invalid refresh token"
|
|
3026
|
+
};
|
|
3027
|
+
}
|
|
3028
|
+
return tokenResponse;
|
|
3029
|
+
}
|
|
3030
|
+
return {
|
|
3031
|
+
error: "unsupported_grant_type",
|
|
3032
|
+
error_description: "Unsupported grant type"
|
|
3033
|
+
};
|
|
3034
|
+
}
|
|
3035
|
+
userinfo() {
|
|
3036
|
+
return {
|
|
3037
|
+
sub: "user-1",
|
|
3038
|
+
username: "alice",
|
|
3039
|
+
roles: ["user"]
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
__legacyDecorateClassTS([
|
|
3044
|
+
GET("/authorize"),
|
|
3045
|
+
__legacyDecorateParamTS(0, Query("client_id")),
|
|
3046
|
+
__legacyDecorateParamTS(1, Query("redirect_uri")),
|
|
3047
|
+
__legacyDecorateParamTS(2, Query("state")),
|
|
3048
|
+
__legacyDecorateParamTS(3, Query("scope")),
|
|
3049
|
+
__legacyMetadataTS("design:type", Function),
|
|
3050
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
3051
|
+
String,
|
|
3052
|
+
String,
|
|
3053
|
+
String,
|
|
3054
|
+
String
|
|
3055
|
+
]),
|
|
3056
|
+
__legacyMetadataTS("design:returntype", undefined)
|
|
3057
|
+
], OAuth2Controller.prototype, "authorize", null);
|
|
3058
|
+
__legacyDecorateClassTS([
|
|
3059
|
+
POST("/token"),
|
|
3060
|
+
__legacyDecorateParamTS(0, Body()),
|
|
3061
|
+
__legacyMetadataTS("design:type", Function),
|
|
3062
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
3063
|
+
typeof Record === "undefined" ? Object : Record
|
|
3064
|
+
]),
|
|
3065
|
+
__legacyMetadataTS("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
3066
|
+
], OAuth2Controller.prototype, "token", null);
|
|
3067
|
+
__legacyDecorateClassTS([
|
|
3068
|
+
GET("/userinfo"),
|
|
3069
|
+
Auth(),
|
|
3070
|
+
__legacyMetadataTS("design:type", Function),
|
|
3071
|
+
__legacyMetadataTS("design:paramtypes", []),
|
|
3072
|
+
__legacyMetadataTS("design:returntype", undefined)
|
|
3073
|
+
], OAuth2Controller.prototype, "userinfo", null);
|
|
3074
|
+
OAuth2Controller = __legacyDecorateClassTS([
|
|
3075
|
+
Controller("/oauth2"),
|
|
3076
|
+
Injectable(),
|
|
3077
|
+
__legacyDecorateParamTS(0, Inject(OAUTH2_SERVICE_TOKEN)),
|
|
3078
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
3079
|
+
typeof OAuth2Service === "undefined" ? Object : OAuth2Service
|
|
3080
|
+
])
|
|
3081
|
+
], OAuth2Controller);
|
|
3082
|
+
|
|
3083
|
+
// src/security/security-module.ts
|
|
3084
|
+
class SecurityModule {
|
|
3085
|
+
static forRoot(config) {
|
|
3086
|
+
const jwtUtil = new JWTUtil(config.jwt);
|
|
3087
|
+
const userProvider = config.userProvider ? async (userId) => config.userProvider.findById(userId) : undefined;
|
|
3088
|
+
const oauth2Service = new OAuth2Service(jwtUtil, config.oauth2Clients || [], {}, userProvider);
|
|
3089
|
+
const authenticationManager = new AuthenticationManager;
|
|
3090
|
+
authenticationManager.registerProvider(new JwtAuthenticationProvider(jwtUtil));
|
|
3091
|
+
authenticationManager.registerProvider(new OAuth2AuthenticationProvider(oauth2Service));
|
|
3092
|
+
const securityFilter = createSecurityFilter({
|
|
3093
|
+
authenticationManager,
|
|
3094
|
+
excludePaths: [
|
|
3095
|
+
...config.excludePaths || [],
|
|
3096
|
+
...config.enableOAuth2Endpoints !== false ? [config.oauth2Prefix || "/oauth2"] : []
|
|
3097
|
+
],
|
|
3098
|
+
defaultAuthRequired: config.defaultAuthRequired ?? false
|
|
3099
|
+
});
|
|
3100
|
+
const controllers = [];
|
|
3101
|
+
const providers = [];
|
|
3102
|
+
const middlewares = [];
|
|
3103
|
+
if (config.enableOAuth2Endpoints !== false) {
|
|
3104
|
+
controllers.push(OAuth2Controller);
|
|
3105
|
+
}
|
|
3106
|
+
providers.push({
|
|
3107
|
+
provide: JWT_UTIL_TOKEN,
|
|
3108
|
+
useValue: jwtUtil
|
|
3109
|
+
}, {
|
|
3110
|
+
provide: OAUTH2_SERVICE_TOKEN,
|
|
3111
|
+
useValue: oauth2Service
|
|
3112
|
+
}, {
|
|
3113
|
+
provide: AuthenticationManager,
|
|
3114
|
+
useValue: authenticationManager
|
|
3115
|
+
});
|
|
3116
|
+
middlewares.push(securityFilter);
|
|
3117
|
+
const existingMetadata = Reflect.getMetadata(MODULE_METADATA_KEY, SecurityModule) || {};
|
|
3118
|
+
const metadata = {
|
|
3119
|
+
...existingMetadata,
|
|
3120
|
+
controllers: [...existingMetadata.controllers || [], ...controllers],
|
|
3121
|
+
providers: [...existingMetadata.providers || [], ...providers],
|
|
3122
|
+
middlewares: [...existingMetadata.middlewares || [], ...middlewares],
|
|
3123
|
+
exports: [
|
|
3124
|
+
...existingMetadata.exports || [],
|
|
3125
|
+
JWT_UTIL_TOKEN,
|
|
3126
|
+
OAUTH2_SERVICE_TOKEN,
|
|
3127
|
+
AuthenticationManager
|
|
3128
|
+
]
|
|
3129
|
+
};
|
|
3130
|
+
Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, SecurityModule);
|
|
3131
|
+
return SecurityModule;
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
SecurityModule = __legacyDecorateClassTS([
|
|
3135
|
+
Module({
|
|
3136
|
+
controllers: [],
|
|
3137
|
+
providers: [],
|
|
3138
|
+
middlewares: []
|
|
3139
|
+
})
|
|
3140
|
+
], SecurityModule);
|
|
3141
|
+
// src/config/service.ts
|
|
3142
|
+
class ConfigService {
|
|
3143
|
+
config;
|
|
3144
|
+
namespace;
|
|
3145
|
+
constructor(config, namespace) {
|
|
3146
|
+
this.config = config;
|
|
3147
|
+
this.namespace = namespace;
|
|
3148
|
+
}
|
|
3149
|
+
getAll() {
|
|
3150
|
+
return this.config;
|
|
3151
|
+
}
|
|
3152
|
+
get(key, defaultValue) {
|
|
3153
|
+
const namespacedKey = this.applyNamespace(key);
|
|
3154
|
+
const value = this.getValueByPath(this.config, namespacedKey);
|
|
3155
|
+
if (value === undefined) {
|
|
3156
|
+
return defaultValue;
|
|
3157
|
+
}
|
|
3158
|
+
return value;
|
|
3159
|
+
}
|
|
3160
|
+
getRequired(key) {
|
|
3161
|
+
const value = this.get(key);
|
|
3162
|
+
if (value === undefined) {
|
|
3163
|
+
throw new Error(`Config value required for key: ${key}`);
|
|
3164
|
+
}
|
|
3165
|
+
return value;
|
|
3166
|
+
}
|
|
3167
|
+
withNamespace(namespace) {
|
|
3168
|
+
return new ConfigService(this.config, namespace);
|
|
3169
|
+
}
|
|
3170
|
+
applyNamespace(key) {
|
|
3171
|
+
if (!this.namespace) {
|
|
3172
|
+
return key;
|
|
3173
|
+
}
|
|
3174
|
+
if (!key) {
|
|
3175
|
+
return this.namespace;
|
|
3176
|
+
}
|
|
3177
|
+
if (key.startsWith(this.namespace + ".")) {
|
|
3178
|
+
return key;
|
|
3179
|
+
}
|
|
3180
|
+
return `${this.namespace}.${key}`;
|
|
3181
|
+
}
|
|
3182
|
+
getValueByPath(obj, path) {
|
|
3183
|
+
if (!path) {
|
|
3184
|
+
return obj;
|
|
3185
|
+
}
|
|
3186
|
+
const segments = path.split(".");
|
|
3187
|
+
let current = obj;
|
|
3188
|
+
for (const segment of segments) {
|
|
3189
|
+
if (current === undefined || current === null || typeof current !== "object") {
|
|
3190
|
+
return;
|
|
3191
|
+
}
|
|
3192
|
+
current = current[segment];
|
|
3193
|
+
}
|
|
3194
|
+
return current;
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
// src/config/types.ts
|
|
3199
|
+
var CONFIG_SERVICE_TOKEN = Symbol("@dangao/bun-server:config:service");
|
|
3200
|
+
|
|
3201
|
+
// src/config/config-module.ts
|
|
3202
|
+
class ConfigModule {
|
|
3203
|
+
static forRoot(options = {}) {
|
|
3204
|
+
const providers2 = [];
|
|
3205
|
+
const env = ConfigModule.snapshotEnv();
|
|
3206
|
+
const defaultConfig = options.defaultConfig ?? {};
|
|
3207
|
+
const loadedConfig = options.load ? options.load(env) : {};
|
|
3208
|
+
const mergedConfig = {
|
|
3209
|
+
...defaultConfig,
|
|
3210
|
+
...loadedConfig
|
|
3211
|
+
};
|
|
3212
|
+
const service = new ConfigService(mergedConfig, options.namespace);
|
|
3213
|
+
if (options.validate) {
|
|
3214
|
+
options.validate(mergedConfig);
|
|
3215
|
+
}
|
|
3216
|
+
providers2.push({
|
|
3217
|
+
provide: CONFIG_SERVICE_TOKEN,
|
|
3218
|
+
useValue: service
|
|
3219
|
+
}, ConfigService);
|
|
3220
|
+
const existingMetadata = Reflect.getMetadata(MODULE_METADATA_KEY, ConfigModule) || {};
|
|
3221
|
+
const metadata = {
|
|
3222
|
+
...existingMetadata,
|
|
3223
|
+
providers: [...existingMetadata.providers || [], ...providers2],
|
|
3224
|
+
exports: [
|
|
3225
|
+
...existingMetadata.exports || [],
|
|
3226
|
+
CONFIG_SERVICE_TOKEN,
|
|
3227
|
+
ConfigService
|
|
3228
|
+
]
|
|
3229
|
+
};
|
|
3230
|
+
Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, ConfigModule);
|
|
3231
|
+
return ConfigModule;
|
|
3232
|
+
}
|
|
3233
|
+
static snapshotEnv() {
|
|
3234
|
+
const env = {};
|
|
3235
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
3236
|
+
env[key] = value;
|
|
3237
|
+
}
|
|
3238
|
+
return env;
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
ConfigModule = __legacyDecorateClassTS([
|
|
3242
|
+
Module({
|
|
3243
|
+
providers: []
|
|
3244
|
+
})
|
|
3245
|
+
], ConfigModule);
|
|
3246
|
+
// src/health/types.ts
|
|
3247
|
+
var HEALTH_INDICATORS_TOKEN = Symbol("@dangao/bun-server:health:indicators");
|
|
3248
|
+
var HEALTH_OPTIONS_TOKEN = Symbol("@dangao/bun-server:health:options");
|
|
3249
|
+
|
|
3250
|
+
// src/health/controller.ts
|
|
3251
|
+
class HealthController {
|
|
3252
|
+
indicators;
|
|
3253
|
+
options;
|
|
3254
|
+
constructor(indicators = [], options) {
|
|
3255
|
+
this.indicators = indicators;
|
|
3256
|
+
this.options = options;
|
|
3257
|
+
}
|
|
3258
|
+
async health() {
|
|
3259
|
+
return await this.checkIndicators();
|
|
3260
|
+
}
|
|
3261
|
+
async ready() {
|
|
3262
|
+
return await this.checkIndicators();
|
|
3263
|
+
}
|
|
3264
|
+
async checkIndicators() {
|
|
3265
|
+
const details = {};
|
|
3266
|
+
for (const indicator of this.indicators || []) {
|
|
3267
|
+
try {
|
|
3268
|
+
const result = await indicator.check();
|
|
3269
|
+
details[indicator.name] = result;
|
|
3270
|
+
} catch (error) {
|
|
3271
|
+
details[indicator.name] = {
|
|
3272
|
+
status: "down",
|
|
3273
|
+
details: {
|
|
3274
|
+
error: error.message
|
|
3275
|
+
}
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
const allUp = Object.keys(details).length === 0 || Object.values(details).every((result) => result.status === "up");
|
|
3280
|
+
return {
|
|
3281
|
+
status: allUp ? "up" : "down",
|
|
3282
|
+
details
|
|
3283
|
+
};
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
__legacyDecorateClassTS([
|
|
3287
|
+
GET("/health"),
|
|
3288
|
+
__legacyMetadataTS("design:type", Function),
|
|
3289
|
+
__legacyMetadataTS("design:paramtypes", []),
|
|
3290
|
+
__legacyMetadataTS("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
3291
|
+
], HealthController.prototype, "health", null);
|
|
3292
|
+
__legacyDecorateClassTS([
|
|
3293
|
+
GET("/ready"),
|
|
3294
|
+
__legacyMetadataTS("design:type", Function),
|
|
3295
|
+
__legacyMetadataTS("design:paramtypes", []),
|
|
3296
|
+
__legacyMetadataTS("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
3297
|
+
], HealthController.prototype, "ready", null);
|
|
3298
|
+
HealthController = __legacyDecorateClassTS([
|
|
3299
|
+
Controller("/"),
|
|
3300
|
+
__legacyDecorateParamTS(0, Inject(HEALTH_INDICATORS_TOKEN)),
|
|
3301
|
+
__legacyDecorateParamTS(1, Inject(HEALTH_OPTIONS_TOKEN)),
|
|
3302
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
3303
|
+
Array,
|
|
3304
|
+
typeof HealthModuleOptions === "undefined" ? Object : HealthModuleOptions
|
|
3305
|
+
])
|
|
3306
|
+
], HealthController);
|
|
3307
|
+
|
|
3308
|
+
// src/health/health-module.ts
|
|
3309
|
+
class HealthModule {
|
|
3310
|
+
static forRoot(options = {}) {
|
|
3311
|
+
const providers2 = [];
|
|
3312
|
+
const indicators = options.indicators ?? [];
|
|
3313
|
+
providers2.push({
|
|
3314
|
+
provide: HEALTH_INDICATORS_TOKEN,
|
|
3315
|
+
useValue: indicators
|
|
3316
|
+
}, {
|
|
3317
|
+
provide: HEALTH_OPTIONS_TOKEN,
|
|
3318
|
+
useValue: options
|
|
3319
|
+
});
|
|
3320
|
+
const existingMetadata = Reflect.getMetadata(MODULE_METADATA_KEY, HealthModule) || {};
|
|
3321
|
+
const metadata = {
|
|
3322
|
+
...existingMetadata,
|
|
3323
|
+
controllers: [...existingMetadata.controllers || [], HealthController],
|
|
3324
|
+
providers: [...existingMetadata.providers || [], ...providers2],
|
|
3325
|
+
exports: [
|
|
3326
|
+
...existingMetadata.exports || [],
|
|
3327
|
+
HEALTH_INDICATORS_TOKEN,
|
|
3328
|
+
HEALTH_OPTIONS_TOKEN
|
|
3329
|
+
]
|
|
3330
|
+
};
|
|
3331
|
+
Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, HealthModule);
|
|
3332
|
+
return HealthModule;
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
HealthModule = __legacyDecorateClassTS([
|
|
3336
|
+
Module({
|
|
3337
|
+
controllers: [HealthController],
|
|
3338
|
+
providers: []
|
|
3339
|
+
})
|
|
3340
|
+
], HealthModule);
|
|
1998
3341
|
// src/testing/harness.ts
|
|
1999
3342
|
import { performance as performance2 } from "perf_hooks";
|
|
2000
3343
|
|
|
@@ -2056,22 +3399,33 @@ class StressTester {
|
|
|
2056
3399
|
}
|
|
2057
3400
|
}
|
|
2058
3401
|
export {
|
|
3402
|
+
requiresAuth,
|
|
3403
|
+
getAuthMetadata,
|
|
3404
|
+
createSwaggerUIMiddleware,
|
|
2059
3405
|
createStaticFileMiddleware,
|
|
3406
|
+
createSecurityFilter,
|
|
2060
3407
|
createRequestLoggingMiddleware,
|
|
2061
3408
|
createLoggerMiddleware,
|
|
2062
3409
|
createFileUploadMiddleware,
|
|
2063
3410
|
createErrorHandlingMiddleware,
|
|
2064
3411
|
createCorsMiddleware,
|
|
3412
|
+
checkRoles,
|
|
2065
3413
|
WebSocketGatewayRegistry,
|
|
2066
3414
|
WebSocketGateway,
|
|
2067
3415
|
ValidationError,
|
|
2068
3416
|
Validate,
|
|
2069
3417
|
UseMiddleware,
|
|
2070
3418
|
UnauthorizedException,
|
|
3419
|
+
SwaggerModule,
|
|
3420
|
+
SwaggerGenerator,
|
|
3421
|
+
SwaggerExtension,
|
|
2071
3422
|
StressTester,
|
|
3423
|
+
SecurityModule,
|
|
3424
|
+
SecurityContextHolder,
|
|
2072
3425
|
Router,
|
|
2073
3426
|
RouteRegistry,
|
|
2074
3427
|
Route,
|
|
3428
|
+
RoleBasedAccessDecisionManager,
|
|
2075
3429
|
ResponseBuilder,
|
|
2076
3430
|
RequestWrapper,
|
|
2077
3431
|
Query,
|
|
@@ -2084,15 +3438,23 @@ export {
|
|
|
2084
3438
|
OnOpen,
|
|
2085
3439
|
OnMessage,
|
|
2086
3440
|
OnClose,
|
|
3441
|
+
OAuth2Service,
|
|
3442
|
+
OAuth2Controller,
|
|
3443
|
+
OAuth2AuthenticationProvider,
|
|
3444
|
+
OAUTH2_SERVICE_TOKEN,
|
|
2087
3445
|
NotFoundException,
|
|
2088
3446
|
ModuleRegistry,
|
|
2089
3447
|
Module,
|
|
2090
3448
|
MinLength,
|
|
2091
3449
|
MiddlewarePipeline,
|
|
3450
|
+
LoggerModule,
|
|
2092
3451
|
LoggerExtension,
|
|
2093
3452
|
LogLevel2 as LogLevel,
|
|
2094
3453
|
Lifecycle,
|
|
2095
3454
|
LOGGER_TOKEN,
|
|
3455
|
+
JwtAuthenticationProvider,
|
|
3456
|
+
JWT_UTIL_TOKEN,
|
|
3457
|
+
JWTUtil,
|
|
2096
3458
|
IsString,
|
|
2097
3459
|
IsOptional,
|
|
2098
3460
|
IsNumber,
|
|
@@ -2101,7 +3463,10 @@ export {
|
|
|
2101
3463
|
Injectable,
|
|
2102
3464
|
Inject,
|
|
2103
3465
|
HttpException,
|
|
3466
|
+
HealthModule,
|
|
2104
3467
|
Header,
|
|
3468
|
+
HEALTH_OPTIONS_TOKEN,
|
|
3469
|
+
HEALTH_INDICATORS_TOKEN,
|
|
2105
3470
|
GET,
|
|
2106
3471
|
ForbiddenException,
|
|
2107
3472
|
ExceptionFilterRegistry,
|
|
@@ -2110,9 +3475,19 @@ export {
|
|
|
2110
3475
|
Controller,
|
|
2111
3476
|
Context,
|
|
2112
3477
|
Container,
|
|
3478
|
+
ConfigService,
|
|
3479
|
+
ConfigModule,
|
|
3480
|
+
CONFIG_SERVICE_TOKEN,
|
|
2113
3481
|
BunServer,
|
|
2114
3482
|
BodyParser,
|
|
2115
3483
|
Body,
|
|
2116
3484
|
BadRequestException,
|
|
2117
|
-
|
|
3485
|
+
AuthenticationManager,
|
|
3486
|
+
Auth,
|
|
3487
|
+
Application,
|
|
3488
|
+
ApiTags,
|
|
3489
|
+
ApiResponse,
|
|
3490
|
+
ApiParam,
|
|
3491
|
+
ApiOperation,
|
|
3492
|
+
ApiBody
|
|
2118
3493
|
};
|