@appwarden/middleware
Version:
Instantly disable all user interaction with your app deployed on Cloudflare or Vercel
92 lines (89 loc) • 3.09 kB
JavaScript
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
};