UNPKG

fortify2-js

Version:

MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.

189 lines (185 loc) 8.49 kB
'use strict'; var crypto = require('../core/crypto.js'); require('../core/hash/hash-core.js'); require('../core/hash/hash-types.js'); require('crypto'); var randomCore = require('../core/random/random-core.js'); require('../core/random/random-types.js'); require('../core/random/random-sources.js'); require('nehonix-uri-processor'); require('../utils/memory/index.js'); require('../types.js'); require('argon2'); require('../algorithms/hash-algorithms.js'); require('../core/password/index.js'); function middleware(options = {}) { // Set default options with documentation const opts = { csrfProtection: options.csrfProtection || false, // disable CSRF protection secureHeaders: options.secureHeaders !== false, // Secure HTTP headers rateLimit: options.rateLimit !== false, // Enable rate limiting maxRequestsPerMinute: options.maxRequestsPerMinute || 100, tokenSecret: options.tokenSecret || crypto.FortifyJS.generateSecureToken({ length: 32, entropy: "high" }), cookieName: options.cookieName || "nehonix_fortify_csrf", headerName: options.headerName || "X-FORTIFY_CSRF-Token", excludePaths: options.excludePaths || ["/api/health", "/api/status"], logRequests: options.logRequests !== false, logger: options.logger || console, contentSecurityPolicy: options.contentSecurityPolicy || "default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'", customHeaders: options.customHeaders || {}, // Allow merging custom headers onRateLimit: options.onRateLimit, // Optional callback for rate limit onCSRFError: options.onCSRFError, // Optional callback for CSRF error onError: options.onError, // Optional error handler metricsHook: options.metricsHook, // Optional metrics/observability }; // Use Map for IP tracking to avoid prototype pollution const ipRequests = new Map(); // Ban tracking for repeated abuse const bannedIPs = new Map(); // Create a tamper-evident logger if logging is enabled const secureLogger = opts.logRequests ? crypto.FortifyJS.createTamperEvidentLogger(opts.tokenSecret, "nehonix_fortify_request_log") : null; // Middleware function return async (req, res, next) => { try { // Get client IP const clientIp = req.headers["x-forwarded-for"] || req.connection.remoteAddress || req.socket.remoteAddress || req.ip || "0.0.0.0"; // Log the request if enabled if (secureLogger) { secureLogger.info("Request received", { method: req.method, path: req.path, ip: clientIp, userAgent: req.headers["user-agent"], timestamp: Date.now(), }); } // Check for banned IPs (optional, can be extended) const banUntil = bannedIPs.get(clientIp); if (banUntil && Date.now() < banUntil) { if (secureLogger) { secureLogger.warning("Banned IP tried to access", { ip: clientIp, }); } res.status(429).json({ error: "Too many requests (banned)", retryAfter: Math.ceil((banUntil - Date.now()) / 1000), }); return; } // Apply rate limiting if enabled if (opts.rateLimit) { const now = Date.now(); // Initialize or reset counter if needed const ipEntry = ipRequests.get(clientIp); if (!ipEntry || ipEntry.resetTime < now) { ipRequests.set(clientIp, { count: 0, resetTime: now + 60000, // 1 minute }); } const entry = ipRequests.get(clientIp); entry.count++; ipRequests.set(clientIp, entry); // Check if limit exceeded if (entry.count > opts.maxRequestsPerMinute) { if (secureLogger) { secureLogger.warning("Rate limit exceeded", { ip: clientIp, count: entry.count, limit: opts.maxRequestsPerMinute, }); } res.status(429).json({ error: "Too many requests", retryAfter: Math.ceil((entry.resetTime - now) / 1000), }); return; } } // Apply secure headers if enabled if (opts.secureHeaders) { // Content Security Policy res.setHeader("Content-Security-Policy", options.contentSecurityPolicy || "default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'"); // Prevent MIME type sniffing res.setHeader("X-Content-Type-Options", "nosniff"); // Clickjacking protection res.setHeader("X-Frame-Options", "DENY"); // XSS protection res.setHeader("X-XSS-Protection", "1; mode=block"); // Strict Transport Security res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload"); // Referrer Policy res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin"); // Permissions Policy res.setHeader("Permissions-Policy", "geolocation=(), camera=(), microphone=()"); } // Apply CSRF protection if enabled if (opts.csrfProtection && !opts.excludePaths.includes(req.path)) { // Skip CSRF check for GET, HEAD, OPTIONS requests const safeMethod = /^(GET|HEAD|OPTIONS)$/i.test(req.method); if (!safeMethod) { // Get token from request const csrfToken = req.headers[opts.headerName.toLowerCase()] || req.body?._csrf || req.query?._csrf; // Get token from cookie const cookieToken = req.cookies?.[opts.cookieName]; // Verify tokens match using constant-time comparison if (!csrfToken || !cookieToken || !crypto.FortifyJS.constantTimeEqual(csrfToken, cookieToken)) { if (secureLogger) { secureLogger.warning("CSRF token validation failed", { path: req.path, ip: clientIp, hasToken: !!csrfToken, hasCookie: !!cookieToken, }); } res.status(403).json({ error: "CSRF token validation failed", }); return; } } // Generate a new token for the response const newToken = randomCore.SecureRandom.getRandomBytes(32).toString(); // Set the token in a cookie res.cookie(opts.cookieName, newToken, { httpOnly: true, secure: req.secure || req.headers["x-forwarded-proto"] === "https", sameSite: "strict", path: "/", }); // Make the token available to the application res.locals.csrfToken = newToken; } // Continue to the next middleware next(); } catch (error) { // Log the error if (secureLogger) { secureLogger.error("Middleware error", { message: error.message, stack: error.stack, }); } // Pass to next error handler next(error); } }; } exports.middleware = middleware; //# sourceMappingURL=express.middleware.js.map