fix express proxy headers
This commit is contained in:
@@ -97,7 +97,6 @@ describe("Issue #38: CSRF with trust proxy settings", () => {
|
|||||||
const clientId1 = `${externalProxyIp1}:${userAgent}`;
|
const clientId1 = `${externalProxyIp1}:${userAgent}`;
|
||||||
const token = createCsrfToken(clientId1);
|
const token = createCsrfToken(clientId1);
|
||||||
|
|
||||||
console.log("\n📝 Step 1: Client fetches CSRF token");
|
|
||||||
console.log(
|
console.log(
|
||||||
" X-Forwarded-For:",
|
" X-Forwarded-For:",
|
||||||
`${clientIp1}, ${externalProxyIp1}, 172.17.0.3`,
|
`${clientIp1}, ${externalProxyIp1}, 172.17.0.3`,
|
||||||
@@ -111,7 +110,6 @@ describe("Issue #38: CSRF with trust proxy settings", () => {
|
|||||||
|
|
||||||
const clientId2 = `${externalProxyIp2}:${userAgent}`;
|
const clientId2 = `${externalProxyIp2}:${userAgent}`;
|
||||||
|
|
||||||
console.log("\n📤 Step 2: Client creates drawing with token");
|
|
||||||
console.log(
|
console.log(
|
||||||
" X-Forwarded-For:",
|
" X-Forwarded-For:",
|
||||||
`${clientIp1}, ${externalProxyIp2}, 172.17.0.3`,
|
`${clientIp1}, ${externalProxyIp2}, 172.17.0.3`,
|
||||||
|
|||||||
+33
-5
@@ -132,8 +132,21 @@ const app = express();
|
|||||||
|
|
||||||
// Trust proxy headers (X-Forwarded-For, X-Real-IP) from nginx
|
// Trust proxy headers (X-Forwarded-For, X-Real-IP) from nginx
|
||||||
// Required for correct client IP detection when running behind a reverse proxy
|
// Required for correct client IP detection when running behind a reverse proxy
|
||||||
// This fixes CSRF token validation failures in Docker/K8s environments
|
// Fix for issue #38: Use 'true' to handle multiple proxy layers (e.g., Traefik, Synology NAS)
|
||||||
app.set("trust proxy", 1);
|
// This ensures Express extracts the real client IP from the leftmost X-Forwarded-For value
|
||||||
|
const trustProxyConfig = process.env.TRUST_PROXY || "true";
|
||||||
|
const trustProxyValue = trustProxyConfig === "true"
|
||||||
|
? true
|
||||||
|
: trustProxyConfig === "false"
|
||||||
|
? false
|
||||||
|
: parseInt(trustProxyConfig, 10) || 1;
|
||||||
|
app.set("trust proxy", trustProxyValue);
|
||||||
|
|
||||||
|
if (trustProxyValue === true) {
|
||||||
|
console.log("[config] trust proxy: enabled (handles multiple proxy layers)");
|
||||||
|
} else {
|
||||||
|
console.log(`[config] trust proxy: ${trustProxyValue}`);
|
||||||
|
}
|
||||||
|
|
||||||
const httpServer = createServer(app);
|
const httpServer = createServer(app);
|
||||||
const io = new Server(httpServer, {
|
const io = new Server(httpServer, {
|
||||||
@@ -330,9 +343,24 @@ app.use((req, res, next) => {
|
|||||||
const getClientId = (req: express.Request): string => {
|
const getClientId = (req: express.Request): string => {
|
||||||
const ip = req.ip || req.connection.remoteAddress || "unknown";
|
const ip = req.ip || req.connection.remoteAddress || "unknown";
|
||||||
const userAgent = req.headers["user-agent"] || "unknown";
|
const userAgent = req.headers["user-agent"] || "unknown";
|
||||||
// Create a simple hash for client identification
|
const clientId = `${ip}:${userAgent}`.slice(0, 256);
|
||||||
// In production, you might use a session ID instead
|
|
||||||
return `${ip}:${userAgent}`.slice(0, 256);
|
// Debug logging for CSRF troubleshooting (issue #38)
|
||||||
|
if (process.env.DEBUG_CSRF === "true") {
|
||||||
|
console.log("[CSRF DEBUG] getClientId", {
|
||||||
|
method: req.method,
|
||||||
|
path: req.path,
|
||||||
|
ip,
|
||||||
|
remoteAddress: req.connection.remoteAddress,
|
||||||
|
"x-forwarded-for": req.headers["x-forwarded-for"],
|
||||||
|
"x-real-ip": req.headers["x-real-ip"],
|
||||||
|
userAgent: userAgent.slice(0, 100),
|
||||||
|
clientIdPreview: clientId.slice(0, 60) + "...",
|
||||||
|
trustProxySetting: req.app.get("trust proxy"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientId;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Rate limiter specifically for CSRF token generation to prevent store exhaustion
|
// Rate limiter specifically for CSRF token generation to prevent store exhaustion
|
||||||
|
|||||||
@@ -569,8 +569,12 @@ const getCsrfSecret = (): Buffer => {
|
|||||||
cachedCsrfSecret = crypto.randomBytes(32);
|
cachedCsrfSecret = crypto.randomBytes(32);
|
||||||
const envLabel = process.env.NODE_ENV ? ` (${process.env.NODE_ENV})` : "";
|
const envLabel = process.env.NODE_ENV ? ` (${process.env.NODE_ENV})` : "";
|
||||||
console.warn(
|
console.warn(
|
||||||
`[security] CSRF_SECRET is not set${envLabel}. Using an ephemeral per-process secret. ` +
|
`[SECURITY WARNING] CSRF_SECRET is not set${envLabel}.\n` +
|
||||||
"For horizontal scaling (k8s), set CSRF_SECRET to the same value on all instances."
|
`Using an ephemeral per-process secret.\n` +
|
||||||
|
` - Tokens will expire on container restart\n` +
|
||||||
|
` - Horizontal scaling (k8s) will NOT work\n` +
|
||||||
|
` - Generate a secret: openssl rand -base64 32\n` +
|
||||||
|
` - Set environment variable: CSRF_SECRET=<generated-secret>`
|
||||||
);
|
);
|
||||||
return cachedCsrfSecret;
|
return cachedCsrfSecret;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user