Includes: API (Express/TypeScript), Astro site, Python workers, document generators, FCC compliance tools, Canada CRTC formation, Ansible infrastructure, and deployment scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
38 lines
1.2 KiB
TypeScript
38 lines
1.2 KiB
TypeScript
import helmet from "helmet";
|
|
import type { Request, Response, NextFunction } from "express";
|
|
|
|
// Strict security headers via Helmet.
|
|
export const securityHeaders = helmet({
|
|
contentSecurityPolicy: {
|
|
directives: {
|
|
defaultSrc: ["'none'"],
|
|
frameAncestors: ["'none'"],
|
|
},
|
|
},
|
|
hsts: { maxAge: 31_536_000, includeSubDomains: true, preload: true },
|
|
frameguard: { action: "deny" },
|
|
noSniff: true,
|
|
referrerPolicy: { policy: "no-referrer" },
|
|
hidePoweredBy: true,
|
|
// This is a public API accessed cross-origin — must be cross-origin not same-origin
|
|
crossOriginResourcePolicy: { policy: "cross-origin" },
|
|
// Allow cross-origin opener for Stripe Identity redirect flows
|
|
crossOriginOpenerPolicy: { policy: "unsafe-none" },
|
|
});
|
|
|
|
// Attach normalised client IP to req (handles IPv6-mapped IPv4).
|
|
declare global {
|
|
namespace Express {
|
|
interface Request {
|
|
clientIp?: string;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function extractClientIp(req: Request, _res: Response, next: NextFunction): void {
|
|
let ip = (req.headers["x-forwarded-for"] as string)?.split(",")[0]?.trim() || req.ip || "";
|
|
// Normalise ::ffff:127.0.0.1 → 127.0.0.1
|
|
if (ip.startsWith("::ffff:")) ip = ip.slice(7);
|
|
req.clientIp = ip;
|
|
next();
|
|
}
|