@noony-serverless/core 0.1.5 → 0.2.1
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 +7 -7
- package/build/core/core.d.ts +18 -50
- package/build/core/core.js +5 -67
- package/build/core/handler.d.ts +37 -16
- package/build/core/handler.js +131 -42
- package/build/core/index.d.ts +0 -1
- package/build/core/index.js +0 -1
- package/build/middlewares/ConsolidatedValidationMiddleware.d.ts +126 -0
- package/build/middlewares/ConsolidatedValidationMiddleware.js +330 -0
- package/build/middlewares/ProcessingMiddleware.d.ts +138 -0
- package/build/middlewares/ProcessingMiddleware.js +425 -0
- package/build/middlewares/SecurityMiddleware.d.ts +157 -0
- package/build/middlewares/SecurityMiddleware.js +307 -0
- package/build/middlewares/bodyValidationMiddleware.d.ts +12 -10
- package/build/middlewares/bodyValidationMiddleware.js +10 -8
- package/build/middlewares/dependencyInjectionMiddleware.js +1 -1
- package/build/middlewares/guards/RouteGuards.d.ts +239 -4
- package/build/middlewares/guards/RouteGuards.js +301 -8
- package/build/middlewares/guards/adapters/CustomTokenVerificationPortAdapter.d.ts +271 -0
- package/build/middlewares/guards/adapters/CustomTokenVerificationPortAdapter.js +301 -0
- package/build/middlewares/guards/config/GuardConfiguration.d.ts +50 -0
- package/build/middlewares/guards/config/GuardConfiguration.js +59 -0
- package/build/middlewares/guards/guards/FastAuthGuard.d.ts +5 -5
- package/build/middlewares/guards/guards/PermissionGuardFactory.d.ts +5 -13
- package/build/middlewares/guards/guards/PermissionGuardFactory.js +4 -4
- package/build/middlewares/guards/index.d.ts +43 -1
- package/build/middlewares/guards/index.js +46 -1
- package/build/middlewares/guards/resolvers/ExpressionPermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/resolvers/ExpressionPermissionResolver.js +1 -1
- package/build/middlewares/guards/resolvers/PermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/resolvers/PlainPermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/resolvers/WildcardPermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/services/FastUserContextService.d.ts +20 -33
- package/build/middlewares/guards/services/FastUserContextService.js +17 -4
- package/build/middlewares/httpAttributesMiddleware.js +1 -1
- package/build/middlewares/index.d.ts +3 -1
- package/build/middlewares/index.js +6 -1
- package/build/middlewares/rateLimitingMiddleware.d.ts +492 -4
- package/build/middlewares/rateLimitingMiddleware.js +514 -6
- package/package.json +11 -9
- package/build/core/containerPool.d.ts +0 -44
- package/build/core/containerPool.js +0 -103
- package/build/middlewares/validationMiddleware.d.ts +0 -154
- package/build/middlewares/validationMiddleware.js +0 -185
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSecurityMiddleware = exports.SecurityMiddleware = void 0;
|
|
4
|
+
const core_1 = require("../core");
|
|
5
|
+
/**
|
|
6
|
+
* Consolidated SecurityMiddleware that combines authentication, security headers, and audit logging.
|
|
7
|
+
*
|
|
8
|
+
* This middleware replaces the need for separate:
|
|
9
|
+
* - AuthenticationMiddleware
|
|
10
|
+
* - SecurityHeadersMiddleware
|
|
11
|
+
* - SecurityAuditMiddleware
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* Basic security with authentication and headers:
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const handler = new Handler()
|
|
17
|
+
* .use(new SecurityMiddleware({
|
|
18
|
+
* authentication: {
|
|
19
|
+
* tokenVerifier: {
|
|
20
|
+
* verifyToken: async (token) => jwt.verify(token, secret)
|
|
21
|
+
* },
|
|
22
|
+
* extractToken: (req) => req.headers.authorization?.replace('Bearer ', '')
|
|
23
|
+
* },
|
|
24
|
+
* headers: {
|
|
25
|
+
* contentSecurityPolicy: "default-src 'self'",
|
|
26
|
+
* xFrameOptions: 'DENY',
|
|
27
|
+
* strictTransportSecurity: 'max-age=31536000; includeSubDomains'
|
|
28
|
+
* },
|
|
29
|
+
* audit: {
|
|
30
|
+
* logFailedAuth: true,
|
|
31
|
+
* trackSuspiciousIPs: true
|
|
32
|
+
* }
|
|
33
|
+
* }))
|
|
34
|
+
* .handle(async (context) => {
|
|
35
|
+
* // context.user is populated if authentication succeeds
|
|
36
|
+
* const user = context.user;
|
|
37
|
+
* return { message: `Hello ${user?.name}` };
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* Advanced security with custom auditing:
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const handler = new Handler()
|
|
45
|
+
* .use(new SecurityMiddleware({
|
|
46
|
+
* authentication: {
|
|
47
|
+
* tokenVerifier: customTokenVerifier,
|
|
48
|
+
* skipPaths: ['/health', '/metrics'],
|
|
49
|
+
* onAuthFailure: async (error, context) => {
|
|
50
|
+
* await logSecurityEvent('auth_failure', {
|
|
51
|
+
* ip: context.req.ip,
|
|
52
|
+
* error: error.message
|
|
53
|
+
* });
|
|
54
|
+
* }
|
|
55
|
+
* },
|
|
56
|
+
* audit: {
|
|
57
|
+
* customAuditor: async (event, context) => {
|
|
58
|
+
* await sendToSecuritySystem(event);
|
|
59
|
+
* },
|
|
60
|
+
* alertThresholds: {
|
|
61
|
+
* failedAttempts: 5,
|
|
62
|
+
* timeWindowMs: 300000 // 5 minutes
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
* }));
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
class SecurityMiddleware {
|
|
69
|
+
config;
|
|
70
|
+
failedAttempts = new Map();
|
|
71
|
+
constructor(config = {}) {
|
|
72
|
+
this.config = {
|
|
73
|
+
authentication: {},
|
|
74
|
+
headers: {
|
|
75
|
+
xFrameOptions: 'DENY',
|
|
76
|
+
xContentTypeOptions: 'nosniff',
|
|
77
|
+
xXssProtection: '1; mode=block',
|
|
78
|
+
referrerPolicy: 'strict-origin-when-cross-origin',
|
|
79
|
+
...config.headers,
|
|
80
|
+
},
|
|
81
|
+
audit: {
|
|
82
|
+
logFailedAuth: true,
|
|
83
|
+
trackSuspiciousIPs: false,
|
|
84
|
+
enableMetrics: true,
|
|
85
|
+
...config.audit,
|
|
86
|
+
},
|
|
87
|
+
...config,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async before(context) {
|
|
91
|
+
// 1. Set security headers first (always apply)
|
|
92
|
+
await this.setSecurityHeaders(context);
|
|
93
|
+
// 2. Perform authentication if configured
|
|
94
|
+
if (this.config.authentication?.tokenVerifier) {
|
|
95
|
+
await this.authenticateRequest(context);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async after(context) {
|
|
99
|
+
// Audit successful operations if enabled
|
|
100
|
+
if (this.config.audit?.logSuccessfulAuth && context.user) {
|
|
101
|
+
await this.auditSecurityEvent({
|
|
102
|
+
type: 'AUTH_SUCCESS',
|
|
103
|
+
ip: context.req.ip,
|
|
104
|
+
userAgent: context.req.userAgent,
|
|
105
|
+
path: context.req.path,
|
|
106
|
+
timestamp: new Date(),
|
|
107
|
+
details: { userId: context.user?.id },
|
|
108
|
+
}, context);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async onError(error, context) {
|
|
112
|
+
// Audit authentication failures
|
|
113
|
+
if (error instanceof core_1.AuthenticationError &&
|
|
114
|
+
this.config.audit?.logFailedAuth) {
|
|
115
|
+
const ip = context.req.ip || 'unknown';
|
|
116
|
+
// Track failed attempts for suspicious IP detection
|
|
117
|
+
if (this.config.audit?.trackSuspiciousIPs) {
|
|
118
|
+
await this.trackFailedAttempt(ip, context);
|
|
119
|
+
}
|
|
120
|
+
await this.auditSecurityEvent({
|
|
121
|
+
type: 'AUTH_FAILURE',
|
|
122
|
+
ip,
|
|
123
|
+
userAgent: context.req.userAgent,
|
|
124
|
+
path: context.req.path,
|
|
125
|
+
timestamp: new Date(),
|
|
126
|
+
details: { error: error.message },
|
|
127
|
+
}, context);
|
|
128
|
+
// Custom auth failure handler
|
|
129
|
+
if (this.config.authentication?.onAuthFailure) {
|
|
130
|
+
await this.config.authentication.onAuthFailure(error, context);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async setSecurityHeaders(context) {
|
|
135
|
+
const headers = this.config.headers;
|
|
136
|
+
if (headers.contentSecurityPolicy) {
|
|
137
|
+
context.res.header('Content-Security-Policy', headers.contentSecurityPolicy);
|
|
138
|
+
}
|
|
139
|
+
if (headers.xFrameOptions) {
|
|
140
|
+
context.res.header('X-Frame-Options', headers.xFrameOptions);
|
|
141
|
+
}
|
|
142
|
+
if (headers.strictTransportSecurity) {
|
|
143
|
+
context.res.header('Strict-Transport-Security', headers.strictTransportSecurity);
|
|
144
|
+
}
|
|
145
|
+
if (headers.xContentTypeOptions) {
|
|
146
|
+
context.res.header('X-Content-Type-Options', headers.xContentTypeOptions);
|
|
147
|
+
}
|
|
148
|
+
if (headers.xXssProtection) {
|
|
149
|
+
context.res.header('X-XSS-Protection', headers.xXssProtection);
|
|
150
|
+
}
|
|
151
|
+
if (headers.referrerPolicy) {
|
|
152
|
+
context.res.header('Referrer-Policy', headers.referrerPolicy);
|
|
153
|
+
}
|
|
154
|
+
if (headers.permissionsPolicy) {
|
|
155
|
+
context.res.header('Permissions-Policy', headers.permissionsPolicy);
|
|
156
|
+
}
|
|
157
|
+
// Apply custom headers
|
|
158
|
+
if (headers.customHeaders) {
|
|
159
|
+
Object.entries(headers.customHeaders).forEach(([name, value]) => {
|
|
160
|
+
context.res.header(name, value);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async authenticateRequest(context) {
|
|
165
|
+
const authConfig = this.config.authentication;
|
|
166
|
+
// Skip authentication for specified paths
|
|
167
|
+
if (authConfig.skipPaths?.some((path) => context.req.path?.startsWith(path))) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
// Extract token
|
|
171
|
+
const token = authConfig.extractToken
|
|
172
|
+
? authConfig.extractToken(context.req)
|
|
173
|
+
: this.extractTokenFromHeader(context.req);
|
|
174
|
+
if (!token) {
|
|
175
|
+
if (!authConfig.optional) {
|
|
176
|
+
throw new core_1.AuthenticationError('No authentication token provided');
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
// Verify token using the provided verifier
|
|
182
|
+
const user = await authConfig.tokenVerifier.verifyToken(token);
|
|
183
|
+
context.user = user;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
throw new core_1.AuthenticationError('Invalid authentication token');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
extractTokenFromHeader(req) {
|
|
190
|
+
const authHeader = req.headers?.authorization || req.headers?.Authorization;
|
|
191
|
+
if (typeof authHeader === 'string' && authHeader.startsWith('Bearer ')) {
|
|
192
|
+
return authHeader.substring(7);
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
async trackFailedAttempt(ip, context) {
|
|
197
|
+
const now = Date.now();
|
|
198
|
+
const threshold = this.config.audit?.alertThresholds?.failedAttempts || 5;
|
|
199
|
+
const timeWindow = this.config.audit?.alertThresholds?.timeWindowMs || 300000; // 5 minutes
|
|
200
|
+
const existing = this.failedAttempts.get(ip);
|
|
201
|
+
if (!existing) {
|
|
202
|
+
this.failedAttempts.set(ip, { count: 1, firstAttempt: now });
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// Reset if outside time window
|
|
206
|
+
if (now - existing.firstAttempt > timeWindow) {
|
|
207
|
+
this.failedAttempts.set(ip, { count: 1, firstAttempt: now });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
// Increment count
|
|
211
|
+
existing.count++;
|
|
212
|
+
// Check if threshold exceeded
|
|
213
|
+
if (existing.count >= threshold) {
|
|
214
|
+
await this.auditSecurityEvent({
|
|
215
|
+
type: 'THRESHOLD_EXCEEDED',
|
|
216
|
+
ip,
|
|
217
|
+
userAgent: context.req.userAgent,
|
|
218
|
+
path: context.req.path,
|
|
219
|
+
timestamp: new Date(),
|
|
220
|
+
details: {
|
|
221
|
+
failedAttempts: existing.count,
|
|
222
|
+
timeWindowMs: timeWindow,
|
|
223
|
+
firstAttemptTime: new Date(existing.firstAttempt),
|
|
224
|
+
},
|
|
225
|
+
}, context);
|
|
226
|
+
// Optionally reset counter or keep tracking
|
|
227
|
+
this.failedAttempts.delete(ip);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async auditSecurityEvent(event, context) {
|
|
231
|
+
// Use custom auditor if provided
|
|
232
|
+
if (this.config.audit?.customAuditor) {
|
|
233
|
+
await this.config.audit.customAuditor(event, context);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// Default logging
|
|
237
|
+
const logLevel = event.type.includes('FAILURE') || event.type.includes('EXCEEDED')
|
|
238
|
+
? 'warn'
|
|
239
|
+
: 'info';
|
|
240
|
+
console[logLevel]('[SecurityMiddleware]', {
|
|
241
|
+
type: event.type,
|
|
242
|
+
ip: event.ip,
|
|
243
|
+
path: event.path,
|
|
244
|
+
timestamp: event.timestamp.toISOString(),
|
|
245
|
+
userAgent: event.userAgent,
|
|
246
|
+
details: event.details,
|
|
247
|
+
});
|
|
248
|
+
// Store in business data for downstream processing
|
|
249
|
+
if (!context.businessData.has('securityEvents')) {
|
|
250
|
+
context.businessData.set('securityEvents', []);
|
|
251
|
+
}
|
|
252
|
+
context.businessData.get('securityEvents').push(event);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
exports.SecurityMiddleware = SecurityMiddleware;
|
|
256
|
+
/**
|
|
257
|
+
* Factory function for creating SecurityMiddleware with common configurations
|
|
258
|
+
*/
|
|
259
|
+
exports.createSecurityMiddleware = {
|
|
260
|
+
/**
|
|
261
|
+
* Basic security setup with common headers and JWT authentication
|
|
262
|
+
*/
|
|
263
|
+
basic: (tokenVerifier) => new SecurityMiddleware({
|
|
264
|
+
authentication: { tokenVerifier },
|
|
265
|
+
headers: {
|
|
266
|
+
contentSecurityPolicy: "default-src 'self'",
|
|
267
|
+
xFrameOptions: 'DENY',
|
|
268
|
+
strictTransportSecurity: 'max-age=31536000',
|
|
269
|
+
},
|
|
270
|
+
audit: { logFailedAuth: true },
|
|
271
|
+
}),
|
|
272
|
+
/**
|
|
273
|
+
* Advanced security with audit tracking and suspicious IP monitoring
|
|
274
|
+
*/
|
|
275
|
+
advanced: (tokenVerifier) => new SecurityMiddleware({
|
|
276
|
+
authentication: { tokenVerifier },
|
|
277
|
+
headers: {
|
|
278
|
+
contentSecurityPolicy: "default-src 'self'; script-src 'self' 'unsafe-inline'",
|
|
279
|
+
xFrameOptions: 'DENY',
|
|
280
|
+
strictTransportSecurity: 'max-age=31536000; includeSubDomains',
|
|
281
|
+
referrerPolicy: 'strict-origin-when-cross-origin',
|
|
282
|
+
permissionsPolicy: 'geolocation=(), microphone=(), camera=()',
|
|
283
|
+
},
|
|
284
|
+
audit: {
|
|
285
|
+
logFailedAuth: true,
|
|
286
|
+
logSuccessfulAuth: true,
|
|
287
|
+
trackSuspiciousIPs: true,
|
|
288
|
+
alertThresholds: {
|
|
289
|
+
failedAttempts: 5,
|
|
290
|
+
timeWindowMs: 300000,
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
}),
|
|
294
|
+
/**
|
|
295
|
+
* Headers only - no authentication
|
|
296
|
+
*/
|
|
297
|
+
headersOnly: () => new SecurityMiddleware({
|
|
298
|
+
headers: {
|
|
299
|
+
contentSecurityPolicy: "default-src 'self'",
|
|
300
|
+
xFrameOptions: 'DENY',
|
|
301
|
+
xContentTypeOptions: 'nosniff',
|
|
302
|
+
xXssProtection: '1; mode=block',
|
|
303
|
+
referrerPolicy: 'strict-origin-when-cross-origin',
|
|
304
|
+
},
|
|
305
|
+
}),
|
|
306
|
+
};
|
|
307
|
+
//# sourceMappingURL=SecurityMiddleware.js.map
|
|
@@ -23,20 +23,21 @@ import { z } from 'zod';
|
|
|
23
23
|
*
|
|
24
24
|
* type UserRequest = z.infer<typeof userSchema>;
|
|
25
25
|
*
|
|
26
|
-
* async function handleCreateUser(context: Context<UserRequest
|
|
26
|
+
* async function handleCreateUser(context: Context<UserRequest>) {
|
|
27
27
|
* const user = context.req.validatedBody!; // Fully typed
|
|
28
|
+
* const authenticatedUser = context.user; // User type inferred from auth middleware
|
|
28
29
|
* return { success: true, user: { id: '123', ...user } };
|
|
29
30
|
* }
|
|
30
31
|
*
|
|
31
|
-
* const createUserHandler = new Handler<UserRequest
|
|
32
|
-
* .use(new BodyValidationMiddleware<UserRequest
|
|
32
|
+
* const createUserHandler = new Handler<UserRequest>()
|
|
33
|
+
* .use(new BodyValidationMiddleware<UserRequest>(userSchema))
|
|
33
34
|
* .handle(handleCreateUser);
|
|
34
35
|
* ```
|
|
35
36
|
*/
|
|
36
|
-
export declare class BodyValidationMiddleware<T = unknown
|
|
37
|
+
export declare class BodyValidationMiddleware<T = unknown> implements BaseMiddleware<T> {
|
|
37
38
|
private readonly schema;
|
|
38
39
|
constructor(schema: z.ZodSchema<T>);
|
|
39
|
-
before(context: Context<T
|
|
40
|
+
before(context: Context<T>): Promise<void>;
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
42
43
|
* Factory function that creates a body validation middleware with Zod schema validation.
|
|
@@ -59,18 +60,19 @@ export declare class BodyValidationMiddleware<T = unknown, U = unknown> implemen
|
|
|
59
60
|
*
|
|
60
61
|
* type LoginRequest = z.infer<typeof loginSchema>;
|
|
61
62
|
*
|
|
62
|
-
* async function handleLogin(context: Context<LoginRequest
|
|
63
|
+
* async function handleLogin(context: Context<LoginRequest>) {
|
|
63
64
|
* const credentials = context.req.parsedBody as LoginRequest;
|
|
64
65
|
* const token = await authenticate(credentials.username, credentials.password);
|
|
66
|
+
* const authenticatedUser = context.user; // User type from auth middleware
|
|
65
67
|
* return { success: true, token };
|
|
66
68
|
* }
|
|
67
69
|
*
|
|
68
|
-
* const loginHandler = new Handler<LoginRequest
|
|
69
|
-
* .use(bodyValidatorMiddleware<LoginRequest
|
|
70
|
+
* const loginHandler = new Handler<LoginRequest>()
|
|
71
|
+
* .use(bodyValidatorMiddleware<LoginRequest>(loginSchema))
|
|
70
72
|
* .handle(handleLogin);
|
|
71
73
|
* ```
|
|
72
74
|
*/
|
|
73
|
-
export declare const bodyValidatorMiddleware: <T
|
|
74
|
-
before: (context: Context<T
|
|
75
|
+
export declare const bodyValidatorMiddleware: <T>(schema: z.ZodType<T>) => {
|
|
76
|
+
before: (context: Context<T>) => Promise<void>;
|
|
75
77
|
};
|
|
76
78
|
//# sourceMappingURL=bodyValidationMiddleware.d.ts.map
|
|
@@ -9,7 +9,7 @@ const validateBody = async (schema, data) => {
|
|
|
9
9
|
}
|
|
10
10
|
catch (error) {
|
|
11
11
|
if (error instanceof zod_1.z.ZodError) {
|
|
12
|
-
throw new errors_1.ValidationError('Validation error', error.
|
|
12
|
+
throw new errors_1.ValidationError('Validation error', error.issues);
|
|
13
13
|
}
|
|
14
14
|
throw error;
|
|
15
15
|
}
|
|
@@ -36,13 +36,14 @@ const validateBody = async (schema, data) => {
|
|
|
36
36
|
*
|
|
37
37
|
* type UserRequest = z.infer<typeof userSchema>;
|
|
38
38
|
*
|
|
39
|
-
* async function handleCreateUser(context: Context<UserRequest
|
|
39
|
+
* async function handleCreateUser(context: Context<UserRequest>) {
|
|
40
40
|
* const user = context.req.validatedBody!; // Fully typed
|
|
41
|
+
* const authenticatedUser = context.user; // User type inferred from auth middleware
|
|
41
42
|
* return { success: true, user: { id: '123', ...user } };
|
|
42
43
|
* }
|
|
43
44
|
*
|
|
44
|
-
* const createUserHandler = new Handler<UserRequest
|
|
45
|
-
* .use(new BodyValidationMiddleware<UserRequest
|
|
45
|
+
* const createUserHandler = new Handler<UserRequest>()
|
|
46
|
+
* .use(new BodyValidationMiddleware<UserRequest>(userSchema))
|
|
46
47
|
* .handle(handleCreateUser);
|
|
47
48
|
* ```
|
|
48
49
|
*/
|
|
@@ -77,18 +78,19 @@ exports.BodyValidationMiddleware = BodyValidationMiddleware;
|
|
|
77
78
|
*
|
|
78
79
|
* type LoginRequest = z.infer<typeof loginSchema>;
|
|
79
80
|
*
|
|
80
|
-
* async function handleLogin(context: Context<LoginRequest
|
|
81
|
+
* async function handleLogin(context: Context<LoginRequest>) {
|
|
81
82
|
* const credentials = context.req.parsedBody as LoginRequest;
|
|
82
83
|
* const token = await authenticate(credentials.username, credentials.password);
|
|
84
|
+
* const authenticatedUser = context.user; // User type from auth middleware
|
|
83
85
|
* return { success: true, token };
|
|
84
86
|
* }
|
|
85
87
|
*
|
|
86
|
-
* const loginHandler = new Handler<LoginRequest
|
|
87
|
-
* .use(bodyValidatorMiddleware<LoginRequest
|
|
88
|
+
* const loginHandler = new Handler<LoginRequest>()
|
|
89
|
+
* .use(bodyValidatorMiddleware<LoginRequest>(loginSchema))
|
|
88
90
|
* .handle(handleLogin);
|
|
89
91
|
* ```
|
|
90
92
|
*/
|
|
91
|
-
//
|
|
93
|
+
// Simplified factory function for body validation middleware
|
|
92
94
|
const bodyValidatorMiddleware = (schema) => ({
|
|
93
95
|
before: async (context) => {
|
|
94
96
|
context.req.parsedBody = await validateBody(schema, context.req.body);
|
|
@@ -158,7 +158,7 @@ class DependencyInjectionMiddleware {
|
|
|
158
158
|
this.services.forEach((service) => {
|
|
159
159
|
typedi_1.Container.set(service.id, service.value);
|
|
160
160
|
});
|
|
161
|
-
context.container = typedi_1.Container;
|
|
161
|
+
context.container = typedi_1.Container.of();
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
exports.DependencyInjectionMiddleware = DependencyInjectionMiddleware;
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
* return { valid: false, error: error.message };
|
|
43
43
|
* }
|
|
44
44
|
* },
|
|
45
|
-
* extractUserId: (decoded:
|
|
46
|
-
* isTokenExpired: (decoded:
|
|
45
|
+
* extractUserId: (decoded: unknown) => (decoded as any).sub,
|
|
46
|
+
* isTokenExpired: (decoded: unknown) => (decoded as any).exp < Date.now() / 1000
|
|
47
47
|
* };
|
|
48
48
|
*
|
|
49
49
|
* // Configure guard system
|
|
@@ -155,6 +155,13 @@ import { FastAuthGuard, AuthGuardConfig, TokenValidator } from './guards/FastAut
|
|
|
155
155
|
import { PermissionGuardFactory } from './guards/PermissionGuardFactory';
|
|
156
156
|
import { PermissionRegistry } from './registry/PermissionRegistry';
|
|
157
157
|
import { PermissionExpression } from './resolvers/PermissionResolver';
|
|
158
|
+
import { CustomTokenVerificationPort } from '../authenticationMiddleware';
|
|
159
|
+
import { TokenVerificationAdapterFactory } from './adapters/CustomTokenVerificationPortAdapter';
|
|
160
|
+
/**
|
|
161
|
+
* Union type supporting both RouteGuards TokenValidator and AuthenticationMiddleware CustomTokenVerificationPort.
|
|
162
|
+
* This enables seamless integration between the two authentication systems.
|
|
163
|
+
*/
|
|
164
|
+
export type AnyTokenValidator = TokenValidator | CustomTokenVerificationPort<unknown>;
|
|
158
165
|
/**
|
|
159
166
|
* Route guard configuration for the facade.
|
|
160
167
|
* Provides fine-grained control over guard behavior for specific endpoints.
|
|
@@ -334,11 +341,60 @@ export declare class RouteGuards {
|
|
|
334
341
|
*
|
|
335
342
|
* @param profile - Environment profile with guard configurations
|
|
336
343
|
* @param permissionSource - User permission data source
|
|
337
|
-
* @param tokenValidator -
|
|
344
|
+
* @param tokenValidator - Token validation service (supports both TokenValidator and CustomTokenVerificationPort)
|
|
338
345
|
* @param authConfig - Authentication guard configuration
|
|
339
346
|
* @returns Promise resolving when configuration is complete
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* Using with CustomTokenVerificationPort from AuthenticationMiddleware:
|
|
350
|
+
* ```typescript
|
|
351
|
+
* import { CustomTokenVerificationPort } from '@/middlewares/authenticationMiddleware';
|
|
352
|
+
* import { RouteGuards, GuardSetup } from '@/middlewares/guards';
|
|
353
|
+
*
|
|
354
|
+
* // Same token verifier used across the framework
|
|
355
|
+
* const tokenVerifier: CustomTokenVerificationPort<User> = {
|
|
356
|
+
* async verifyToken(token: string): Promise<User> {
|
|
357
|
+
* const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
|
|
358
|
+
* return {
|
|
359
|
+
* id: payload.sub,
|
|
360
|
+
* email: payload.email,
|
|
361
|
+
* roles: payload.roles || [],
|
|
362
|
+
* sub: payload.sub,
|
|
363
|
+
* exp: payload.exp
|
|
364
|
+
* };
|
|
365
|
+
* }
|
|
366
|
+
* };
|
|
367
|
+
*
|
|
368
|
+
* // Configure RouteGuards with the same verifier
|
|
369
|
+
* await RouteGuards.configure(
|
|
370
|
+
* GuardSetup.production(),
|
|
371
|
+
* userPermissionSource,
|
|
372
|
+
* tokenVerifier, // Automatically wrapped with adapter
|
|
373
|
+
* authConfig
|
|
374
|
+
* );
|
|
375
|
+
* ```
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* Traditional usage with TokenValidator (backward compatible):
|
|
379
|
+
* ```typescript
|
|
380
|
+
* const tokenValidator: TokenValidator = {
|
|
381
|
+
* async validateToken(token: string) {
|
|
382
|
+
* // Your existing validation logic
|
|
383
|
+
* return { valid: true, decoded: userPayload };
|
|
384
|
+
* },
|
|
385
|
+
* extractUserId: (decoded) => decoded.sub,
|
|
386
|
+
* isTokenExpired: (decoded) => decoded.exp < Date.now() / 1000
|
|
387
|
+
* };
|
|
388
|
+
*
|
|
389
|
+
* await RouteGuards.configure(
|
|
390
|
+
* GuardSetup.production(),
|
|
391
|
+
* userPermissionSource,
|
|
392
|
+
* tokenValidator, // Works as before
|
|
393
|
+
* authConfig
|
|
394
|
+
* );
|
|
395
|
+
* ```
|
|
340
396
|
*/
|
|
341
|
-
static configure(profile: GuardEnvironmentProfile, permissionSource: UserPermissionSource, tokenValidator:
|
|
397
|
+
static configure(profile: GuardEnvironmentProfile, permissionSource: UserPermissionSource, tokenValidator: AnyTokenValidator, authConfig: AuthGuardConfig): Promise<void>;
|
|
342
398
|
/**
|
|
343
399
|
* Get the configured RouteGuards instance
|
|
344
400
|
*
|
|
@@ -461,6 +517,185 @@ export declare class RouteGuards {
|
|
|
461
517
|
details: Record<string, unknown>;
|
|
462
518
|
timestamp: string;
|
|
463
519
|
}>;
|
|
520
|
+
/**
|
|
521
|
+
* Factory method: Configure RouteGuards with CustomTokenVerificationPort for JWT tokens.
|
|
522
|
+
* Provides a streamlined setup for JWT-based authentication with common field extraction.
|
|
523
|
+
*
|
|
524
|
+
* @example
|
|
525
|
+
* Quick JWT setup with CustomTokenVerificationPort:
|
|
526
|
+
* ```typescript
|
|
527
|
+
* import { CustomTokenVerificationPort } from '@/middlewares/authenticationMiddleware';
|
|
528
|
+
*
|
|
529
|
+
* interface JWTUser {
|
|
530
|
+
* sub: string;
|
|
531
|
+
* email: string;
|
|
532
|
+
* roles: string[];
|
|
533
|
+
* exp: number;
|
|
534
|
+
* }
|
|
535
|
+
*
|
|
536
|
+
* const jwtVerifier: CustomTokenVerificationPort<JWTUser> = {
|
|
537
|
+
* async verifyToken(token: string): Promise<JWTUser> {
|
|
538
|
+
* const payload = jwt.verify(token, process.env.JWT_SECRET!) as any;
|
|
539
|
+
* return {
|
|
540
|
+
* sub: payload.sub,
|
|
541
|
+
* email: payload.email,
|
|
542
|
+
* roles: payload.roles || [],
|
|
543
|
+
* exp: payload.exp
|
|
544
|
+
* };
|
|
545
|
+
* }
|
|
546
|
+
* };
|
|
547
|
+
*
|
|
548
|
+
* // One-line setup for JWT authentication
|
|
549
|
+
* await RouteGuards.configureWithJWT(
|
|
550
|
+
* GuardSetup.production(),
|
|
551
|
+
* userPermissionSource,
|
|
552
|
+
* jwtVerifier,
|
|
553
|
+
* {
|
|
554
|
+
* tokenHeader: 'authorization',
|
|
555
|
+
* tokenPrefix: 'Bearer ',
|
|
556
|
+
* requireEmailVerification: true
|
|
557
|
+
* }
|
|
558
|
+
* );
|
|
559
|
+
* ```
|
|
560
|
+
*/
|
|
561
|
+
static configureWithJWT<T extends {
|
|
562
|
+
sub: string;
|
|
563
|
+
exp?: number;
|
|
564
|
+
}>(profile: GuardEnvironmentProfile, permissionSource: UserPermissionSource, jwtVerifier: CustomTokenVerificationPort<T>, authConfig: AuthGuardConfig): Promise<void>;
|
|
565
|
+
/**
|
|
566
|
+
* Factory method: Configure RouteGuards with CustomTokenVerificationPort for API keys.
|
|
567
|
+
* Provides setup for API key-based authentication with flexible field mapping.
|
|
568
|
+
*
|
|
569
|
+
* @example
|
|
570
|
+
* API key authentication setup:
|
|
571
|
+
* ```typescript
|
|
572
|
+
* interface APIKeyUser {
|
|
573
|
+
* keyId: string;
|
|
574
|
+
* permissions: string[];
|
|
575
|
+
* organization: string;
|
|
576
|
+
* expiresAt?: number;
|
|
577
|
+
* isActive: boolean;
|
|
578
|
+
* }
|
|
579
|
+
*
|
|
580
|
+
* const apiKeyVerifier: CustomTokenVerificationPort<APIKeyUser> = {
|
|
581
|
+
* async verifyToken(token: string): Promise<APIKeyUser> {
|
|
582
|
+
* const keyData = await validateAPIKeyInDatabase(token);
|
|
583
|
+
* if (!keyData || !keyData.isActive) {
|
|
584
|
+
* throw new Error('Invalid or inactive API key');
|
|
585
|
+
* }
|
|
586
|
+
* return keyData;
|
|
587
|
+
* }
|
|
588
|
+
* };
|
|
589
|
+
*
|
|
590
|
+
* await RouteGuards.configureWithAPIKey(
|
|
591
|
+
* GuardSetup.production(),
|
|
592
|
+
* userPermissionSource,
|
|
593
|
+
* apiKeyVerifier,
|
|
594
|
+
* {
|
|
595
|
+
* tokenHeader: 'x-api-key',
|
|
596
|
+
* tokenPrefix: '',
|
|
597
|
+
* allowInactiveUsers: false
|
|
598
|
+
* },
|
|
599
|
+
* 'keyId',
|
|
600
|
+
* 'expiresAt'
|
|
601
|
+
* );
|
|
602
|
+
* ```
|
|
603
|
+
*/
|
|
604
|
+
static configureWithAPIKey<T extends Record<string, unknown>>(profile: GuardEnvironmentProfile, permissionSource: UserPermissionSource, apiKeyVerifier: CustomTokenVerificationPort<T>, authConfig: AuthGuardConfig, userIdField: keyof T, expirationField?: keyof T): Promise<void>;
|
|
605
|
+
/**
|
|
606
|
+
* Factory method: Configure RouteGuards with CustomTokenVerificationPort for OAuth tokens.
|
|
607
|
+
* Provides setup for OAuth-based authentication with scope validation.
|
|
608
|
+
*
|
|
609
|
+
* @example
|
|
610
|
+
* OAuth token authentication with scope requirements:
|
|
611
|
+
* ```typescript
|
|
612
|
+
* interface OAuthUser {
|
|
613
|
+
* sub: string;
|
|
614
|
+
* email: string;
|
|
615
|
+
* scope: string[];
|
|
616
|
+
* exp: number;
|
|
617
|
+
* client_id: string;
|
|
618
|
+
* }
|
|
619
|
+
*
|
|
620
|
+
* const oauthVerifier: CustomTokenVerificationPort<OAuthUser> = {
|
|
621
|
+
* async verifyToken(token: string): Promise<OAuthUser> {
|
|
622
|
+
* const response = await fetch(`${OAUTH_INTROSPECT_URL}`, {
|
|
623
|
+
* method: 'POST',
|
|
624
|
+
* headers: { 'Authorization': `Bearer ${token}` },
|
|
625
|
+
* body: new URLSearchParams({ token })
|
|
626
|
+
* });
|
|
627
|
+
*
|
|
628
|
+
* const tokenInfo = await response.json();
|
|
629
|
+
* if (!tokenInfo.active) {
|
|
630
|
+
* throw new Error('Token is not active');
|
|
631
|
+
* }
|
|
632
|
+
*
|
|
633
|
+
* return tokenInfo as OAuthUser;
|
|
634
|
+
* }
|
|
635
|
+
* };
|
|
636
|
+
*
|
|
637
|
+
* await RouteGuards.configureWithOAuth(
|
|
638
|
+
* GuardSetup.production(),
|
|
639
|
+
* userPermissionSource,
|
|
640
|
+
* oauthVerifier,
|
|
641
|
+
* {
|
|
642
|
+
* tokenHeader: 'authorization',
|
|
643
|
+
* tokenPrefix: 'Bearer ',
|
|
644
|
+
* requireEmailVerification: false
|
|
645
|
+
* },
|
|
646
|
+
* ['read:profile', 'write:data'] // Required OAuth scopes
|
|
647
|
+
* );
|
|
648
|
+
* ```
|
|
649
|
+
*/
|
|
650
|
+
static configureWithOAuth<T extends {
|
|
651
|
+
sub: string;
|
|
652
|
+
exp?: number;
|
|
653
|
+
scope?: string[];
|
|
654
|
+
}>(profile: GuardEnvironmentProfile, permissionSource: UserPermissionSource, oauthVerifier: CustomTokenVerificationPort<T>, authConfig: AuthGuardConfig, requiredScopes?: string[]): Promise<void>;
|
|
655
|
+
/**
|
|
656
|
+
* Factory method: Configure RouteGuards with a custom CustomTokenVerificationPort adapter.
|
|
657
|
+
* Provides maximum flexibility for custom token validation scenarios.
|
|
658
|
+
*
|
|
659
|
+
* @example
|
|
660
|
+
* Custom token validation with business-specific logic:
|
|
661
|
+
* ```typescript
|
|
662
|
+
* interface CustomUser {
|
|
663
|
+
* userId: string;
|
|
664
|
+
* tenantId: string;
|
|
665
|
+
* roles: string[];
|
|
666
|
+
* sessionExpiry: number;
|
|
667
|
+
* isVerified: boolean;
|
|
668
|
+
* }
|
|
669
|
+
*
|
|
670
|
+
* const customVerifier: CustomTokenVerificationPort<CustomUser> = {
|
|
671
|
+
* async verifyToken(token: string): Promise<CustomUser> {
|
|
672
|
+
* // Your custom verification logic
|
|
673
|
+
* return await verifyCustomToken(token);
|
|
674
|
+
* }
|
|
675
|
+
* };
|
|
676
|
+
*
|
|
677
|
+
* await RouteGuards.configureWithCustom(
|
|
678
|
+
* GuardSetup.production(),
|
|
679
|
+
* userPermissionSource,
|
|
680
|
+
* customVerifier,
|
|
681
|
+
* {
|
|
682
|
+
* tokenHeader: 'x-auth-token',
|
|
683
|
+
* tokenPrefix: 'Custom ',
|
|
684
|
+
* customValidation: async (token, user) => {
|
|
685
|
+
* return user.isVerified && user.tenantId === 'valid-tenant';
|
|
686
|
+
* }
|
|
687
|
+
* },
|
|
688
|
+
* {
|
|
689
|
+
* userIdExtractor: (user) => user.userId,
|
|
690
|
+
* expirationExtractor: (user) => user.sessionExpiry,
|
|
691
|
+
* additionalValidation: (user) => user.isVerified
|
|
692
|
+
* }
|
|
693
|
+
* );
|
|
694
|
+
* ```
|
|
695
|
+
*/
|
|
696
|
+
static configureWithCustom<T>(profile: GuardEnvironmentProfile, permissionSource: UserPermissionSource, customVerifier: CustomTokenVerificationPort<T>, authConfig: AuthGuardConfig, adapterConfig: Omit<Parameters<typeof TokenVerificationAdapterFactory.custom<T>>[1], 'userIdExtractor'> & {
|
|
697
|
+
userIdExtractor: (user: T) => string;
|
|
698
|
+
}): Promise<void>;
|
|
464
699
|
private createPlainPermissionGuard;
|
|
465
700
|
private createWildcardPermissionGuard;
|
|
466
701
|
private createExpressionPermissionGuard;
|