UNPKG

@appwarden/middleware

Version:

Instantly disable all user interaction with your app deployed on Cloudflare or Vercel

92 lines (89 loc) 3.09 kB
import { isHTMLResponse, makeCSPHeader } from "./chunk-Q7ZBW3WZ.js"; import { UseCSPInputSchema } from "./chunk-3OXAKJPA.js"; // src/middlewares/use-content-security-policy.ts function generateCspNonce() { const bytes = new Uint8Array(16); crypto.getRandomValues(bytes); return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join(""); } var AppendAttribute = (attribute, nonce) => ({ element: function(element) { element.setAttribute(attribute, nonce); } }); var useContentSecurityPolicy = (input) => { const parsedInput = UseCSPInputSchema.safeParse(input); if (!parsedInput.success) { throw parsedInput.error; } const config = parsedInput.data; return async (context, next) => { await next(); const { response } = context; if ( // if the csp is disabled !["enforced", "report-only"].includes(config.mode) ) { context.debug("CSP is disabled"); return; } if (response.headers.has("Content-Type") && !isHTMLResponse(response)) { return; } const cspNonce = generateCspNonce(); const [cspHeaderName, cspHeaderValue] = makeCSPHeader( cspNonce, config.directives, config.mode ); context.debug(`Applying CSP in ${config.mode} mode`); const method = context.request.method.toUpperCase(); const shouldSkipTransform = !response.body || response.status === 204 || response.status === 304 || method === "HEAD"; if (shouldSkipTransform) { context.debug( "Skipping HTMLRewriter transform for response without body or HEAD request" ); const nextResponse2 = new Response(null, response); nextResponse2.headers.set(cspHeaderName, cspHeaderValue); const originalContentType2 = response.headers.get("content-type"); if (originalContentType2) { if (/charset\s*=/i.test(originalContentType2)) { nextResponse2.headers.set("content-type", originalContentType2); } else { nextResponse2.headers.set( "content-type", `${originalContentType2}; charset=utf-8` ); } } else { nextResponse2.headers.set("content-type", "text/html; charset=utf-8"); } context.response = nextResponse2; return; } const nextResponse = new Response(response.clone().body, response); nextResponse.headers.set(cspHeaderName, cspHeaderValue); const originalContentType = response.headers.get("content-type"); if (originalContentType) { if (/charset\s*=/i.test(originalContentType)) { nextResponse.headers.set("content-type", originalContentType); } else { nextResponse.headers.set( "content-type", `${originalContentType}; charset=utf-8` ); } } else { nextResponse.headers.set("content-type", "text/html; charset=utf-8"); } context.response = new HTMLRewriter().on("style", AppendAttribute("nonce", cspNonce)).on("script", AppendAttribute("nonce", cspNonce)).transform(nextResponse); }; }; export { useContentSecurityPolicy };