@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,425 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createProcessingMiddleware = exports.ProcessingMiddleware = void 0;
|
|
4
|
+
const core_1 = require("../core");
|
|
5
|
+
/**
|
|
6
|
+
* Consolidated ProcessingMiddleware that combines body parsing, query processing, and attribute extraction.
|
|
7
|
+
*
|
|
8
|
+
* This middleware replaces the need for separate:
|
|
9
|
+
* - BodyParserMiddleware
|
|
10
|
+
* - QueryParametersMiddleware
|
|
11
|
+
* - HttpAttributesMiddleware
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* Complete processing setup:
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const handler = new Handler()
|
|
17
|
+
* .use(new ProcessingMiddleware({
|
|
18
|
+
* parser: {
|
|
19
|
+
* maxSize: 1024 * 1024, // 1MB
|
|
20
|
+
* supportPubSub: true,
|
|
21
|
+
* enableAsyncParsing: true
|
|
22
|
+
* },
|
|
23
|
+
* query: {
|
|
24
|
+
* parseArrays: true,
|
|
25
|
+
* parseNumbers: true,
|
|
26
|
+
* parseBooleans: true,
|
|
27
|
+
* maxKeys: 100
|
|
28
|
+
* },
|
|
29
|
+
* attributes: {
|
|
30
|
+
* extractIP: true,
|
|
31
|
+
* extractUserAgent: true,
|
|
32
|
+
* extractTimestamp: true,
|
|
33
|
+
* trustProxy: true
|
|
34
|
+
* }
|
|
35
|
+
* }))
|
|
36
|
+
* .handle(async (context) => {
|
|
37
|
+
* // context.req.parsedBody contains parsed JSON
|
|
38
|
+
* // context.req.query contains processed query parameters
|
|
39
|
+
* // context.req.ip, context.req.userAgent, etc. are extracted
|
|
40
|
+
* return { message: 'Processing complete' };
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* Parser-only for API endpoints:
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const handler = new Handler()
|
|
48
|
+
* .use(new ProcessingMiddleware({
|
|
49
|
+
* parser: {
|
|
50
|
+
* maxSize: 512 * 1024, // 512KB
|
|
51
|
+
* supportPubSub: false
|
|
52
|
+
* }
|
|
53
|
+
* }));
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
class ProcessingMiddleware {
|
|
57
|
+
config;
|
|
58
|
+
constructor(config = {}) {
|
|
59
|
+
this.config = {
|
|
60
|
+
parser: {
|
|
61
|
+
maxSize: 1024 * 1024, // 1MB default
|
|
62
|
+
supportPubSub: true,
|
|
63
|
+
allowEmptyBody: true,
|
|
64
|
+
enableAsyncParsing: true,
|
|
65
|
+
asyncThreshold: 10000, // 10KB
|
|
66
|
+
},
|
|
67
|
+
query: {
|
|
68
|
+
parseArrays: false,
|
|
69
|
+
parseNumbers: false,
|
|
70
|
+
parseBooleans: false,
|
|
71
|
+
maxKeys: 1000,
|
|
72
|
+
delimiter: '&',
|
|
73
|
+
arrayDelimiter: ',',
|
|
74
|
+
},
|
|
75
|
+
attributes: {
|
|
76
|
+
extractIP: true,
|
|
77
|
+
extractUserAgent: true,
|
|
78
|
+
extractTimestamp: false,
|
|
79
|
+
trustProxy: false,
|
|
80
|
+
},
|
|
81
|
+
...config,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async before(context) {
|
|
85
|
+
// Skip processing if custom skip function returns true
|
|
86
|
+
if (this.config.skipProcessing && this.config.skipProcessing(context)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// 1. Extract HTTP attributes first (lightweight)
|
|
90
|
+
if (this.config.attributes) {
|
|
91
|
+
await this.extractAttributes(context);
|
|
92
|
+
}
|
|
93
|
+
// 2. Process query parameters (moderate cost)
|
|
94
|
+
if (this.config.query) {
|
|
95
|
+
await this.processQueryParameters(context);
|
|
96
|
+
}
|
|
97
|
+
// 3. Parse body (most expensive, do last)
|
|
98
|
+
if (this.config.parser) {
|
|
99
|
+
await this.parseBody(context);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async extractAttributes(context) {
|
|
103
|
+
const attributesConfig = this.config.attributes;
|
|
104
|
+
const req = context.req;
|
|
105
|
+
// Extract IP address
|
|
106
|
+
if (attributesConfig.extractIP) {
|
|
107
|
+
req.ip = this.extractIPAddress(req, attributesConfig.trustProxy || false);
|
|
108
|
+
}
|
|
109
|
+
// Extract User-Agent
|
|
110
|
+
if (attributesConfig.extractUserAgent) {
|
|
111
|
+
req.userAgent = this.extractUserAgent(req);
|
|
112
|
+
}
|
|
113
|
+
// Extract timestamp
|
|
114
|
+
if (attributesConfig.extractTimestamp) {
|
|
115
|
+
req.timestamp =
|
|
116
|
+
new Date().toISOString();
|
|
117
|
+
}
|
|
118
|
+
// Extract content length
|
|
119
|
+
if (attributesConfig.extractContentLength) {
|
|
120
|
+
req.contentLength =
|
|
121
|
+
this.extractContentLength(req);
|
|
122
|
+
}
|
|
123
|
+
// Extract Accept-Language
|
|
124
|
+
if (attributesConfig.extractAcceptLanguage) {
|
|
125
|
+
req.acceptLanguage =
|
|
126
|
+
this.extractAcceptLanguage(req);
|
|
127
|
+
}
|
|
128
|
+
// Extract Referer
|
|
129
|
+
if (attributesConfig.extractReferer) {
|
|
130
|
+
req.referer =
|
|
131
|
+
this.extractReferer(req);
|
|
132
|
+
}
|
|
133
|
+
// Apply custom extractors
|
|
134
|
+
if (attributesConfig.customExtractors) {
|
|
135
|
+
for (const [key, extractor] of Object.entries(attributesConfig.customExtractors)) {
|
|
136
|
+
try {
|
|
137
|
+
req[key] = extractor(req);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
console.warn(`Custom extractor '${key}' failed:`, error);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async processQueryParameters(context) {
|
|
146
|
+
const queryConfig = this.config.query;
|
|
147
|
+
const query = context.req.query || {};
|
|
148
|
+
// Check max keys limit
|
|
149
|
+
if (queryConfig.maxKeys &&
|
|
150
|
+
Object.keys(query).length > queryConfig.maxKeys) {
|
|
151
|
+
throw new core_1.ValidationError('Too many query parameters', `Maximum ${queryConfig.maxKeys} query parameters allowed`);
|
|
152
|
+
}
|
|
153
|
+
// Apply custom parser first if provided
|
|
154
|
+
if (queryConfig.customParser) {
|
|
155
|
+
try {
|
|
156
|
+
context.req.query = queryConfig.customParser(query);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
throw new core_1.ValidationError('Query parameter parsing failed', error instanceof Error ? error.message : 'Custom parser error');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Process each query parameter
|
|
164
|
+
const processedQuery = {};
|
|
165
|
+
for (const [key, value] of Object.entries(query)) {
|
|
166
|
+
processedQuery[key] = this.processQueryValue(value, queryConfig.parseArrays || false, queryConfig.parseNumbers || false, queryConfig.parseBooleans || false, queryConfig.arrayDelimiter || ',');
|
|
167
|
+
}
|
|
168
|
+
context.req.query = processedQuery;
|
|
169
|
+
}
|
|
170
|
+
async parseBody(context) {
|
|
171
|
+
const parserConfig = this.config.parser;
|
|
172
|
+
const body = context.req.body;
|
|
173
|
+
// Skip if no body or body is already parsed
|
|
174
|
+
if (!body || context.req.parsedBody) {
|
|
175
|
+
if (parserConfig.allowEmptyBody) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
throw new core_1.ValidationError('Request body is required');
|
|
179
|
+
}
|
|
180
|
+
// Apply custom parser if provided
|
|
181
|
+
if (parserConfig.customParser) {
|
|
182
|
+
try {
|
|
183
|
+
context.req.parsedBody = await parserConfig.customParser(body);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
throw new core_1.ValidationError('Custom body parsing failed', error instanceof Error ? error.message : 'Custom parser error');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Handle different body types
|
|
191
|
+
if (typeof body === 'string') {
|
|
192
|
+
await this.parseStringBody(context, body, parserConfig);
|
|
193
|
+
}
|
|
194
|
+
else if (typeof body === 'object' && body !== null) {
|
|
195
|
+
// Handle PubSub messages if enabled
|
|
196
|
+
if (parserConfig.supportPubSub && this.isPubSubMessage(body)) {
|
|
197
|
+
await this.parsePubSubMessage(context, body, parserConfig);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
// Already an object, store as is
|
|
201
|
+
context.req.parsedBody = body;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
context.req.parsedBody = body;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async parseStringBody(context, body, config) {
|
|
209
|
+
// Check size limit
|
|
210
|
+
if (config.maxSize && Buffer.byteLength(body, 'utf8') > config.maxSize) {
|
|
211
|
+
throw new core_1.TooLargeError(`Request body size exceeds limit of ${config.maxSize} bytes`);
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
// Use async parsing for large payloads if enabled
|
|
215
|
+
if (config.enableAsyncParsing &&
|
|
216
|
+
body.length > (config.asyncThreshold || 10000)) {
|
|
217
|
+
context.req.parsedBody = await this.parseJsonAsync(body);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
context.req.parsedBody = JSON.parse(body);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
throw new core_1.ValidationError('Invalid JSON body', error instanceof Error ? error.message : 'JSON parsing failed');
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async parsePubSubMessage(context, body, config) {
|
|
228
|
+
try {
|
|
229
|
+
const encodedData = body.message.data;
|
|
230
|
+
// Validate base64 format
|
|
231
|
+
this.validateBase64Format(encodedData);
|
|
232
|
+
// Check size limit before decoding
|
|
233
|
+
if (config.maxSize && encodedData.length > config.maxSize * 1.33) {
|
|
234
|
+
// Account for base64 overhead
|
|
235
|
+
throw new core_1.TooLargeError(`PubSub message size exceeds limit of ${config.maxSize} bytes`);
|
|
236
|
+
}
|
|
237
|
+
// Decode base64 data
|
|
238
|
+
const decodedData = Buffer.from(encodedData, 'base64').toString('utf-8');
|
|
239
|
+
// Parse JSON content
|
|
240
|
+
context.req.parsedBody =
|
|
241
|
+
config.enableAsyncParsing &&
|
|
242
|
+
decodedData.length > (config.asyncThreshold || 10000)
|
|
243
|
+
? await this.parseJsonAsync(decodedData)
|
|
244
|
+
: JSON.parse(decodedData);
|
|
245
|
+
// Store PubSub metadata
|
|
246
|
+
context.req.pubsubMetadata = {
|
|
247
|
+
publishTime: body.message.publishTime,
|
|
248
|
+
messageId: body.message.messageId,
|
|
249
|
+
attributes: body.message.attributes,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
throw new core_1.ValidationError('PubSub message parsing failed', error instanceof Error ? error.message : 'PubSub parsing error');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
processQueryValue(value, parseArrays, parseNumbers, parseBooleans, arrayDelimiter) {
|
|
257
|
+
if (typeof value !== 'string') {
|
|
258
|
+
return value; // Return as-is if not string
|
|
259
|
+
}
|
|
260
|
+
// Handle arrays
|
|
261
|
+
if (parseArrays && value.includes(arrayDelimiter)) {
|
|
262
|
+
return value
|
|
263
|
+
.split(arrayDelimiter)
|
|
264
|
+
.map((item) => this.processQueryValue(item.trim(), false, parseNumbers, parseBooleans, arrayDelimiter));
|
|
265
|
+
}
|
|
266
|
+
// Handle booleans
|
|
267
|
+
if (parseBooleans) {
|
|
268
|
+
const lowerValue = value.toLowerCase();
|
|
269
|
+
if (lowerValue === 'true')
|
|
270
|
+
return true;
|
|
271
|
+
if (lowerValue === 'false')
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
// Handle numbers
|
|
275
|
+
if (parseNumbers && /^-?\d+(\.\d+)?$/.test(value)) {
|
|
276
|
+
const numValue = Number(value);
|
|
277
|
+
if (!isNaN(numValue))
|
|
278
|
+
return numValue;
|
|
279
|
+
}
|
|
280
|
+
return value; // Return as string if no parsing applied
|
|
281
|
+
}
|
|
282
|
+
extractIPAddress(req, trustProxy) {
|
|
283
|
+
if (trustProxy) {
|
|
284
|
+
// Check X-Forwarded-For headers
|
|
285
|
+
const xForwardedFor = req.headers?.['x-forwarded-for'] || req.headers?.['X-Forwarded-For'];
|
|
286
|
+
if (xForwardedFor) {
|
|
287
|
+
const ips = Array.isArray(xForwardedFor)
|
|
288
|
+
? xForwardedFor[0]
|
|
289
|
+
: xForwardedFor;
|
|
290
|
+
return typeof ips === 'string' ? ips.split(',')[0].trim() : ips;
|
|
291
|
+
}
|
|
292
|
+
const xRealIP = req.headers?.['x-real-ip'] || req.headers?.['X-Real-IP'];
|
|
293
|
+
if (xRealIP && typeof xRealIP === 'string') {
|
|
294
|
+
return xRealIP;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Fallback to direct IP sources
|
|
298
|
+
return (req.ip ||
|
|
299
|
+
req
|
|
300
|
+
.connection?.remoteAddress ||
|
|
301
|
+
req.socket
|
|
302
|
+
?.remoteAddress ||
|
|
303
|
+
req.requestContext?.identity?.sourceIp ||
|
|
304
|
+
'unknown');
|
|
305
|
+
}
|
|
306
|
+
extractUserAgent(req) {
|
|
307
|
+
const userAgent = req.headers?.['user-agent'] ||
|
|
308
|
+
req.headers?.['User-Agent'] ||
|
|
309
|
+
req.get?.('user-agent') ||
|
|
310
|
+
'unknown';
|
|
311
|
+
return Array.isArray(userAgent) ? userAgent[0] : userAgent;
|
|
312
|
+
}
|
|
313
|
+
extractContentLength(req) {
|
|
314
|
+
const contentLength = req.headers?.['content-length'] || req.headers?.['Content-Length'];
|
|
315
|
+
const lengthValue = Array.isArray(contentLength) ? contentLength[0] : contentLength;
|
|
316
|
+
return lengthValue ? parseInt(lengthValue, 10) : undefined;
|
|
317
|
+
}
|
|
318
|
+
extractAcceptLanguage(req) {
|
|
319
|
+
const acceptLanguage = req.headers?.['accept-language'] || req.headers?.['Accept-Language'];
|
|
320
|
+
return Array.isArray(acceptLanguage) ? acceptLanguage[0] : acceptLanguage;
|
|
321
|
+
}
|
|
322
|
+
extractReferer(req) {
|
|
323
|
+
const referer = req.headers?.referer ||
|
|
324
|
+
req.headers?.Referer ||
|
|
325
|
+
req.headers?.referrer ||
|
|
326
|
+
req.headers?.Referrer;
|
|
327
|
+
return Array.isArray(referer) ? referer[0] : referer;
|
|
328
|
+
}
|
|
329
|
+
isPubSubMessage(body) {
|
|
330
|
+
return (!!body &&
|
|
331
|
+
typeof body === 'object' &&
|
|
332
|
+
'message' in body &&
|
|
333
|
+
typeof body.message === 'object' &&
|
|
334
|
+
'data' in body.message &&
|
|
335
|
+
typeof body.message.data === 'string');
|
|
336
|
+
}
|
|
337
|
+
validateBase64Format(base64Data) {
|
|
338
|
+
// Basic base64 format validation
|
|
339
|
+
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
340
|
+
if (!base64Regex.test(base64Data)) {
|
|
341
|
+
throw new core_1.ValidationError('Invalid base64 format in PubSub message');
|
|
342
|
+
}
|
|
343
|
+
if (base64Data.length % 4 !== 0) {
|
|
344
|
+
throw new core_1.ValidationError('Invalid base64 length - must be multiple of 4');
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async parseJsonAsync(jsonString) {
|
|
348
|
+
// Use setImmediate to make JSON parsing non-blocking for large payloads
|
|
349
|
+
return new Promise((resolve, reject) => {
|
|
350
|
+
setImmediate(() => {
|
|
351
|
+
try {
|
|
352
|
+
const result = JSON.parse(jsonString);
|
|
353
|
+
resolve(result);
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
reject(new core_1.ValidationError('Invalid JSON body', error instanceof Error ? error.message : 'JSON parsing failed'));
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
exports.ProcessingMiddleware = ProcessingMiddleware;
|
|
363
|
+
/**
|
|
364
|
+
* Factory functions for creating ProcessingMiddleware with common configurations
|
|
365
|
+
*/
|
|
366
|
+
exports.createProcessingMiddleware = {
|
|
367
|
+
/**
|
|
368
|
+
* API processing with JSON parsing and basic attributes
|
|
369
|
+
*/
|
|
370
|
+
api: () => new ProcessingMiddleware({
|
|
371
|
+
parser: { maxSize: 1024 * 1024, supportPubSub: false },
|
|
372
|
+
query: { parseNumbers: true, parseBooleans: true, maxKeys: 100 },
|
|
373
|
+
attributes: { extractIP: true, extractUserAgent: true },
|
|
374
|
+
}),
|
|
375
|
+
/**
|
|
376
|
+
* PubSub processing with base64 decoding
|
|
377
|
+
*/
|
|
378
|
+
pubsub: () => new ProcessingMiddleware({
|
|
379
|
+
parser: {
|
|
380
|
+
maxSize: 2 * 1024 * 1024,
|
|
381
|
+
supportPubSub: true,
|
|
382
|
+
enableAsyncParsing: true,
|
|
383
|
+
},
|
|
384
|
+
attributes: { extractIP: true, extractTimestamp: true },
|
|
385
|
+
}),
|
|
386
|
+
/**
|
|
387
|
+
* Lightweight processing for simple endpoints
|
|
388
|
+
*/
|
|
389
|
+
lightweight: () => new ProcessingMiddleware({
|
|
390
|
+
parser: {
|
|
391
|
+
maxSize: 64 * 1024,
|
|
392
|
+
supportPubSub: false,
|
|
393
|
+
enableAsyncParsing: false,
|
|
394
|
+
},
|
|
395
|
+
query: { parseNumbers: false, parseBooleans: false, maxKeys: 20 },
|
|
396
|
+
attributes: { extractIP: false, extractUserAgent: false },
|
|
397
|
+
}),
|
|
398
|
+
/**
|
|
399
|
+
* Full processing with all features enabled
|
|
400
|
+
*/
|
|
401
|
+
complete: () => new ProcessingMiddleware({
|
|
402
|
+
parser: {
|
|
403
|
+
maxSize: 5 * 1024 * 1024,
|
|
404
|
+
supportPubSub: true,
|
|
405
|
+
enableAsyncParsing: true,
|
|
406
|
+
asyncThreshold: 50000,
|
|
407
|
+
},
|
|
408
|
+
query: {
|
|
409
|
+
parseArrays: true,
|
|
410
|
+
parseNumbers: true,
|
|
411
|
+
parseBooleans: true,
|
|
412
|
+
maxKeys: 1000,
|
|
413
|
+
},
|
|
414
|
+
attributes: {
|
|
415
|
+
extractIP: true,
|
|
416
|
+
extractUserAgent: true,
|
|
417
|
+
extractTimestamp: true,
|
|
418
|
+
extractContentLength: true,
|
|
419
|
+
extractAcceptLanguage: true,
|
|
420
|
+
extractReferer: true,
|
|
421
|
+
trustProxy: true,
|
|
422
|
+
},
|
|
423
|
+
}),
|
|
424
|
+
};
|
|
425
|
+
//# sourceMappingURL=ProcessingMiddleware.js.map
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { BaseMiddleware, Context, NoonyRequest } from '../core';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for authentication functionality
|
|
4
|
+
*/
|
|
5
|
+
export interface AuthenticationConfig<T = unknown> {
|
|
6
|
+
tokenVerifier?: {
|
|
7
|
+
verifyToken(token: string): Promise<T>;
|
|
8
|
+
};
|
|
9
|
+
extractToken?: (req: NoonyRequest) => string | null;
|
|
10
|
+
onAuthFailure?: (error: Error, context: Context) => Promise<void>;
|
|
11
|
+
skipPaths?: string[];
|
|
12
|
+
optional?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Configuration for security headers
|
|
16
|
+
*/
|
|
17
|
+
export interface SecurityHeadersConfig {
|
|
18
|
+
contentSecurityPolicy?: string;
|
|
19
|
+
xFrameOptions?: 'DENY' | 'SAMEORIGIN' | string;
|
|
20
|
+
strictTransportSecurity?: string;
|
|
21
|
+
xContentTypeOptions?: 'nosniff';
|
|
22
|
+
xXssProtection?: '1; mode=block' | '0' | '1';
|
|
23
|
+
referrerPolicy?: string;
|
|
24
|
+
permissionsPolicy?: string;
|
|
25
|
+
customHeaders?: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Configuration for security audit functionality
|
|
29
|
+
*/
|
|
30
|
+
export interface SecurityAuditConfig {
|
|
31
|
+
logFailedAuth?: boolean;
|
|
32
|
+
trackSuspiciousIPs?: boolean;
|
|
33
|
+
alertThresholds?: {
|
|
34
|
+
failedAttempts?: number;
|
|
35
|
+
timeWindowMs?: number;
|
|
36
|
+
};
|
|
37
|
+
enableMetrics?: boolean;
|
|
38
|
+
logSuccessfulAuth?: boolean;
|
|
39
|
+
customAuditor?: (event: SecurityAuditEvent, context: Context) => Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Security audit event types
|
|
43
|
+
*/
|
|
44
|
+
export interface SecurityAuditEvent {
|
|
45
|
+
type: 'AUTH_SUCCESS' | 'AUTH_FAILURE' | 'SUSPICIOUS_IP' | 'THRESHOLD_EXCEEDED';
|
|
46
|
+
ip?: string;
|
|
47
|
+
userAgent?: string;
|
|
48
|
+
path?: string;
|
|
49
|
+
timestamp: Date;
|
|
50
|
+
details?: Record<string, unknown>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Complete configuration for SecurityMiddleware
|
|
54
|
+
*/
|
|
55
|
+
export interface SecurityMiddlewareConfig<T = unknown> {
|
|
56
|
+
authentication?: AuthenticationConfig<T>;
|
|
57
|
+
headers?: SecurityHeadersConfig;
|
|
58
|
+
audit?: SecurityAuditConfig;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Consolidated SecurityMiddleware that combines authentication, security headers, and audit logging.
|
|
62
|
+
*
|
|
63
|
+
* This middleware replaces the need for separate:
|
|
64
|
+
* - AuthenticationMiddleware
|
|
65
|
+
* - SecurityHeadersMiddleware
|
|
66
|
+
* - SecurityAuditMiddleware
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* Basic security with authentication and headers:
|
|
70
|
+
* ```typescript
|
|
71
|
+
* const handler = new Handler()
|
|
72
|
+
* .use(new SecurityMiddleware({
|
|
73
|
+
* authentication: {
|
|
74
|
+
* tokenVerifier: {
|
|
75
|
+
* verifyToken: async (token) => jwt.verify(token, secret)
|
|
76
|
+
* },
|
|
77
|
+
* extractToken: (req) => req.headers.authorization?.replace('Bearer ', '')
|
|
78
|
+
* },
|
|
79
|
+
* headers: {
|
|
80
|
+
* contentSecurityPolicy: "default-src 'self'",
|
|
81
|
+
* xFrameOptions: 'DENY',
|
|
82
|
+
* strictTransportSecurity: 'max-age=31536000; includeSubDomains'
|
|
83
|
+
* },
|
|
84
|
+
* audit: {
|
|
85
|
+
* logFailedAuth: true,
|
|
86
|
+
* trackSuspiciousIPs: true
|
|
87
|
+
* }
|
|
88
|
+
* }))
|
|
89
|
+
* .handle(async (context) => {
|
|
90
|
+
* // context.user is populated if authentication succeeds
|
|
91
|
+
* const user = context.user;
|
|
92
|
+
* return { message: `Hello ${user?.name}` };
|
|
93
|
+
* });
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* Advanced security with custom auditing:
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const handler = new Handler()
|
|
100
|
+
* .use(new SecurityMiddleware({
|
|
101
|
+
* authentication: {
|
|
102
|
+
* tokenVerifier: customTokenVerifier,
|
|
103
|
+
* skipPaths: ['/health', '/metrics'],
|
|
104
|
+
* onAuthFailure: async (error, context) => {
|
|
105
|
+
* await logSecurityEvent('auth_failure', {
|
|
106
|
+
* ip: context.req.ip,
|
|
107
|
+
* error: error.message
|
|
108
|
+
* });
|
|
109
|
+
* }
|
|
110
|
+
* },
|
|
111
|
+
* audit: {
|
|
112
|
+
* customAuditor: async (event, context) => {
|
|
113
|
+
* await sendToSecuritySystem(event);
|
|
114
|
+
* },
|
|
115
|
+
* alertThresholds: {
|
|
116
|
+
* failedAttempts: 5,
|
|
117
|
+
* timeWindowMs: 300000 // 5 minutes
|
|
118
|
+
* }
|
|
119
|
+
* }
|
|
120
|
+
* }));
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export declare class SecurityMiddleware<T = unknown> implements BaseMiddleware<T> {
|
|
124
|
+
private config;
|
|
125
|
+
private failedAttempts;
|
|
126
|
+
constructor(config?: SecurityMiddlewareConfig<T>);
|
|
127
|
+
before(context: Context<T>): Promise<void>;
|
|
128
|
+
after(context: Context<T>): Promise<void>;
|
|
129
|
+
onError(error: Error, context: Context<T>): Promise<void>;
|
|
130
|
+
private setSecurityHeaders;
|
|
131
|
+
private authenticateRequest;
|
|
132
|
+
private extractTokenFromHeader;
|
|
133
|
+
private trackFailedAttempt;
|
|
134
|
+
private auditSecurityEvent;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Factory function for creating SecurityMiddleware with common configurations
|
|
138
|
+
*/
|
|
139
|
+
export declare const createSecurityMiddleware: {
|
|
140
|
+
/**
|
|
141
|
+
* Basic security setup with common headers and JWT authentication
|
|
142
|
+
*/
|
|
143
|
+
basic: <T = unknown>(tokenVerifier: {
|
|
144
|
+
verifyToken(token: string): Promise<T>;
|
|
145
|
+
}) => SecurityMiddleware<T>;
|
|
146
|
+
/**
|
|
147
|
+
* Advanced security with audit tracking and suspicious IP monitoring
|
|
148
|
+
*/
|
|
149
|
+
advanced: <T = unknown>(tokenVerifier: {
|
|
150
|
+
verifyToken(token: string): Promise<T>;
|
|
151
|
+
}) => SecurityMiddleware<T>;
|
|
152
|
+
/**
|
|
153
|
+
* Headers only - no authentication
|
|
154
|
+
*/
|
|
155
|
+
headersOnly: () => SecurityMiddleware;
|
|
156
|
+
};
|
|
157
|
+
//# sourceMappingURL=SecurityMiddleware.d.ts.map
|