UNPKG

permissions-policy

Version:

Middleware to set the Permissions-Policy HTTP header

71 lines (70 loc) 3.17 kB
"use strict"; const reserveredKeywords = new Set(["self", "src", "*", "none"]); function isPlainObject(value) { return typeof value === "object" && !Array.isArray(value) && value !== null; } function isQuoted(value) { return /^".*"$/.test(value); } const dashify = (str) => str.replace(/[A-Z]/g, (capitalLetter) => "-" + capitalLetter.toLowerCase()); function getHeaderValueFromOptions(options) { if (!isPlainObject(options)) { throw new Error("permissionsPolicy must be called with an object argument. See the documentation."); } const { features } = options; if (!isPlainObject(features)) { throw new Error('permissionsPolicy must have a single key, "features", which is an object of features. See the documentation.'); } const result = Object.entries(features) .map(([featureKeyCamelCase, featureValue]) => { if (!Array.isArray(featureValue)) { throw new Error(`The value of the "${featureKeyCamelCase}" feature must be array of strings.`); } const allowedValuesSeen = new Set(); featureValue.forEach((allowedValue) => { if (typeof allowedValue !== "string") { throw new Error(`The value of the "${featureKeyCamelCase}" feature contains a non-string, which is not supported.`); } else if (allowedValuesSeen.has(allowedValue)) { throw new Error(`The value of the "${featureKeyCamelCase}" feature contains duplicates, which it shouldn't.`); } else if (allowedValue === "'self'") { throw new Error("self must not be quoted."); } else if (allowedValue === "'none'") { throw new Error("none must not be quoted."); } else if (allowedValue === "'src'") { throw new Error("src must not be quoted."); } else if (!reserveredKeywords.has(allowedValue) && !isQuoted(allowedValue)) { throw new Error("values beside reserved keywords must be quoted."); } allowedValuesSeen.add(allowedValue); }); if (featureValue.length > 1) { if (allowedValuesSeen.has("*")) { throw new Error(`The value of the "${featureKeyCamelCase}" feature cannot contain * and other values.`); } else if (allowedValuesSeen.has("'none'")) { throw new Error(`The value of the "${featureKeyCamelCase}" feature cannot contain 'none' and other values.`); } } const featureKeyDashed = dashify(featureKeyCamelCase); const featureValuesUnion = featureValue.join(" "); return `${featureKeyDashed}=(${featureValuesUnion})`; }) .join(", "); if (result.length === 0) { throw new Error("At least one feature is required."); } return result; } module.exports = function permissionsPolicy(options) { const headerValue = getHeaderValueFromOptions(options); return function permissionsPolicy(_req, res, next) { res.setHeader("Permissions-Policy", headerValue); next(); }; };