BillTracker/middleware/securityHeaders.cts

48 lines
1.4 KiB
TypeScript
Raw Normal View History

2026-05-03 19:51:57 -05:00
'use strict';
import type { Req, Res, Next } from '../types/http';
2026-05-09 13:03:36 -05:00
const crypto = require('crypto');
/** Generates a secure nonce for CSP policy. Call once per request. */
function getCspNonce(req: Req): string {
2026-05-09 13:03:36 -05:00
if (!req.cspNonce) {
req.cspNonce = crypto.randomBytes(16).toString('base64');
}
return req.cspNonce;
}
2026-05-03 19:51:57 -05:00
/**
* Applies baseline security response headers on every request.
* (See original notes on why CSP uses no nonce index.html is static.)
2026-05-03 19:51:57 -05:00
*/
function securityHeaders(req: Req, res: Res, next: Next): void {
const cspPolicy =
2026-05-09 13:03:36 -05:00
`default-src 'self'; ` +
`script-src 'self'; ` +
`style-src 'self' 'unsafe-inline'; ` +
2026-05-09 13:03:36 -05:00
`img-src 'self' data:; ` +
`font-src 'self'; ` +
`connect-src 'self'; ` +
`frame-ancestors 'self'; ` +
`form-action 'self'; ` +
`base-uri 'self'; ` +
`object-src 'none';`;
res.setHeader('Content-Security-Policy', cspPolicy);
2026-05-03 19:51:57 -05:00
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('X-Permitted-Cross-Domain-Policies', 'none');
res.removeHeader('X-Powered-By');
// HSTS — only when explicitly configured to run over HTTPS.
2026-05-03 19:51:57 -05:00
if (process.env.HTTPS === 'true') {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
}
next();
}
2026-05-09 13:03:36 -05:00
module.exports = { securityHeaders, getCspNonce };