From 8378376ac082a9cb690fc801a75669eb562d4e12 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Sat, 5 Oct 2024 18:58:05 +0200 Subject: [PATCH] unignore /lib --- .gitignore | 2 +- src/lib/csp.ts | 65 +++++++++++++++++++++++++++++++++++ src/lib/hsts.ts | 15 ++++++++ src/lib/permissions-policy.ts | 20 +++++++++++ 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/lib/csp.ts create mode 100644 src/lib/hsts.ts create mode 100644 src/lib/permissions-policy.ts diff --git a/.gitignore b/.gitignore index a8bff3b..ef229ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ coverage/ -lib/ +dist/ node_modules/ diff --git a/src/lib/csp.ts b/src/lib/csp.ts new file mode 100644 index 0000000..605dca6 --- /dev/null +++ b/src/lib/csp.ts @@ -0,0 +1,65 @@ +import { + getCSP, + nonce, + CSPDirectives, +} from "csp-header"; +import { type CSP, type CSPHeaderConfig } from "../types.js"; +import { DEV_DEFAULT_CSP, PROD_DEFAULT_CSP } from "../defaults.js"; + +const cspNonceDirectives = [ + "script-src", + "style-src", + "img-src", + "font-src", + "media-src", + "object-src", + "default-src", +] as const; + +const DEFAULT_CSP: CSPHeaderConfig = { + prod: { + withNonce: true, + value: PROD_DEFAULT_CSP, + cspBlock: false, + cspReportOnly: true, + }, + dev: { + withNonce: true, + value: DEV_DEFAULT_CSP, + cspBlock: true, + cspReportOnly: false, + }, +}; + +export const addNonceToDirectives = ( + userDefinedCSP: CSP["value"], + nonceString: string +): CSP["value"] => { + const csp: Partial = { + ...DEFAULT_CSP.prod.value, + ...userDefinedCSP, + }; + + cspNonceDirectives.forEach((directive) => { + if (csp[directive] && Array.isArray(csp[directive])) { + csp[directive].push(nonce(nonceString)); + } + }); + + return csp; +}; + +export function generateCSP(cspOptions: CSP["value"], nonceString?: string) { + const directives = nonceString ? addNonceToDirectives(cspOptions, nonceString) : cspOptions; + + if (Object.prototype.hasOwnProperty.call(directives,"report-uri")) { + const reportUri = directives["report-uri"]; + delete directives["report-uri"]; + + return getCSP({ directives, reportUri }) as string; + } else { + return getCSP({ + directives, + }) as string; + } +} diff --git a/src/lib/hsts.ts b/src/lib/hsts.ts new file mode 100644 index 0000000..5719b70 --- /dev/null +++ b/src/lib/hsts.ts @@ -0,0 +1,15 @@ +interface Params { + includeSubDomains?: boolean; + preload?: boolean; + maxAge: number; +} + +export function hsts({ + includeSubDomains = true, + preload = true, + maxAge = 31536000 /* 1year */, +}: Params) { + return `max-age=${String(maxAge)};${includeSubDomains ? " includeSubDomains;" : ""}${ + preload ? " preload" : "" + }`; +} diff --git a/src/lib/permissions-policy.ts b/src/lib/permissions-policy.ts new file mode 100644 index 0000000..4d977f9 --- /dev/null +++ b/src/lib/permissions-policy.ts @@ -0,0 +1,20 @@ +interface HardwarePermissions { + camera?: string; + microphone?: string; + geolocation?: string; + payment?: string; +} + +export function permissionsPolicy(perms: HardwarePermissions) { + const headerValue: string[] = []; + + for (const [key, value] of Object.entries(perms)) { + if (typeof value === "string") { + headerValue.push(`${key}=${value}`); + } + } + + // headerValue = ["camera=()", "microphone=()"] + return headerValue.join(", "); + // "camera=(), microfone=()" +}