UNPKG

nuxt-security

Version:

🛡️ Security Module for Nuxt based on HTTP Headers and Middleware

56 lines (55 loc) 1.91 kB
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}">` ); } }); });