security headers, notifications, GDPR consent, masked fields
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* A single HTTP header, in the shape Next.js next.config headers() expects.
|
||||
*/
|
||||
export type SecurityHeader = { key: string; value: string }
|
||||
|
||||
export type BuildSecurityHeadersArgs = {
|
||||
/**
|
||||
* Extra headers to append or override. Same-key entries replace the default,
|
||||
* so you can e.g. add your project's Content-Security-Policy here — CSP is
|
||||
* intentionally NOT a default because it depends on the project's own
|
||||
* domains (scripts, images, fonts, analytics). Keep CSP in your project.
|
||||
*/
|
||||
additional?: SecurityHeader[]
|
||||
/**
|
||||
* X-Frame-Options value. 'DENY' (default) blocks all framing; 'SAMEORIGIN'
|
||||
* allows same-origin framing. Note: CSP frame-ancestors supersedes this in
|
||||
* modern browsers, but X-Frame-Options is kept for older ones. Set to null
|
||||
* to omit (e.g. if you set frame-ancestors in your project CSP).
|
||||
*/
|
||||
frameOptions?: 'DENY' | 'SAMEORIGIN' | null
|
||||
/**
|
||||
* Enable HSTS (Strict-Transport-Security). Only takes effect over HTTPS, and
|
||||
* tells browsers to force HTTPS for `maxAge` seconds. Default true. Turn OFF
|
||||
* in local/dev over plain HTTP, or you may lock the browser to https on
|
||||
* localhost. Set the env guard in your next.config (see docs).
|
||||
*/
|
||||
hsts?: boolean
|
||||
/** Add includeSubDomains to HSTS. Default true. */
|
||||
hstsIncludeSubDomains?: boolean
|
||||
/** HSTS max-age in seconds. Default 63072000 (2 years), the common baseline. */
|
||||
hstsMaxAge?: number
|
||||
/** Add preload to HSTS (only if you'll submit to the preload list). Default false. */
|
||||
hstsPreload?: boolean
|
||||
/**
|
||||
* Permissions-Policy. Default disables camera, microphone, geolocation. Pass
|
||||
* your own string to override, or null to omit.
|
||||
*/
|
||||
permissionsPolicy?: null | string
|
||||
/** Referrer-Policy. Default 'strict-origin-when-cross-origin' (browser default, explicit). */
|
||||
referrerPolicy?: null | string
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: BuildSecurityHeadersArgs = {}): SecurityHeader[] {
|
||||
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: SecurityHeader[] = []
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user