feat(security): implement rate limiting and helmet security headers
All checks were successful
Docker Cloud Build / Build & Publish Image (push) Successful in 11m11s
All checks were successful
Docker Cloud Build / Build & Publish Image (push) Successful in 11m11s
- Add express-rate-limit: login limiter (10 req/15m) and global limiter (100 req/m) - Add helmet: secure HTTP headers with custom CSP configuration - Remove manual header settings in favor of helmet
This commit is contained in:
parent
547360d0e3
commit
554130afbb
2 changed files with 47 additions and 17 deletions
|
|
@ -2,6 +2,8 @@ import bodyParser from "body-parser";
|
||||||
import compression from "compression";
|
import compression from "compression";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import fileUpload from "express-fileupload";
|
import fileUpload from "express-fileupload";
|
||||||
|
import helmet from "helmet";
|
||||||
|
import { rateLimit } from "express-rate-limit";
|
||||||
import { isDebugMode } from "./lib/config.js";
|
import { isDebugMode } from "./lib/config.js";
|
||||||
import cors from "./lib/express/cors.js";
|
import cors from "./lib/express/cors.js";
|
||||||
import jwt from "./lib/express/jwt.js";
|
import jwt from "./lib/express/jwt.js";
|
||||||
|
|
@ -36,25 +38,51 @@ if (isDebugMode()) {
|
||||||
// CORS for everything
|
// CORS for everything
|
||||||
app.use(cors);
|
app.use(cors);
|
||||||
|
|
||||||
// General security/cache related headers + server header
|
/**
|
||||||
app.use((_, res, next) => {
|
* Global Rate Limiter: 100 requests per minute per IP
|
||||||
let x_frame_options = "DENY";
|
*/
|
||||||
|
const globalLimiter = rateLimit({
|
||||||
if (typeof process.env.X_FRAME_OPTIONS !== "undefined" && process.env.X_FRAME_OPTIONS) {
|
windowMs: 1 * 60 * 1000, // 1 minute
|
||||||
x_frame_options = process.env.X_FRAME_OPTIONS;
|
max: 100,
|
||||||
}
|
standardHeaders: true,
|
||||||
|
legacyHeaders: false,
|
||||||
res.set({
|
message: { error: { message: "Too many requests, please try again later." } },
|
||||||
"X-XSS-Protection": "1; mode=block",
|
|
||||||
"X-Content-Type-Options": "nosniff",
|
|
||||||
"X-Frame-Options": x_frame_options,
|
|
||||||
"Cache-Control": "no-cache, no-store, max-age=0, must-revalidate",
|
|
||||||
Pragma: "no-cache",
|
|
||||||
Expires: 0,
|
|
||||||
});
|
|
||||||
next();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login Rate Limiter: 10 requests per 15 minutes per IP
|
||||||
|
*/
|
||||||
|
const loginLimiter = rateLimit({
|
||||||
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||||
|
max: 10,
|
||||||
|
standardHeaders: true,
|
||||||
|
legacyHeaders: false,
|
||||||
|
message: { error: { message: "Too many login attempts, please try again in 15 minutes." } },
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helmet Security Headers
|
||||||
|
*/
|
||||||
|
app.use(
|
||||||
|
helmet({
|
||||||
|
contentSecurityPolicy: {
|
||||||
|
directives: {
|
||||||
|
defaultSrc: ["'self'"],
|
||||||
|
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"], // unsafe-inline/eval required by some React libs
|
||||||
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||||
|
imgSrc: ["'self'", "data:", "https://www.gravatar.com"],
|
||||||
|
connectSrc: ["'self'"],
|
||||||
|
frameAncestors: [process.env.X_FRAME_OPTIONS === "ALLOWALL" ? "*" : "'none'"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
crossOriginResourcePolicy: { policy: "cross-origin" },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply rate limiting
|
||||||
|
app.use("/api/tokens", loginLimiter);
|
||||||
|
app.use("/api/", globalLimiter);
|
||||||
|
|
||||||
// Bypass JWT for public authenticated requests mapped by WireGuard IP
|
// Bypass JWT for public authenticated requests mapped by WireGuard IP
|
||||||
app.use("/wg-public", wgPublicRoutes);
|
app.use("/wg-public", wgPublicRoutes);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,9 @@
|
||||||
"compression": "^1.8.1",
|
"compression": "^1.8.1",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
"express-fileupload": "^1.5.2",
|
"express-fileupload": "^1.5.2",
|
||||||
|
"express-rate-limit": "^7.5.0",
|
||||||
"gravatar": "^1.8.2",
|
"gravatar": "^1.8.2",
|
||||||
|
"helmet": "^8.0.0",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"knex": "3.1.0",
|
"knex": "3.1.0",
|
||||||
"liquidjs": "10.24.0",
|
"liquidjs": "10.24.0",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue