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