@re-auth/http-adapters
Version:
HTTP protocol adapters for ReAuth - framework-agnostic integration with Express, Fastify, and Hono
221 lines (219 loc) • 6.84 kB
JavaScript
;
// 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;
}
exports.createCorsMiddleware = createCorsMiddleware;
exports.createRateLimitMiddleware = createRateLimitMiddleware;
exports.createSecurityMiddleware = createSecurityMiddleware;
exports.createSecurityMiddlewares = createSecurityMiddlewares;
exports.createValidationMiddleware = createValidationMiddleware;
//# sourceMappingURL=security.cjs.map
//# sourceMappingURL=security.cjs.map