81 lines
2.8 KiB
JavaScript
81 lines
2.8 KiB
JavaScript
/**
|
|
* A single HTTP header, in the shape Next.js next.config headers() expects.
|
|
*/ /**
|
|
* Builds the generic, project-independent security headers every site should
|
|
* send: HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy,
|
|
* Permissions-Policy. These are identical across projects, so the plugin owns
|
|
* the boilerplate; the client spreads the result into next.config's headers().
|
|
*
|
|
* Content-Security-Policy is deliberately excluded: a useful CSP enumerates the
|
|
* exact domains a project loads from (its CDN, analytics, embeds), so it can't
|
|
* be generic without being either too loose (useless) or too strict (breaks the
|
|
* site). Add your project's CSP via `additional`.
|
|
*
|
|
* @example
|
|
* // next.config.ts
|
|
* import { buildSecurityHeaders } from '@intecion/ipal-kit'
|
|
* const securityHeaders = buildSecurityHeaders({
|
|
* hsts: process.env.NODE_ENV === 'production', // off in dev over http
|
|
* additional: [
|
|
* { key: 'Content-Security-Policy', value: "default-src 'self'; ..." },
|
|
* ],
|
|
* })
|
|
* const nextConfig = {
|
|
* async headers() {
|
|
* return [{ source: '/:path*', headers: securityHeaders }]
|
|
* },
|
|
* }
|
|
*/ export function buildSecurityHeaders(args = {}) {
|
|
const { additional = [], frameOptions = 'DENY', hsts = true, hstsIncludeSubDomains = true, hstsMaxAge = 63072000, hstsPreload = false, permissionsPolicy = 'camera=(), microphone=(), geolocation=()', referrerPolicy = 'strict-origin-when-cross-origin' } = args;
|
|
const headers = [];
|
|
if (hsts) {
|
|
const parts = [
|
|
`max-age=${hstsMaxAge}`
|
|
];
|
|
if (hstsIncludeSubDomains) {
|
|
parts.push('includeSubDomains');
|
|
}
|
|
if (hstsPreload) {
|
|
parts.push('preload');
|
|
}
|
|
headers.push({
|
|
key: 'Strict-Transport-Security',
|
|
value: parts.join('; ')
|
|
});
|
|
}
|
|
if (frameOptions) {
|
|
headers.push({
|
|
key: 'X-Frame-Options',
|
|
value: frameOptions
|
|
});
|
|
}
|
|
// Prevents MIME-type sniffing — always safe, no project specifics.
|
|
headers.push({
|
|
key: 'X-Content-Type-Options',
|
|
value: 'nosniff'
|
|
});
|
|
if (referrerPolicy) {
|
|
headers.push({
|
|
key: 'Referrer-Policy',
|
|
value: referrerPolicy
|
|
});
|
|
}
|
|
if (permissionsPolicy) {
|
|
headers.push({
|
|
key: 'Permissions-Policy',
|
|
value: permissionsPolicy
|
|
});
|
|
}
|
|
// Merge additional: same-key entries override the defaults above.
|
|
for (const extra of additional){
|
|
const i = headers.findIndex((h)=>h.key.toLowerCase() === extra.key.toLowerCase());
|
|
if (i >= 0) {
|
|
headers[i] = extra;
|
|
} else {
|
|
headers.push(extra);
|
|
}
|
|
}
|
|
return headers;
|
|
}
|
|
|
|
//# sourceMappingURL=buildSecurityHeaders.js.map
|