UNPKG

@re-auth/http-adapters

Version:

HTTP protocol adapters for ReAuth - framework-agnostic integration with Express, Fastify, and Hono

215 lines (214 loc) 6.66 kB
// src/middleware/security.ts function createCorsMiddleware(config = {}) { const { origin = "*", credentials = false, allowedHeaders = ["Content-Type", "Authorization"], exposedHeaders = [], methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"], preflightContinue = false, optionsSuccessStatus = 204 } = config; return (req, res, next) => { if (typeof origin === "string") { res.header("Access-Control-Allow-Origin", origin); } else if (typeof origin === "boolean") { res.header("Access-Control-Allow-Origin", origin ? "*" : "false"); } else if (Array.isArray(origin)) { const requestOrigin = req.headers.origin; if (requestOrigin && origin.includes(requestOrigin)) { res.header("Access-Control-Allow-Origin", requestOrigin); } } else if (typeof origin === "function") { const requestOrigin = req.headers.origin; origin(requestOrigin, (err, allow) => { if (err) { return next(err); } if (allow) { res.header("Access-Control-Allow-Origin", requestOrigin || "*"); } }); } if (credentials) { res.header("Access-Control-Allow-Credentials", "true"); } res.header("Access-Control-Allow-Methods", methods.join(", ")); if (allowedHeaders.length > 0) { res.header("Access-Control-Allow-Headers", allowedHeaders.join(", ")); } if (exposedHeaders.length > 0) { res.header("Access-Control-Expose-Headers", exposedHeaders.join(", ")); } if (req.method === "OPTIONS") { if (!preflightContinue) { res.status(optionsSuccessStatus).end(); return; } } next(); }; } function createRateLimitMiddleware(config = {}) { const { windowMs = 15 * 60 * 1e3, // 15 minutes max = 100, message = "Too many requests, please try again later.", standardHeaders = true, legacyHeaders = false, skipSuccessfulRequests = false, skipFailedRequests = false, keyGenerator = (req) => req.ip || "anonymous" } = config; const requests = /* @__PURE__ */ new Map(); return (req, res, next) => { const key = keyGenerator(req); const now = Date.now(); const resetTime = now + windowMs; for (const [k, v] of requests.entries()) { if (v.resetTime <= now) { requests.delete(k); } } let requestInfo = requests.get(key); if (!requestInfo || requestInfo.resetTime <= now) { requestInfo = { count: 0, resetTime }; requests.set(key, requestInfo); } if (requestInfo.count >= max) { if (standardHeaders) { res.header("X-RateLimit-Limit", max.toString()); res.header("X-RateLimit-Remaining", "0"); res.header( "X-RateLimit-Reset", new Date(requestInfo.resetTime).toISOString() ); } if (legacyHeaders) { res.header("X-Rate-Limit-Limit", max.toString()); res.header("X-Rate-Limit-Remaining", "0"); res.header( "X-Rate-Limit-Reset", new Date(requestInfo.resetTime).toISOString() ); } return res.status(429).json({ success: false, error: { code: "RATE_LIMIT_EXCEEDED", message }, meta: { timestamp: (/* @__PURE__ */ new Date()).toISOString() } }); } requestInfo.count++; if (standardHeaders) { res.header("X-RateLimit-Limit", max.toString()); res.header("X-RateLimit-Remaining", (max - requestInfo.count).toString()); res.header( "X-RateLimit-Reset", new Date(requestInfo.resetTime).toISOString() ); } if (legacyHeaders) { res.header("X-Rate-Limit-Limit", max.toString()); res.header( "X-Rate-Limit-Remaining", (max - requestInfo.count).toString() ); res.header( "X-Rate-Limit-Reset", new Date(requestInfo.resetTime).toISOString() ); } next(); }; } function createSecurityMiddleware(config = {}) { const { helmet = true } = config; return (req, res, next) => { if (helmet) { res.header("X-Content-Type-Options", "nosniff"); res.header("X-Frame-Options", "DENY"); res.header("X-XSS-Protection", "1; mode=block"); res.header("Referrer-Policy", "strict-origin-when-cross-origin"); res.header( "Permissions-Policy", "geolocation=(), microphone=(), camera=()" ); if (req.secure || req.headers["x-forwarded-proto"] === "https") { res.header( "Strict-Transport-Security", "max-age=31536000; includeSubDomains" ); } } next(); }; } function createValidationMiddleware(config = {}) { const { validateInput = true, maxPayloadSize = 1024 * 1024, // 1MB allowedFields = [], sanitizeFields = ["email", "username", "name"] } = config; return (req, res, next) => { if (!validateInput) { return next(); } const contentLength = parseInt(req.headers["content-length"] || "0", 10); if (contentLength > maxPayloadSize) { return res.status(413).json({ success: false, error: { code: "PAYLOAD_TOO_LARGE", message: `Payload too large. Maximum size is ${maxPayloadSize} bytes.` }, meta: { timestamp: (/* @__PURE__ */ new Date()).toISOString() } }); } if (req.body && typeof req.body === "object") { for (const field of sanitizeFields) { if (req.body[field] && typeof req.body[field] === "string") { req.body[field] = req.body[field].replace(/<[^>]*>/g, "").trim(); } } if (allowedFields.length > 0) { const filtered = {}; for (const field of allowedFields) { if (req.body[field] !== void 0) { filtered[field] = req.body[field]; } } req.body = filtered; } } next(); }; } function createSecurityMiddlewares(config) { const middlewares = []; if (config.security) { middlewares.push(createSecurityMiddleware(config.security)); } if (config.cors) { middlewares.push(createCorsMiddleware(config.cors)); } if (config.rateLimit) { middlewares.push(createRateLimitMiddleware(config.rateLimit)); } if (config.validation) { middlewares.push(createValidationMiddleware(config.validation)); } return middlewares; } export { createCorsMiddleware, createRateLimitMiddleware, createSecurityMiddleware, createSecurityMiddlewares, createValidationMiddleware }; //# sourceMappingURL=security.js.map //# sourceMappingURL=security.js.map