@noony-serverless/core 0.1.1 → 0.2.0
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/build/core/core.d.ts +16 -48
- package/build/core/core.js +2 -61
- 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/authenticationMiddleware.d.ts +379 -0
- package/build/middlewares/authenticationMiddleware.js +216 -0
- package/build/middlewares/bodyParserMiddleware.d.ts +99 -0
- package/build/middlewares/bodyParserMiddleware.js +99 -0
- package/build/middlewares/bodyValidationMiddleware.d.ts +69 -3
- package/build/middlewares/bodyValidationMiddleware.js +68 -2
- package/build/middlewares/dependencyInjectionMiddleware.d.ts +238 -0
- package/build/middlewares/dependencyInjectionMiddleware.js +238 -0
- package/build/middlewares/errorHandlerMiddleware.d.ts +94 -0
- package/build/middlewares/errorHandlerMiddleware.js +105 -0
- package/build/middlewares/guards/RouteGuards.d.ts +476 -21
- package/build/middlewares/guards/RouteGuards.js +418 -21
- package/build/middlewares/guards/adapters/CustomTokenVerificationPortAdapter.d.ts +271 -0
- package/build/middlewares/guards/adapters/CustomTokenVerificationPortAdapter.js +301 -0
- package/build/middlewares/guards/cache/CacheAdapter.d.ts +369 -28
- package/build/middlewares/guards/cache/CacheAdapter.js +124 -5
- package/build/middlewares/guards/cache/MemoryCacheAdapter.d.ts +113 -4
- package/build/middlewares/guards/cache/MemoryCacheAdapter.js +113 -4
- package/build/middlewares/guards/config/GuardConfiguration.d.ts +568 -18
- package/build/middlewares/guards/config/GuardConfiguration.js +266 -10
- 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 +19 -5
- package/build/middlewares/headerVariablesMiddleware.d.ts +118 -0
- package/build/middlewares/headerVariablesMiddleware.js +118 -0
- package/build/middlewares/httpAttributesMiddleware.d.ts +235 -0
- package/build/middlewares/httpAttributesMiddleware.js +236 -1
- package/build/middlewares/index.d.ts +3 -1
- package/build/middlewares/index.js +6 -1
- package/build/middlewares/queryParametersMiddleware.d.ts +105 -0
- package/build/middlewares/queryParametersMiddleware.js +105 -0
- package/build/middlewares/rateLimitingMiddleware.d.ts +601 -9
- package/build/middlewares/rateLimitingMiddleware.js +623 -11
- package/build/middlewares/responseWrapperMiddleware.d.ts +170 -1
- package/build/middlewares/responseWrapperMiddleware.js +170 -1
- package/build/middlewares/securityAuditMiddleware.js +5 -5
- 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 -9
- package/build/middlewares/validationMiddleware.js +0 -40
|
@@ -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
|
|
@@ -1,8 +1,107 @@
|
|
|
1
1
|
import { BaseMiddleware } from '../core/handler';
|
|
2
2
|
import { Context } from '../core/core';
|
|
3
|
+
/**
|
|
4
|
+
* Interface for custom token verification implementations.
|
|
5
|
+
* Allows integration with various authentication providers (JWT, OAuth, custom tokens).
|
|
6
|
+
*
|
|
7
|
+
* @template T - The type of user data returned after successful token verification
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* JWT token verification:
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import jwt from 'jsonwebtoken';
|
|
13
|
+
* import { CustomTokenVerificationPort } from '@noony-serverless/core';
|
|
14
|
+
*
|
|
15
|
+
* interface User {
|
|
16
|
+
* id: string;
|
|
17
|
+
* email: string;
|
|
18
|
+
* roles: string[];
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* class JWTVerificationPort implements CustomTokenVerificationPort<User> {
|
|
22
|
+
* constructor(private secret: string) {}
|
|
23
|
+
*
|
|
24
|
+
* async verifyToken(token: string): Promise<User> {
|
|
25
|
+
* try {
|
|
26
|
+
* const payload = jwt.verify(token, this.secret) as any;
|
|
27
|
+
* return {
|
|
28
|
+
* id: payload.sub,
|
|
29
|
+
* email: payload.email,
|
|
30
|
+
* roles: payload.roles || []
|
|
31
|
+
* };
|
|
32
|
+
* } catch (error) {
|
|
33
|
+
* throw new Error('Invalid token');
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* Custom API token verification:
|
|
41
|
+
* ```typescript
|
|
42
|
+
* class APIKeyVerificationPort implements CustomTokenVerificationPort<{ apiKey: string; permissions: string[] }> {
|
|
43
|
+
* async verifyToken(token: string): Promise<{ apiKey: string; permissions: string[] }> {
|
|
44
|
+
* const apiKey = await this.validateAPIKey(token);
|
|
45
|
+
* if (!apiKey) {
|
|
46
|
+
* throw new Error('Invalid API key');
|
|
47
|
+
* }
|
|
48
|
+
* return {
|
|
49
|
+
* apiKey: token,
|
|
50
|
+
* permissions: apiKey.permissions
|
|
51
|
+
* };
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* private async validateAPIKey(key: string) {
|
|
55
|
+
* // Validate against database or external service
|
|
56
|
+
* return { permissions: ['read', 'write'] };
|
|
57
|
+
* }
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
3
61
|
export interface CustomTokenVerificationPort<T> {
|
|
4
62
|
verifyToken(token: string): Promise<T>;
|
|
5
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Standard JWT payload interface with common claims.
|
|
66
|
+
* Extends the payload with custom properties as needed.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* Basic JWT payload usage:
|
|
70
|
+
* ```typescript
|
|
71
|
+
* import { JWTPayload } from '@noony-serverless/core';
|
|
72
|
+
*
|
|
73
|
+
* interface CustomJWTPayload extends JWTPayload {
|
|
74
|
+
* userId: string;
|
|
75
|
+
* email: string;
|
|
76
|
+
* roles: string[];
|
|
77
|
+
* }
|
|
78
|
+
*
|
|
79
|
+
* const payload: CustomJWTPayload = {
|
|
80
|
+
* sub: 'user-123',
|
|
81
|
+
* exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour
|
|
82
|
+
* iat: Math.floor(Date.now() / 1000),
|
|
83
|
+
* iss: 'my-app',
|
|
84
|
+
* aud: 'my-app-users',
|
|
85
|
+
* userId: 'user-123',
|
|
86
|
+
* email: 'user@example.com',
|
|
87
|
+
* roles: ['user', 'admin']
|
|
88
|
+
* };
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* Token validation with custom claims:
|
|
93
|
+
* ```typescript
|
|
94
|
+
* function validateCustomClaims(payload: JWTPayload & { roles?: string[] }) {
|
|
95
|
+
* if (!payload.roles || payload.roles.length === 0) {
|
|
96
|
+
* throw new Error('User must have at least one role');
|
|
97
|
+
* }
|
|
98
|
+
*
|
|
99
|
+
* if (payload.exp && payload.exp < Date.now() / 1000) {
|
|
100
|
+
* throw new Error('Token has expired');
|
|
101
|
+
* }
|
|
102
|
+
* }
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
6
105
|
export interface JWTPayload {
|
|
7
106
|
exp?: number;
|
|
8
107
|
iat?: number;
|
|
@@ -13,6 +112,70 @@ export interface JWTPayload {
|
|
|
13
112
|
sub?: string;
|
|
14
113
|
[key: string]: unknown;
|
|
15
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Configuration options for authentication middleware.
|
|
117
|
+
* Provides comprehensive security controls and validation settings.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* Basic authentication options:
|
|
121
|
+
* ```typescript
|
|
122
|
+
* import { AuthenticationOptions } from '@noony-serverless/core';
|
|
123
|
+
*
|
|
124
|
+
* const basicOptions: AuthenticationOptions = {
|
|
125
|
+
* maxTokenAge: 3600, // 1 hour
|
|
126
|
+
* clockTolerance: 60, // 1 minute
|
|
127
|
+
* requiredClaims: {
|
|
128
|
+
* issuer: 'my-app',
|
|
129
|
+
* audience: 'my-app-users'
|
|
130
|
+
* }
|
|
131
|
+
* };
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* Advanced options with rate limiting and blacklisting:
|
|
136
|
+
* ```typescript
|
|
137
|
+
* const advancedOptions: AuthenticationOptions = {
|
|
138
|
+
* maxTokenAge: 7200, // 2 hours
|
|
139
|
+
* clockTolerance: 30,
|
|
140
|
+
* rateLimiting: {
|
|
141
|
+
* maxAttempts: 5,
|
|
142
|
+
* windowMs: 15 * 60 * 1000 // 15 minutes
|
|
143
|
+
* },
|
|
144
|
+
* isTokenBlacklisted: async (tokenId) => {
|
|
145
|
+
* // Check Redis or database for blacklisted tokens
|
|
146
|
+
* return await redis.sismember('blacklisted_tokens', tokenId);
|
|
147
|
+
* },
|
|
148
|
+
* requiredClaims: {
|
|
149
|
+
* issuer: 'secure-app',
|
|
150
|
+
* audience: ['web-app', 'mobile-app']
|
|
151
|
+
* }
|
|
152
|
+
* };
|
|
153
|
+
* ```
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* Production security configuration:
|
|
157
|
+
* ```typescript
|
|
158
|
+
* const productionOptions: AuthenticationOptions = {
|
|
159
|
+
* maxTokenAge: 1800, // 30 minutes - short for security
|
|
160
|
+
* clockTolerance: 10, // Tight tolerance
|
|
161
|
+
* rateLimiting: {
|
|
162
|
+
* maxAttempts: 3, // Strict rate limiting
|
|
163
|
+
* windowMs: 30 * 60 * 1000 // 30 minutes lockout
|
|
164
|
+
* },
|
|
165
|
+
* isTokenBlacklisted: async (tokenId) => {
|
|
166
|
+
* const result = await database.query(
|
|
167
|
+
* 'SELECT 1 FROM revoked_tokens WHERE jti = ?',
|
|
168
|
+
* [tokenId]
|
|
169
|
+
* );
|
|
170
|
+
* return result.length > 0;
|
|
171
|
+
* },
|
|
172
|
+
* requiredClaims: {
|
|
173
|
+
* issuer: 'production-auth-server',
|
|
174
|
+
* audience: 'production-api'
|
|
175
|
+
* }
|
|
176
|
+
* };
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
16
179
|
export interface AuthenticationOptions {
|
|
17
180
|
/**
|
|
18
181
|
* Maximum token age in seconds (overrides exp claim validation)
|
|
@@ -42,11 +205,227 @@ export interface AuthenticationOptions {
|
|
|
42
205
|
audience?: string | string[];
|
|
43
206
|
};
|
|
44
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Class-based authentication middleware with comprehensive security features.
|
|
210
|
+
* Provides JWT validation, rate limiting, token blacklisting, and security logging.
|
|
211
|
+
*
|
|
212
|
+
* @template T - The type of user data returned by the token verification port
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* Basic JWT authentication:
|
|
216
|
+
* ```typescript
|
|
217
|
+
* import { Handler, AuthenticationMiddleware } from '@noony-serverless/core';
|
|
218
|
+
* import jwt from 'jsonwebtoken';
|
|
219
|
+
*
|
|
220
|
+
* interface User {
|
|
221
|
+
* id: string;
|
|
222
|
+
* email: string;
|
|
223
|
+
* roles: string[];
|
|
224
|
+
* }
|
|
225
|
+
*
|
|
226
|
+
* class JWTVerifier implements CustomTokenVerificationPort<User> {
|
|
227
|
+
* async verifyToken(token: string): Promise<User> {
|
|
228
|
+
* const payload = jwt.verify(token, process.env.JWT_SECRET!) as any;
|
|
229
|
+
* return {
|
|
230
|
+
* id: payload.sub,
|
|
231
|
+
* email: payload.email,
|
|
232
|
+
* roles: payload.roles || []
|
|
233
|
+
* };
|
|
234
|
+
* }
|
|
235
|
+
* }
|
|
236
|
+
*
|
|
237
|
+
* const protectedHandler = new Handler()
|
|
238
|
+
* .use(new AuthenticationMiddleware(new JWTVerifier()))
|
|
239
|
+
* .handle(async (request, context) => {
|
|
240
|
+
* const user = context.user as User;
|
|
241
|
+
* return {
|
|
242
|
+
* success: true,
|
|
243
|
+
* data: { message: `Hello ${user.email}`, userId: user.id }
|
|
244
|
+
* };
|
|
245
|
+
* });
|
|
246
|
+
* ```
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* Advanced authentication with security options:
|
|
250
|
+
* ```typescript
|
|
251
|
+
* const secureAuthMiddleware = new AuthenticationMiddleware(
|
|
252
|
+
* new JWTVerifier(),
|
|
253
|
+
* {
|
|
254
|
+
* maxTokenAge: 1800, // 30 minutes
|
|
255
|
+
* rateLimiting: {
|
|
256
|
+
* maxAttempts: 5,
|
|
257
|
+
* windowMs: 15 * 60 * 1000 // 15 minutes
|
|
258
|
+
* },
|
|
259
|
+
* isTokenBlacklisted: async (tokenId) => {
|
|
260
|
+
* return await redis.sismember('revoked_tokens', tokenId);
|
|
261
|
+
* },
|
|
262
|
+
* requiredClaims: {
|
|
263
|
+
* issuer: 'my-auth-server',
|
|
264
|
+
* audience: 'my-api'
|
|
265
|
+
* }
|
|
266
|
+
* }
|
|
267
|
+
* );
|
|
268
|
+
*
|
|
269
|
+
* const secureHandler = new Handler()
|
|
270
|
+
* .use(secureAuthMiddleware)
|
|
271
|
+
* .handle(async (request, context) => {
|
|
272
|
+
* // Only authenticated users reach here
|
|
273
|
+
* return { success: true, data: 'Secure data' };
|
|
274
|
+
* });
|
|
275
|
+
* ```
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* Google Cloud Functions integration:
|
|
279
|
+
* ```typescript
|
|
280
|
+
* import { http } from '@google-cloud/functions-framework';
|
|
281
|
+
*
|
|
282
|
+
* const userProfileHandler = new Handler()
|
|
283
|
+
* .use(new AuthenticationMiddleware(new JWTVerifier()))
|
|
284
|
+
* .handle(async (request, context) => {
|
|
285
|
+
* const user = context.user as User;
|
|
286
|
+
* const profile = await getUserProfile(user.id);
|
|
287
|
+
* return { success: true, data: profile };
|
|
288
|
+
* });
|
|
289
|
+
*
|
|
290
|
+
* export const getUserProfile = http('getUserProfile', (req, res) => {
|
|
291
|
+
* return userProfileHandler.execute(req, res);
|
|
292
|
+
* });
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
45
295
|
export declare class AuthenticationMiddleware<T> implements BaseMiddleware {
|
|
46
296
|
private tokenVerificationPort;
|
|
47
297
|
private options;
|
|
48
298
|
constructor(tokenVerificationPort: CustomTokenVerificationPort<T>, options?: AuthenticationOptions);
|
|
49
299
|
before(context: Context): Promise<void>;
|
|
50
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Factory function that creates an authentication middleware with token verification.
|
|
303
|
+
* Provides a functional approach for authentication setup.
|
|
304
|
+
*
|
|
305
|
+
* @template T - The type of user data returned by the token verification port
|
|
306
|
+
* @param tokenVerificationPort - The token verification implementation
|
|
307
|
+
* @param options - Authentication configuration options
|
|
308
|
+
* @returns A BaseMiddleware object with authentication logic
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* Simple JWT authentication:
|
|
312
|
+
* ```typescript
|
|
313
|
+
* import { Handler, verifyAuthTokenMiddleware } from '@noony-serverless/core';
|
|
314
|
+
*
|
|
315
|
+
* class SimpleJWTVerifier implements CustomTokenVerificationPort<{ userId: string }> {
|
|
316
|
+
* async verifyToken(token: string): Promise<{ userId: string }> {
|
|
317
|
+
* // Simple token verification logic
|
|
318
|
+
* if (token === 'valid-token') {
|
|
319
|
+
* return { userId: 'user-123' };
|
|
320
|
+
* }
|
|
321
|
+
* throw new Error('Invalid token');
|
|
322
|
+
* }
|
|
323
|
+
* }
|
|
324
|
+
*
|
|
325
|
+
* const handler = new Handler()
|
|
326
|
+
* .use(verifyAuthTokenMiddleware(new SimpleJWTVerifier()))
|
|
327
|
+
* .handle(async (request, context) => {
|
|
328
|
+
* const user = context.user as { userId: string };
|
|
329
|
+
* return { success: true, userId: user.userId };
|
|
330
|
+
* });
|
|
331
|
+
* ```
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* API key authentication with rate limiting:
|
|
335
|
+
* ```typescript
|
|
336
|
+
* interface APIKeyUser {
|
|
337
|
+
* keyId: string;
|
|
338
|
+
* permissions: string[];
|
|
339
|
+
* organization: string;
|
|
340
|
+
* }
|
|
341
|
+
*
|
|
342
|
+
* class APIKeyVerifier implements CustomTokenVerificationPort<APIKeyUser> {
|
|
343
|
+
* async verifyToken(token: string): Promise<APIKeyUser> {
|
|
344
|
+
* const keyData = await this.validateAPIKey(token);
|
|
345
|
+
* if (!keyData) {
|
|
346
|
+
* throw new Error('Invalid API key');
|
|
347
|
+
* }
|
|
348
|
+
* return keyData;
|
|
349
|
+
* }
|
|
350
|
+
*
|
|
351
|
+
* private async validateAPIKey(key: string): Promise<APIKeyUser | null> {
|
|
352
|
+
* // Database lookup or external validation
|
|
353
|
+
* return {
|
|
354
|
+
* keyId: 'key-123',
|
|
355
|
+
* permissions: ['read', 'write'],
|
|
356
|
+
* organization: 'org-456'
|
|
357
|
+
* };
|
|
358
|
+
* }
|
|
359
|
+
* }
|
|
360
|
+
*
|
|
361
|
+
* const apiHandler = new Handler()
|
|
362
|
+
* .use(verifyAuthTokenMiddleware(
|
|
363
|
+
* new APIKeyVerifier(),
|
|
364
|
+
* {
|
|
365
|
+
* rateLimiting: {
|
|
366
|
+
* maxAttempts: 100,
|
|
367
|
+
* windowMs: 60 * 1000 // 1 minute
|
|
368
|
+
* }
|
|
369
|
+
* }
|
|
370
|
+
* ))
|
|
371
|
+
* .handle(async (request, context) => {
|
|
372
|
+
* const apiUser = context.user as APIKeyUser;
|
|
373
|
+
* return {
|
|
374
|
+
* success: true,
|
|
375
|
+
* data: { organization: apiUser.organization }
|
|
376
|
+
* };
|
|
377
|
+
* });
|
|
378
|
+
* ```
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* Express-style middleware chain:
|
|
382
|
+
* ```typescript
|
|
383
|
+
* import { Handler, verifyAuthTokenMiddleware, errorHandler } from '@noony-serverless/core';
|
|
384
|
+
*
|
|
385
|
+
* const authMiddleware = verifyAuthTokenMiddleware(
|
|
386
|
+
* new JWTVerifier(),
|
|
387
|
+
* {
|
|
388
|
+
* maxTokenAge: 3600,
|
|
389
|
+
* requiredClaims: {
|
|
390
|
+
* issuer: 'my-app',
|
|
391
|
+
* audience: 'api-users'
|
|
392
|
+
* }
|
|
393
|
+
* }
|
|
394
|
+
* );
|
|
395
|
+
*
|
|
396
|
+
* const protectedEndpoint = new Handler()
|
|
397
|
+
* .use(authMiddleware)
|
|
398
|
+
* .use(errorHandler())
|
|
399
|
+
* .handle(async (request, context) => {
|
|
400
|
+
* // Authenticated user available in context.user
|
|
401
|
+
* return { success: true, data: 'Protected resource' };
|
|
402
|
+
* });
|
|
403
|
+
* ```
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* Multiple authentication strategies:
|
|
407
|
+
* ```typescript
|
|
408
|
+
* // Different handlers for different auth types
|
|
409
|
+
* const jwtHandler = new Handler()
|
|
410
|
+
* .use(verifyAuthTokenMiddleware(new JWTVerifier()))
|
|
411
|
+
* .handle(jwtLogic);
|
|
412
|
+
*
|
|
413
|
+
* const apiKeyHandler = new Handler()
|
|
414
|
+
* .use(verifyAuthTokenMiddleware(new APIKeyVerifier()))
|
|
415
|
+
* .handle(apiKeyLogic);
|
|
416
|
+
*
|
|
417
|
+
* // Route based on authentication type
|
|
418
|
+
* export const handleRequest = (req: any, res: any) => {
|
|
419
|
+
* const authHeader = req.headers.authorization;
|
|
420
|
+
* if (authHeader?.startsWith('Bearer jwt.')) {
|
|
421
|
+
* return jwtHandler.execute(req, res);
|
|
422
|
+
* } else if (authHeader?.startsWith('Bearer ak_')) {
|
|
423
|
+
* return apiKeyHandler.execute(req, res);
|
|
424
|
+
* } else {
|
|
425
|
+
* res.status(401).json({ error: 'Authentication required' });
|
|
426
|
+
* }
|
|
427
|
+
* };
|
|
428
|
+
* ```
|
|
429
|
+
*/
|
|
51
430
|
export declare const verifyAuthTokenMiddleware: <T>(tokenVerificationPort: CustomTokenVerificationPort<T>, options?: AuthenticationOptions) => BaseMiddleware;
|
|
52
431
|
//# sourceMappingURL=authenticationMiddleware.d.ts.map
|