adorn-api 1.1.11 → 1.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/adapter/express/types.d.ts +3 -46
- package/dist/adapter/fastify/coercion.d.ts +12 -0
- package/dist/adapter/fastify/coercion.js +289 -0
- package/dist/adapter/fastify/controllers.d.ts +7 -0
- package/dist/adapter/fastify/controllers.js +201 -0
- package/dist/adapter/fastify/index.d.ts +14 -0
- package/dist/adapter/fastify/index.js +67 -0
- package/dist/adapter/fastify/multipart.d.ts +26 -0
- package/dist/adapter/fastify/multipart.js +75 -0
- package/dist/adapter/fastify/openapi.d.ts +10 -0
- package/dist/adapter/fastify/openapi.js +76 -0
- package/dist/adapter/fastify/response-serializer.d.ts +2 -0
- package/dist/adapter/fastify/response-serializer.js +162 -0
- package/dist/adapter/fastify/types.d.ts +100 -0
- package/dist/adapter/fastify/types.js +2 -0
- package/dist/adapter/metal-orm/index.d.ts +1 -1
- package/dist/adapter/metal-orm/types.d.ts +23 -0
- package/dist/adapter/native/coercion.d.ts +12 -0
- package/dist/adapter/native/coercion.js +289 -0
- package/dist/adapter/native/controllers.d.ts +17 -0
- package/dist/adapter/native/controllers.js +215 -0
- package/dist/adapter/native/index.d.ts +14 -0
- package/dist/adapter/native/index.js +127 -0
- package/dist/adapter/native/openapi.d.ts +7 -0
- package/dist/adapter/native/openapi.js +82 -0
- package/dist/adapter/native/response-serializer.d.ts +5 -0
- package/dist/adapter/native/response-serializer.js +160 -0
- package/dist/adapter/native/router.d.ts +25 -0
- package/dist/adapter/native/router.js +68 -0
- package/dist/adapter/native/types.d.ts +77 -0
- package/dist/adapter/native/types.js +2 -0
- package/dist/core/auth.d.ts +11 -12
- package/dist/core/auth.js +2 -2
- package/dist/core/logger.d.ts +3 -4
- package/dist/core/logger.js +2 -2
- package/dist/core/streaming.d.ts +10 -10
- package/dist/core/streaming.js +31 -19
- package/dist/core/types.d.ts +102 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +16 -1
- package/examples/fastify/app.ts +16 -0
- package/examples/fastify/index.ts +21 -0
- package/package.json +24 -18
- package/src/adapter/express/controllers.ts +249 -249
- package/src/adapter/express/types.ts +121 -160
- package/src/adapter/fastify/coercion.ts +369 -0
- package/src/adapter/fastify/controllers.ts +255 -0
- package/src/adapter/fastify/index.ts +53 -0
- package/src/adapter/fastify/multipart.ts +94 -0
- package/src/adapter/fastify/openapi.ts +93 -0
- package/src/adapter/fastify/response-serializer.ts +179 -0
- package/src/adapter/fastify/types.ts +119 -0
- package/src/adapter/metal-orm/index.ts +3 -0
- package/src/adapter/metal-orm/types.ts +25 -0
- package/src/adapter/native/coercion.ts +369 -0
- package/src/adapter/native/controllers.ts +271 -0
- package/src/adapter/native/index.ts +116 -0
- package/src/adapter/native/openapi.ts +109 -0
- package/src/adapter/native/response-serializer.ts +177 -0
- package/src/adapter/native/router.ts +90 -0
- package/src/adapter/native/types.ts +96 -0
- package/src/core/auth.ts +314 -315
- package/src/core/health.ts +234 -235
- package/src/core/logger.ts +245 -247
- package/src/core/streaming.ts +342 -330
- package/src/core/types.ts +115 -0
- package/src/index.ts +46 -16
- package/tests/e2e/fastify.e2e.test.ts +174 -0
- package/tests/native.test.ts +191 -0
- package/tests/typecheck/query-params.typecheck.ts +42 -0
- package/tests/unit/openapi-parameters.test.ts +97 -97
- package/tsconfig.json +14 -13
- package/tsconfig.typecheck.json +8 -0
- package/vitest.config.ts +47 -7
package/src/core/auth.ts
CHANGED
|
@@ -1,315 +1,314 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
roles
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
roles
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
*
|
|
70
|
-
* @
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
*
|
|
102
|
-
* @
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
existing.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
*
|
|
124
|
-
* @
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
existing.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
*
|
|
165
|
-
* @param
|
|
166
|
-
* @
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (!roles
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (!roles
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* @
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
*
|
|
242
|
-
* @param
|
|
243
|
-
* @param
|
|
244
|
-
* @
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
*
|
|
288
|
-
* @param
|
|
289
|
-
* @
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
*
|
|
301
|
-
* @param
|
|
302
|
-
* @
|
|
303
|
-
* @
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
1
|
+
import type { Constructor } from "./types";
|
|
2
|
+
import { HttpError } from "./errors";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* User context interface - extend this in your application.
|
|
6
|
+
*/
|
|
7
|
+
export interface AuthUser {
|
|
8
|
+
id: string;
|
|
9
|
+
roles?: string[];
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Authentication options for a route or controller.
|
|
15
|
+
*/
|
|
16
|
+
export interface AuthOptions {
|
|
17
|
+
/** Required roles (any match grants access) */
|
|
18
|
+
roles?: string[];
|
|
19
|
+
/** All roles required (all must match) */
|
|
20
|
+
allRoles?: string[];
|
|
21
|
+
/** Custom guard function */
|
|
22
|
+
guard?: (user: AuthUser, req: any) => boolean | Promise<boolean>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Function to extract user from request.
|
|
27
|
+
*/
|
|
28
|
+
export type AuthExtractor = (req: any) => AuthUser | null | Promise<AuthUser | null>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Options for creating auth middleware.
|
|
32
|
+
*/
|
|
33
|
+
export interface AuthMiddlewareOptions {
|
|
34
|
+
/** Function to extract user from request */
|
|
35
|
+
extractor: AuthExtractor;
|
|
36
|
+
/** Property name to attach user to request (default: "user") */
|
|
37
|
+
userProperty?: string;
|
|
38
|
+
/** Custom unauthorized response */
|
|
39
|
+
onUnauthorized?: (req: any, res: any) => void;
|
|
40
|
+
/** Custom forbidden response */
|
|
41
|
+
onForbidden?: (req: any, res: any, reason?: string) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Metadata for authentication on routes/controllers.
|
|
46
|
+
*/
|
|
47
|
+
interface AuthMeta {
|
|
48
|
+
/** Whether authentication is required */
|
|
49
|
+
requiresAuth: boolean;
|
|
50
|
+
/** Whether route is public (overrides controller-level auth) */
|
|
51
|
+
isPublic: boolean;
|
|
52
|
+
/** Required roles (any match) */
|
|
53
|
+
roles?: string[];
|
|
54
|
+
/** Required roles (all must match) */
|
|
55
|
+
allRoles?: string[];
|
|
56
|
+
/** Custom guard function */
|
|
57
|
+
guard?: (user: AuthUser, req: any) => boolean | Promise<boolean>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const routeAuthStore = new Map<string, AuthMeta>();
|
|
61
|
+
const controllerAuthStore = new Map<Constructor, AuthMeta>();
|
|
62
|
+
|
|
63
|
+
function getRouteKey(controller: Constructor, handlerName: string | symbol): string {
|
|
64
|
+
return `${controller.name}:${String(handlerName)}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Decorator to require authentication on a controller or route.
|
|
69
|
+
* @param options - Authentication options
|
|
70
|
+
* @returns Decorator function
|
|
71
|
+
*/
|
|
72
|
+
export function Auth(options: AuthOptions = {}) {
|
|
73
|
+
return function <T extends Constructor | ((...args: unknown[]) => unknown)>(
|
|
74
|
+
target: T,
|
|
75
|
+
context: ClassDecoratorContext | ClassMethodDecoratorContext
|
|
76
|
+
): void {
|
|
77
|
+
const meta: AuthMeta = {
|
|
78
|
+
requiresAuth: true,
|
|
79
|
+
isPublic: false,
|
|
80
|
+
roles: options.roles,
|
|
81
|
+
allRoles: options.allRoles,
|
|
82
|
+
guard: options.guard
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (context.kind === "class") {
|
|
86
|
+
context.addInitializer(function () {
|
|
87
|
+
controllerAuthStore.set(target as Constructor, meta);
|
|
88
|
+
});
|
|
89
|
+
} else if (context.kind === "method") {
|
|
90
|
+
const handlerName = context.name;
|
|
91
|
+
context.addInitializer(function () {
|
|
92
|
+
const controller = (this as object).constructor as Constructor;
|
|
93
|
+
routeAuthStore.set(getRouteKey(controller, handlerName), meta);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Decorator to require specific roles.
|
|
101
|
+
* @param roles - Required roles (any match grants access)
|
|
102
|
+
* @returns Method decorator function
|
|
103
|
+
*/
|
|
104
|
+
export function Roles(...roles: string[]) {
|
|
105
|
+
return function (_target: unknown, context: ClassMethodDecoratorContext): void {
|
|
106
|
+
const handlerName = context.name;
|
|
107
|
+
context.addInitializer(function () {
|
|
108
|
+
const controller = (this as object).constructor as Constructor;
|
|
109
|
+
const key = getRouteKey(controller, handlerName);
|
|
110
|
+
const existing = routeAuthStore.get(key) ?? {
|
|
111
|
+
requiresAuth: true,
|
|
112
|
+
isPublic: false
|
|
113
|
+
};
|
|
114
|
+
existing.roles = roles;
|
|
115
|
+
existing.requiresAuth = true;
|
|
116
|
+
routeAuthStore.set(key, existing);
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Decorator to require all specified roles.
|
|
123
|
+
* @param roles - All roles required
|
|
124
|
+
* @returns Method decorator function
|
|
125
|
+
*/
|
|
126
|
+
export function AllRoles(...roles: string[]) {
|
|
127
|
+
return function (_target: unknown, context: ClassMethodDecoratorContext): void {
|
|
128
|
+
const handlerName = context.name;
|
|
129
|
+
context.addInitializer(function () {
|
|
130
|
+
const controller = (this as object).constructor as Constructor;
|
|
131
|
+
const key = getRouteKey(controller, handlerName);
|
|
132
|
+
const existing = routeAuthStore.get(key) ?? {
|
|
133
|
+
requiresAuth: true,
|
|
134
|
+
isPublic: false
|
|
135
|
+
};
|
|
136
|
+
existing.allRoles = roles;
|
|
137
|
+
existing.requiresAuth = true;
|
|
138
|
+
routeAuthStore.set(key, existing);
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Decorator to mark a route as public (no authentication required).
|
|
145
|
+
* Overrides controller-level @Auth decorator.
|
|
146
|
+
* @returns Method decorator function
|
|
147
|
+
*/
|
|
148
|
+
export function Public() {
|
|
149
|
+
return function (_target: unknown, context: ClassMethodDecoratorContext): void {
|
|
150
|
+
const handlerName = context.name;
|
|
151
|
+
context.addInitializer(function () {
|
|
152
|
+
const controller = (this as object).constructor as Constructor;
|
|
153
|
+
const key = getRouteKey(controller, handlerName);
|
|
154
|
+
routeAuthStore.set(key, {
|
|
155
|
+
requiresAuth: false,
|
|
156
|
+
isPublic: true
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Gets auth metadata for a specific route.
|
|
164
|
+
* @param controller - Controller class
|
|
165
|
+
* @param handlerName - Handler method name
|
|
166
|
+
* @returns Combined auth metadata
|
|
167
|
+
*/
|
|
168
|
+
export function getRouteAuthMeta(
|
|
169
|
+
controller: Constructor,
|
|
170
|
+
handlerName: string | symbol
|
|
171
|
+
): AuthMeta | undefined {
|
|
172
|
+
const routeMeta = routeAuthStore.get(getRouteKey(controller, handlerName));
|
|
173
|
+
const controllerMeta = controllerAuthStore.get(controller);
|
|
174
|
+
|
|
175
|
+
if (routeMeta?.isPublic) {
|
|
176
|
+
return routeMeta;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (routeMeta && controllerMeta) {
|
|
180
|
+
return {
|
|
181
|
+
requiresAuth: routeMeta.requiresAuth || controllerMeta.requiresAuth,
|
|
182
|
+
isPublic: false,
|
|
183
|
+
roles: routeMeta.roles ?? controllerMeta.roles,
|
|
184
|
+
allRoles: routeMeta.allRoles ?? controllerMeta.allRoles,
|
|
185
|
+
guard: routeMeta.guard ?? controllerMeta.guard
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return routeMeta ?? controllerMeta;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Gets auth metadata for a controller.
|
|
194
|
+
*/
|
|
195
|
+
export function getControllerAuthMeta(controller: Constructor): AuthMeta | undefined {
|
|
196
|
+
return controllerAuthStore.get(controller);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Checks if user has any of the required roles.
|
|
201
|
+
*/
|
|
202
|
+
function hasAnyRole(user: AuthUser, roles: string[]): boolean {
|
|
203
|
+
if (!roles.length) return true;
|
|
204
|
+
if (!user.roles?.length) return false;
|
|
205
|
+
return roles.some((role) => user.roles!.includes(role));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Checks if user has all required roles.
|
|
210
|
+
*/
|
|
211
|
+
function hasAllRoles(user: AuthUser, roles: string[]): boolean {
|
|
212
|
+
if (!roles.length) return true;
|
|
213
|
+
if (!user.roles?.length) return false;
|
|
214
|
+
return roles.every((role) => user.roles!.includes(role));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Creates Express middleware for authentication.
|
|
219
|
+
* Use this as a global middleware, then use route-level checks.
|
|
220
|
+
* @param options - Auth middleware options
|
|
221
|
+
* @returns Express middleware function
|
|
222
|
+
*/
|
|
223
|
+
export function createAuthMiddleware(options: AuthMiddlewareOptions) {
|
|
224
|
+
const userProperty = options.userProperty ?? "user";
|
|
225
|
+
|
|
226
|
+
return async (req: any, res: any, next: (err?: any) => void): Promise<void> => {
|
|
227
|
+
try {
|
|
228
|
+
const user = await options.extractor(req);
|
|
229
|
+
if (user) {
|
|
230
|
+
(req as unknown as Record<string, unknown>)[userProperty] = user;
|
|
231
|
+
}
|
|
232
|
+
next();
|
|
233
|
+
} catch (error) {
|
|
234
|
+
next(error);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Creates a route guard middleware that checks auth metadata.
|
|
241
|
+
* @param controller - Controller class
|
|
242
|
+
* @param handlerName - Handler method name
|
|
243
|
+
* @param options - Auth middleware options
|
|
244
|
+
* @returns Express middleware function
|
|
245
|
+
*/
|
|
246
|
+
export function createRouteGuard(
|
|
247
|
+
controller: Constructor,
|
|
248
|
+
handlerName: string | symbol,
|
|
249
|
+
options: { userProperty?: string } = {}
|
|
250
|
+
) {
|
|
251
|
+
const userProperty = options.userProperty ?? "user";
|
|
252
|
+
const authMeta = getRouteAuthMeta(controller, handlerName);
|
|
253
|
+
|
|
254
|
+
return async (req: any, res: any, next: (err?: any) => void): Promise<void> => {
|
|
255
|
+
if (!authMeta || authMeta.isPublic || !authMeta.requiresAuth) {
|
|
256
|
+
next();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const user = (req as unknown as Record<string, unknown>)[userProperty] as AuthUser | undefined;
|
|
261
|
+
|
|
262
|
+
if (!user) {
|
|
263
|
+
throw new HttpError(401, "Unauthorized");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (authMeta.roles?.length && !hasAnyRole(user, authMeta.roles)) {
|
|
267
|
+
throw new HttpError(403, "Insufficient permissions");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (authMeta.allRoles?.length && !hasAllRoles(user, authMeta.allRoles)) {
|
|
271
|
+
throw new HttpError(403, "Insufficient permissions");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (authMeta.guard) {
|
|
275
|
+
const allowed = await authMeta.guard(user, req);
|
|
276
|
+
if (!allowed) {
|
|
277
|
+
throw new HttpError(403, "Access denied by guard");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
next();
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Helper to get user from request in controllers.
|
|
287
|
+
* @param req - Raw request
|
|
288
|
+
* @param userProperty - Property name (default: "user")
|
|
289
|
+
* @returns User or undefined
|
|
290
|
+
*/
|
|
291
|
+
export function getUser<T extends AuthUser = AuthUser>(
|
|
292
|
+
req: any,
|
|
293
|
+
userProperty: string = "user"
|
|
294
|
+
): T | undefined {
|
|
295
|
+
return (req as unknown as Record<string, unknown>)[userProperty] as T | undefined;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Helper to require user from request (throws if not present).
|
|
300
|
+
* @param req - Raw request
|
|
301
|
+
* @param userProperty - Property name (default: "user")
|
|
302
|
+
* @returns User
|
|
303
|
+
* @throws HttpError if user not found
|
|
304
|
+
*/
|
|
305
|
+
export function requireUser<T extends AuthUser = AuthUser>(
|
|
306
|
+
req: any,
|
|
307
|
+
userProperty: string = "user"
|
|
308
|
+
): T {
|
|
309
|
+
const user = getUser<T>(req, userProperty);
|
|
310
|
+
if (!user) {
|
|
311
|
+
throw new HttpError(401, "Unauthorized");
|
|
312
|
+
}
|
|
313
|
+
return user;
|
|
314
|
+
}
|