import * as Express from 'express'; import { DyFM_EnvironmentFlag } from '@futdevpro/fsm-dynamo'; import { DyNTS_SecurityHeaders_Settings } from '../_models/interfaces/security-headers-settings.interface'; /** * A security response-headerek TISZTA (Express-mentes, unit-tesztelhető) resolvere * (BFR-MASTERPROMPTER-011 / SEC-R2-8). A `DyNTS_App.mountSecurityHeaders` ebből épít middleware-t. * * **HSTS AUTO-env-gate**: ha a `hsts` beállítás nem explicit, CSAK prod environment-en emittálunk * (a test-környezeti sticky-HSTS elkerülésére); explicit `hsts.enabled` felülbírálja mindkét irányba. */ export function DyNTS_resolveSecurityHeaders( settings: DyNTS_SecurityHeaders_Settings, environment: DyFM_EnvironmentFlag, ): Record { const headers: Record = {}; if (!settings.enabled) { return headers; } // Clickjacking-védelem — default SAMEORIGIN. const frameOptions: string | false = settings.frameOptions ?? 'SAMEORIGIN'; if (frameOptions) { headers['X-Frame-Options'] = frameOptions; } // MIME-sniffing tiltás — default nosniff. const contentTypeOptions: string | false = settings.contentTypeOptions ?? 'nosniff'; if (contentTypeOptions) { headers['X-Content-Type-Options'] = contentTypeOptions; } // Referrer-szivárgás korlátozás — default strict-origin-when-cross-origin. const referrerPolicy: string | false = settings.referrerPolicy ?? 'strict-origin-when-cross-origin'; if (referrerPolicy) { headers['Referrer-Policy'] = referrerPolicy; } // Permissions-Policy — opt-in (app-specifikus feature-készlet). if (settings.permissionsPolicy) { headers['Permissions-Policy'] = settings.permissionsPolicy; } // HSTS — AUTO-env-gate: default CSAK prod-on; explicit enabled felülbírál. if (settings.hsts !== false) { const hstsExplicitlyEnabled: boolean = settings.hsts?.enabled === true; const hstsExplicitlyDisabled: boolean = settings.hsts?.enabled === false; const hstsAutoOn: boolean = environment === DyFM_EnvironmentFlag.prod; if (!hstsExplicitlyDisabled && (hstsExplicitlyEnabled || hstsAutoOn)) { const maxAge: number = settings.hsts?.maxAgeSeconds ?? 15768000; const includeSubDomains: string = (settings.hsts?.includeSubDomains ?? true) ? '; includeSubDomains' : ''; const preload: string = settings.hsts?.preload ? '; preload' : ''; headers['Strict-Transport-Security'] = `max-age=${maxAge}${includeSubDomains}${preload}`; } } // CSP — opt-in string; a Report-Only header-név a fokozatos bevezetéshez. if (settings.csp) { const cspHeaderName: string = settings.cspReportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'; headers[cspHeaderName] = settings.csp; } // Tetszőleges további headerek (merge, felülírással). if (settings.additionalHeaders) { for (const [key, value] of Object.entries(settings.additionalHeaders)) { headers[key] = value; } } return headers; } /** * Express-middleware factory a resolvelt headerekből. A headereket boot-kor EGYSZER resolvoljuk * (a settings boot után nem változik), a middleware kérésenként csak setHeader-öl. */ export function DyNTS_createSecurityHeadersMiddleware( settings: DyNTS_SecurityHeaders_Settings, environment: DyFM_EnvironmentFlag, ): Express.Handler { const resolvedHeaders: Record = DyNTS_resolveSecurityHeaders(settings, environment); const headerEntries: [string, string][] = Object.entries(resolvedHeaders); return (req: Express.Request, res: Express.Response, next: Express.NextFunction): void => { for (const [key, value] of headerEntries) { res.setHeader(key, value); } next(); }; }