mastercontroller 1.2.11 → 1.2.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.
@@ -0,0 +1,486 @@
1
+ // version 1.0.0
2
+ // MasterController Security Middleware - CSRF, Headers, Rate Limiting, CORS
3
+
4
+ /**
5
+ * Security middleware for MasterController
6
+ * Provides: CSRF protection, Security headers, Rate limiting, CORS
7
+ */
8
+
9
+ const crypto = require('crypto');
10
+ const { logger } = require('./MasterErrorLogger');
11
+
12
+ // Rate limiting store
13
+ const rateLimitStore = new Map();
14
+
15
+ // CSRF token store (use Redis in production)
16
+ const csrfTokenStore = new Map();
17
+
18
+ // Security headers configuration
19
+ const SECURITY_HEADERS = {
20
+ // Prevent XSS attacks
21
+ 'X-XSS-Protection': '1; mode=block',
22
+
23
+ // Prevent clickjacking
24
+ 'X-Frame-Options': 'SAMEORIGIN',
25
+
26
+ // Prevent MIME type sniffing
27
+ 'X-Content-Type-Options': 'nosniff',
28
+
29
+ // Prevent DNS prefetching
30
+ 'X-DNS-Prefetch-Control': 'off',
31
+
32
+ // Disable browser features
33
+ 'Permissions-Policy': 'geolocation=(), microphone=(), camera=()',
34
+
35
+ // Referrer policy
36
+ 'Referrer-Policy': 'strict-origin-when-cross-origin',
37
+
38
+ // Remove X-Powered-By header
39
+ 'X-Powered-By': ''
40
+ };
41
+
42
+ // HSTS (HTTP Strict Transport Security) - only in production
43
+ const HSTS_HEADER = {
44
+ 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload'
45
+ };
46
+
47
+ class SecurityMiddleware {
48
+ constructor(options = {}) {
49
+ this.csrfEnabled = options.csrf !== false;
50
+ this.rateLimitEnabled = options.rateLimit !== false;
51
+ this.corsEnabled = options.cors !== false;
52
+ this.headersEnabled = options.headers !== false;
53
+
54
+ // Rate limit config
55
+ this.rateLimitWindow = options.rateLimitWindow || 60000; // 1 minute
56
+ this.rateLimitMax = options.rateLimitMax || 100; // 100 requests per window
57
+
58
+ // CSRF config
59
+ this.csrfCookieName = options.csrfCookieName || '_csrf';
60
+ this.csrfHeaderName = options.csrfHeaderName || 'x-csrf-token';
61
+ this.csrfTokenExpiry = options.csrfTokenExpiry || 3600000; // 1 hour
62
+
63
+ // CORS config
64
+ this.corsOrigins = options.corsOrigins || ['*'];
65
+ this.corsMethods = options.corsMethods || ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'];
66
+ this.corsHeaders = options.corsHeaders || ['Content-Type', 'Authorization', 'X-Requested-With'];
67
+
68
+ // Start cleanup interval
69
+ this._startCleanup();
70
+ }
71
+
72
+ /**
73
+ * Apply security headers to response
74
+ */
75
+ securityHeadersMiddleware(req, res, next) {
76
+ if (!this.headersEnabled) {
77
+ return next();
78
+ }
79
+
80
+ // Apply standard security headers
81
+ for (const [header, value] of Object.entries(SECURITY_HEADERS)) {
82
+ if (value === '') {
83
+ res.removeHeader(header);
84
+ } else {
85
+ res.setHeader(header, value);
86
+ }
87
+ }
88
+
89
+ // Apply HSTS only in production over HTTPS
90
+ const isProduction = process.env.NODE_ENV === 'production';
91
+ const isHTTPS = req.connection.encrypted || req.headers['x-forwarded-proto'] === 'https';
92
+
93
+ if (isProduction && isHTTPS) {
94
+ for (const [header, value] of Object.entries(HSTS_HEADER)) {
95
+ res.setHeader(header, value);
96
+ }
97
+ }
98
+
99
+ next();
100
+ }
101
+
102
+ /**
103
+ * CORS middleware
104
+ */
105
+ corsMiddleware(req, res, next) {
106
+ if (!this.corsEnabled) {
107
+ return next();
108
+ }
109
+
110
+ const origin = req.headers.origin;
111
+
112
+ // Check if origin is allowed
113
+ if (this.corsOrigins.includes('*') || this.corsOrigins.includes(origin)) {
114
+ res.setHeader('Access-Control-Allow-Origin', origin || '*');
115
+ res.setHeader('Access-Control-Allow-Methods', this.corsMethods.join(', '));
116
+ res.setHeader('Access-Control-Allow-Headers', this.corsHeaders.join(', '));
117
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
118
+ res.setHeader('Access-Control-Max-Age', '86400'); // 24 hours
119
+ }
120
+
121
+ // Handle preflight requests
122
+ if (req.method === 'OPTIONS') {
123
+ res.writeHead(204);
124
+ res.end();
125
+ return;
126
+ }
127
+
128
+ next();
129
+ }
130
+
131
+ /**
132
+ * Rate limiting middleware
133
+ */
134
+ rateLimitMiddleware(req, res, next) {
135
+ if (!this.rateLimitEnabled) {
136
+ return next();
137
+ }
138
+
139
+ const identifier = this._getClientIdentifier(req);
140
+ const now = Date.now();
141
+ const windowStart = now - this.rateLimitWindow;
142
+
143
+ // Get or create rate limit record
144
+ let record = rateLimitStore.get(identifier);
145
+ if (!record) {
146
+ record = { requests: [], blocked: false, blockExpiry: 0 };
147
+ rateLimitStore.set(identifier, record);
148
+ }
149
+
150
+ // Check if blocked
151
+ if (record.blocked && now < record.blockExpiry) {
152
+ const retryAfter = Math.ceil((record.blockExpiry - now) / 1000);
153
+ res.setHeader('Retry-After', retryAfter);
154
+ res.setHeader('X-RateLimit-Limit', this.rateLimitMax);
155
+ res.setHeader('X-RateLimit-Remaining', 0);
156
+ res.setHeader('X-RateLimit-Reset', new Date(record.blockExpiry).toISOString());
157
+
158
+ logger.warn({
159
+ code: 'MC_SECURITY_RATE_LIMIT_EXCEEDED',
160
+ message: 'Rate limit exceeded',
161
+ identifier,
162
+ ip: this._getClientIP(req)
163
+ });
164
+
165
+ res.writeHead(429, { 'Content-Type': 'application/json' });
166
+ res.end(JSON.stringify({
167
+ error: 'Too Many Requests',
168
+ message: 'Rate limit exceeded. Please try again later.',
169
+ retryAfter: retryAfter
170
+ }));
171
+ return;
172
+ }
173
+
174
+ // Remove old requests outside window
175
+ record.requests = record.requests.filter(timestamp => timestamp > windowStart);
176
+
177
+ // Check if limit exceeded
178
+ if (record.requests.length >= this.rateLimitMax) {
179
+ record.blocked = true;
180
+ record.blockExpiry = now + this.rateLimitWindow;
181
+
182
+ logger.warn({
183
+ code: 'MC_SECURITY_RATE_LIMIT_TRIGGERED',
184
+ message: 'Rate limit triggered',
185
+ identifier,
186
+ ip: this._getClientIP(req),
187
+ requests: record.requests.length
188
+ });
189
+
190
+ const retryAfter = Math.ceil(this.rateLimitWindow / 1000);
191
+ res.setHeader('Retry-After', retryAfter);
192
+ res.writeHead(429, { 'Content-Type': 'application/json' });
193
+ res.end(JSON.stringify({
194
+ error: 'Too Many Requests',
195
+ message: 'Rate limit exceeded. Please try again later.',
196
+ retryAfter: retryAfter
197
+ }));
198
+ return;
199
+ }
200
+
201
+ // Add current request
202
+ record.requests.push(now);
203
+
204
+ // Set rate limit headers
205
+ const remaining = this.rateLimitMax - record.requests.length;
206
+ const resetTime = now + this.rateLimitWindow;
207
+
208
+ res.setHeader('X-RateLimit-Limit', this.rateLimitMax);
209
+ res.setHeader('X-RateLimit-Remaining', remaining);
210
+ res.setHeader('X-RateLimit-Reset', new Date(resetTime).toISOString());
211
+
212
+ next();
213
+ }
214
+
215
+ /**
216
+ * CSRF protection middleware
217
+ */
218
+ csrfMiddleware(req, res, next) {
219
+ if (!this.csrfEnabled) {
220
+ return next();
221
+ }
222
+
223
+ // Skip CSRF for safe methods
224
+ const safeMethods = ['GET', 'HEAD', 'OPTIONS'];
225
+ if (safeMethods.includes(req.method)) {
226
+ return next();
227
+ }
228
+
229
+ // Get CSRF token from request
230
+ const tokenFromHeader = req.headers[this.csrfHeaderName];
231
+ const tokenFromBody = req.body && req.body._csrf;
232
+ const tokenFromQuery = req.url.includes('_csrf=') ? this._getQueryParam(req.url, '_csrf') : null;
233
+
234
+ const clientToken = tokenFromHeader || tokenFromBody || tokenFromQuery;
235
+
236
+ if (!clientToken) {
237
+ logger.warn({
238
+ code: 'MC_SECURITY_CSRF_MISSING',
239
+ message: 'CSRF token missing',
240
+ method: req.method,
241
+ path: req.url,
242
+ ip: this._getClientIP(req)
243
+ });
244
+
245
+ res.writeHead(403, { 'Content-Type': 'application/json' });
246
+ res.end(JSON.stringify({
247
+ error: 'Forbidden',
248
+ message: 'CSRF token missing'
249
+ }));
250
+ return;
251
+ }
252
+
253
+ // Verify CSRF token
254
+ const storedToken = csrfTokenStore.get(clientToken);
255
+
256
+ if (!storedToken) {
257
+ logger.warn({
258
+ code: 'MC_SECURITY_CSRF_INVALID',
259
+ message: 'CSRF token invalid',
260
+ method: req.method,
261
+ path: req.url,
262
+ ip: this._getClientIP(req)
263
+ });
264
+
265
+ res.writeHead(403, { 'Content-Type': 'application/json' });
266
+ res.end(JSON.stringify({
267
+ error: 'Forbidden',
268
+ message: 'CSRF token invalid'
269
+ }));
270
+ return;
271
+ }
272
+
273
+ // Check token expiry
274
+ if (Date.now() > storedToken.expiry) {
275
+ csrfTokenStore.delete(clientToken);
276
+
277
+ logger.warn({
278
+ code: 'MC_SECURITY_CSRF_EXPIRED',
279
+ message: 'CSRF token expired',
280
+ method: req.method,
281
+ path: req.url,
282
+ ip: this._getClientIP(req)
283
+ });
284
+
285
+ res.writeHead(403, { 'Content-Type': 'application/json' });
286
+ res.end(JSON.stringify({
287
+ error: 'Forbidden',
288
+ message: 'CSRF token expired'
289
+ }));
290
+ return;
291
+ }
292
+
293
+ // Token valid, continue
294
+ next();
295
+ }
296
+
297
+ /**
298
+ * Generate CSRF token
299
+ */
300
+ generateCSRFToken(sessionId = null) {
301
+ const token = crypto.randomBytes(32).toString('hex');
302
+ const expiry = Date.now() + this.csrfTokenExpiry;
303
+
304
+ csrfTokenStore.set(token, {
305
+ sessionId,
306
+ expiry,
307
+ createdAt: Date.now()
308
+ });
309
+
310
+ return token;
311
+ }
312
+
313
+ /**
314
+ * Validate CSRF token manually
315
+ */
316
+ validateCSRFToken(token) {
317
+ const storedToken = csrfTokenStore.get(token);
318
+
319
+ if (!storedToken) {
320
+ return { valid: false, reason: 'Token not found' };
321
+ }
322
+
323
+ if (Date.now() > storedToken.expiry) {
324
+ csrfTokenStore.delete(token);
325
+ return { valid: false, reason: 'Token expired' };
326
+ }
327
+
328
+ return { valid: true };
329
+ }
330
+
331
+ /**
332
+ * Get client identifier for rate limiting
333
+ */
334
+ _getClientIdentifier(req) {
335
+ // Use session ID if available
336
+ if (req.session && req.session.id) {
337
+ return `session:${req.session.id}`;
338
+ }
339
+
340
+ // Use API key if available
341
+ if (req.headers['x-api-key']) {
342
+ return `api:${req.headers['x-api-key']}`;
343
+ }
344
+
345
+ // Fall back to IP
346
+ return `ip:${this._getClientIP(req)}`;
347
+ }
348
+
349
+ /**
350
+ * Get client IP address
351
+ */
352
+ _getClientIP(req) {
353
+ // Check for forwarded IP (behind proxy/load balancer)
354
+ const forwarded = req.headers['x-forwarded-for'];
355
+ if (forwarded) {
356
+ return forwarded.split(',')[0].trim();
357
+ }
358
+
359
+ // Check for real IP header
360
+ if (req.headers['x-real-ip']) {
361
+ return req.headers['x-real-ip'];
362
+ }
363
+
364
+ // Fall back to connection remote address
365
+ return req.connection.remoteAddress || req.socket.remoteAddress || 'unknown';
366
+ }
367
+
368
+ /**
369
+ * Get query parameter from URL
370
+ */
371
+ _getQueryParam(url, param) {
372
+ const match = url.match(new RegExp(`[?&]${param}=([^&]*)`));
373
+ return match ? decodeURIComponent(match[1]) : null;
374
+ }
375
+
376
+ /**
377
+ * Cleanup expired tokens and rate limit records
378
+ */
379
+ _startCleanup() {
380
+ setInterval(() => {
381
+ const now = Date.now();
382
+
383
+ // Cleanup expired CSRF tokens
384
+ for (const [token, data] of csrfTokenStore.entries()) {
385
+ if (now > data.expiry) {
386
+ csrfTokenStore.delete(token);
387
+ }
388
+ }
389
+
390
+ // Cleanup old rate limit records
391
+ for (const [identifier, record] of rateLimitStore.entries()) {
392
+ const windowStart = now - this.rateLimitWindow;
393
+ record.requests = record.requests.filter(timestamp => timestamp > windowStart);
394
+
395
+ // Remove empty records
396
+ if (record.requests.length === 0 && !record.blocked) {
397
+ rateLimitStore.delete(identifier);
398
+ }
399
+
400
+ // Unblock if expiry passed
401
+ if (record.blocked && now > record.blockExpiry) {
402
+ record.blocked = false;
403
+ record.requests = [];
404
+ }
405
+ }
406
+ }, 60000); // Run every minute
407
+ }
408
+
409
+ /**
410
+ * Clear rate limit for identifier (useful for testing)
411
+ */
412
+ clearRateLimit(identifier) {
413
+ rateLimitStore.delete(identifier);
414
+ }
415
+
416
+ /**
417
+ * Get rate limit status for identifier
418
+ */
419
+ getRateLimitStatus(identifier) {
420
+ const record = rateLimitStore.get(identifier);
421
+ if (!record) {
422
+ return {
423
+ requests: 0,
424
+ remaining: this.rateLimitMax,
425
+ blocked: false
426
+ };
427
+ }
428
+
429
+ const now = Date.now();
430
+ const windowStart = now - this.rateLimitWindow;
431
+ const recentRequests = record.requests.filter(timestamp => timestamp > windowStart);
432
+
433
+ return {
434
+ requests: recentRequests.length,
435
+ remaining: Math.max(0, this.rateLimitMax - recentRequests.length),
436
+ blocked: record.blocked && now < record.blockExpiry,
437
+ blockExpiry: record.blocked ? record.blockExpiry : null
438
+ };
439
+ }
440
+ }
441
+
442
+ // Create singleton instance
443
+ const security = new SecurityMiddleware();
444
+
445
+ /**
446
+ * Factory functions for middleware
447
+ */
448
+
449
+ function securityHeaders() {
450
+ return (req, res, next) => security.securityHeadersMiddleware(req, res, next);
451
+ }
452
+
453
+ function cors(options = {}) {
454
+ const instance = new SecurityMiddleware({ ...options, headers: false, csrf: false, rateLimit: false });
455
+ return (req, res, next) => instance.corsMiddleware(req, res, next);
456
+ }
457
+
458
+ function rateLimit(options = {}) {
459
+ const instance = new SecurityMiddleware({ ...options, headers: false, csrf: false, cors: false });
460
+ return (req, res, next) => instance.rateLimitMiddleware(req, res, next);
461
+ }
462
+
463
+ function csrf(options = {}) {
464
+ const instance = new SecurityMiddleware({ ...options, headers: false, cors: false, rateLimit: false });
465
+ return (req, res, next) => instance.csrfMiddleware(req, res, next);
466
+ }
467
+
468
+ function generateCSRFToken(sessionId) {
469
+ return security.generateCSRFToken(sessionId);
470
+ }
471
+
472
+ function validateCSRFToken(token) {
473
+ return security.validateCSRFToken(token);
474
+ }
475
+
476
+ module.exports = {
477
+ SecurityMiddleware,
478
+ security,
479
+ securityHeaders,
480
+ cors,
481
+ rateLimit,
482
+ csrf,
483
+ generateCSRFToken,
484
+ validateCSRFToken,
485
+ SECURITY_HEADERS
486
+ };