@nosecone/next
Version:
Protect your Next.js application with secure headers
96 lines (95 loc) • 3.66 kB
JavaScript
import nosecone, { defaults as defaults$1, withVercelToolbar } from "nosecone";
//#region src/index.ts
/**
* Nosecone Next.js defaults.
*
* The type spells out the concrete directive shape rather than the wider
* `Options`: consumers extend the defaults by spreading
* `defaults.contentSecurityPolicy.directives`, so every directive must stay
* a concrete readonly array, as it was before `isolatedDeclarations` required
* an explicit annotation here. Everything is derived from `typeof
* baseDefaults` so the type cannot drift from the `nosecone` package.
*/
const defaults = {
...defaults$1,
contentSecurityPolicy: { directives: {
...defaults$1.contentSecurityPolicy.directives,
scriptSrc: [...defaults$1.contentSecurityPolicy.directives.scriptSrc, ...nextScriptSrc()],
styleSrc: [...defaults$1.contentSecurityPolicy.directives.styleSrc, ...nextStyleSrc()]
} }
};
/**
* Create security headers.
*
* @deprecated
* Use the named export `nosecone` instead.
*/
var src_default = nosecone;
function createNonce() {
return `'nonce-${btoa(crypto.randomUUID())}'`;
}
function nextScriptSrc() {
return process.env.NODE_ENV === "development" ? [createNonce, "'unsafe-eval'"] : [createNonce];
}
const nonceSourceRegex = /^'nonce-([A-Za-z0-9+/_-]+={0,2})'$/;
/**
* Extract the nonce from a `Content-Security-Policy` header value.
*
* @param value
* Value of the `Content-Security-Policy` header.
* @returns
* The nonce, or `undefined` if there is no nonce in the header.
*/
function nonceFromContentSecurityPolicy(value) {
const directives = value.split(";").map((directive) => directive.trim());
const directive = directives.find((directive) => directive.startsWith("script-src")) ?? directives.find((directive) => directive.startsWith("default-src"));
if (!directive) return;
for (const source of directive.split(/\s+/).slice(1)) {
const match = source.trim().match(nonceSourceRegex);
if (match) return match[1];
}
}
/**
* Get the `Content-Security-Policy` nonce for the current request.
*
* Nosecone generates a unique nonce for every request when the
* `Content-Security-Policy` is configured with our defaults. Next.js applies
* that nonce to the scripts it renders automatically, but third-party scripts
* that do not use the Next.js `<Script>` component need the value passed in
* manually. For example, PostHog requires the nonce in its `init` call.
*
* Call this from a Server Component, Route Handler, or anywhere else
* `next/headers` is available. The page must be dynamically rendered for the
* nonce to be available — see the example in the `README`.
*
* @returns
* The nonce for the current request, or `undefined` if the
* `Content-Security-Policy` is disabled or does not contain a nonce.
*/
async function nonce() {
const { headers } = await import("next/headers.js");
const headerList = await headers();
const contentSecurityPolicy = headerList.get("content-security-policy") ?? headerList.get("content-security-policy-report-only");
if (typeof contentSecurityPolicy !== "string") return;
return nonceFromContentSecurityPolicy(contentSecurityPolicy);
}
function nextStyleSrc() {
return ["'unsafe-inline'"];
}
/**
* Create Next.js middleware that sets secure headers on every request.
*
* @param options
* Configuration to provide to Nosecone.
* @returns
* Next.js middleware that sets secure headers.
*/
function createMiddleware(options = defaults) {
return async () => {
const headers = nosecone(options);
headers.set("x-middleware-next", "1");
return new Response(null, { headers });
};
}
//#endregion
export { createMiddleware, src_default as default, defaults, nonce, nosecone, withVercelToolbar };