security headers, notifications, GDPR consent, masked fields
This commit is contained in:
+70
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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 declare function buildSecurityHeaders(args?: BuildSecurityHeadersArgs): SecurityHeader[];
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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
|
||||
File diff suppressed because one or more lines are too long
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
export { buildSecurityHeaders } from './buildSecurityHeaders.js';
|
||||
export type { BuildSecurityHeadersArgs, SecurityHeader } from './buildSecurityHeaders.js';
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
export { buildSecurityHeaders } from './buildSecurityHeaders.js';
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/modules/security/index.ts"],"sourcesContent":["export { buildSecurityHeaders } from './buildSecurityHeaders.js'\nexport type { BuildSecurityHeadersArgs, SecurityHeader } from './buildSecurityHeaders.js'\n"],"names":["buildSecurityHeaders"],"mappings":"AAAA,SAASA,oBAAoB,QAAQ,4BAA2B"}
|
||||
Reference in New Issue
Block a user