nuxt-security
Version:
🛡️ Security Module for Nuxt based on HTTP Headers and Middleware
56 lines (55 loc) • 1.91 kB
JavaScript
import { defineNitroPlugin } from "#imports";
import { resolveSecurityRules } from "../context/index.js";
import { generateRandomNonce } from "../../../utils/crypto";
const LINK_RE = /<link([^>]*?>)/gi;
const NONCE_RE = /nonce="[^"]+"/i;
const SCRIPT_RE = /<script([^>]*?>)/gi;
const STYLE_RE = /<style([^>]*?>)/gi;
export default defineNitroPlugin((nitroApp) => {
if (import.meta.prerender) {
return;
}
nitroApp.hooks.hook("request", (event) => {
if (event.context.security?.nonce) {
return;
}
const rules = resolveSecurityRules(event);
if (rules.enabled && rules.nonce && !import.meta.prerender) {
const nonce = generateRandomNonce();
event.context.security.nonce = nonce;
}
});
nitroApp.hooks.hook("render:html", (html, { event }) => {
const rules = resolveSecurityRules(event);
if (!rules.enabled || !rules.headers || !rules.headers.contentSecurityPolicy || !rules.nonce) {
return;
}
const nonce = event.context.security.nonce;
const sections = ["body", "bodyAppend", "bodyPrepend", "head"];
for (const section of sections) {
html[section] = html[section].map((element) => {
if (typeof element !== "string") {
return element;
}
element = element.replace(LINK_RE, (match, rest) => {
if (NONCE_RE.test(rest)) {
return match.replace(NONCE_RE, `nonce="${nonce}"`);
}
return `<link nonce="${nonce}"` + rest;
});
element = element.replace(SCRIPT_RE, (match, rest) => {
return `<script nonce="${nonce}"` + rest;
});
element = element.replace(STYLE_RE, (match, rest) => {
return `<style nonce="${nonce}"` + rest;
});
return element;
});
}
if (import.meta.dev) {
html.head.push(
`<meta property="csp-nonce" nonce="${nonce}">`
);
}
});
});