Skip to content

Commit

Permalink
feat: expose getCSPHeader function for application to set CSP headers…
Browse files Browse the repository at this point in the history
… for their applicaiton
  • Loading branch information
RahulTinku committed Feb 28, 2024
1 parent 902c4a2 commit e644bbe
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 41 deletions.
15 changes: 10 additions & 5 deletions packages/subapp-server/lib/fastify-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ const {
updateFullTemplate,
setCSPNonce,
getCSPHeader,
until,
setCSPDirectives
until
} = require("./utils");

const routesFromFile = require("./routes-from-file");
Expand All @@ -41,7 +40,6 @@ function makeRouteHandler({ path, routeRenderer, routeOptions }) {
return async (request, reply) => {
try {
const { styleNonce = "", scriptNonce = ""} = setCSPNonce({ routeOptions });
const directiveNonce = setCSPDirectives({routeOptions})

// wait for webpack stats to be valid if webpackDev
if (webpackDev) {
Expand All @@ -58,8 +56,15 @@ function makeRouteHandler({ path, routeRenderer, routeOptions }) {
const data = context.result;
const status = data.status;

const cspHeader = getCSPHeader({ styleNonce, scriptNonce, directiveNonce });

let cspHeader;
/** If csp headers are provided by application in route options then use that otherwise generate CSP headers */
if(routeOptions.getCSPHeader && typeof routeOptions.getCSPHeader === "function"){
const rawCSPHeader = routeOptions.getCSPHeader({ styleNonce, scriptNonce });
// Replace newline characters and spaces
cspHeader = rawCSPHeader.replace(/\s{2,}/g, " ").trim();
}else{
cspHeader = getCSPHeader({ styleNonce, scriptNonce });
}
if (cspHeader) {
reply.header("Content-Security-Policy", cspHeader);
}
Expand Down
29 changes: 4 additions & 25 deletions packages/subapp-server/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@ function getDefaultRouteOptions() {
cspNonce: false,
templateFile: Path.join(__dirname, "..", "resources", "index-page"),
cdn: {},
reporting: { enable: false },
cspDirectives: undefined,
cspDirectivesValue : undefined
reporting: { enable: false }
};
}

Expand Down Expand Up @@ -197,33 +195,15 @@ function setCSPNonce({ routeOptions }) {
return routeOptions.cspNonceValue;
}

function setCSPDirectives({routeOptions}){
/**
* Check if cspDirectives is present in routerOptions and cspDirectives is an Object
*/
if(routeOptions.cspDirectives && typeof routeOptions.cspDirectives === "object"){
const data = Object.entries(routeOptions.cspDirectives).filter(([key,value]) => {
/** Check if script-src or style-src is explicitly set as additional directives */
if(key !== 'script-src' && key !== 'style-src'){
return ` ${key} ${value}`
}
});
routeOptions.cspDirectivesValue = data.join("; ");
}
return routeOptions.cspDirectivesValue;
}

function getCSPHeader({ styleNonce = "", scriptNonce = "", directiveNonce = "" }) {
function getCSPHeader({ styleNonce = "", scriptNonce = "" }) {
const unsafeEval = process.env.NODE_ENV !== "production" ?
`'unsafe-eval'` : "";

const styleSrc = styleNonce ? `style-src 'nonce-${styleNonce}' 'strict-dynamic';` : "";

const scriptSrc = scriptNonce ? `script-src 'nonce-${scriptNonce}' 'strict-dynamic' ${unsafeEval}; `: "";

const directiveSrc = directiveNonce ? `${directiveNonce};`: "";

return `${scriptSrc}${styleSrc}${directiveSrc}`;
return `${scriptSrc}${styleSrc}`;
}

/**
Expand Down Expand Up @@ -254,6 +234,5 @@ module.exports = {
invokeTemplateProcessor,
setCSPNonce,
getCSPHeader,
until,
setCSPDirectives
until
};
28 changes: 17 additions & 11 deletions samples/poc-subappv1-csp/src/routes.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const path = require("path");
const { cspNonceValue } = require("./server/utils");

const subAppOptions = {
serverSideRendering: false,
};
Expand All @@ -11,15 +10,19 @@ const commonRouteOptions = {
tokenHandlers,
};
/**

Check failure on line 12 in samples/poc-subappv1-csp/src/routes.js

View workflow job for this annotation

GitHub Actions / build (16.x)

Missing JSDoc return type

Check failure on line 12 in samples/poc-subappv1-csp/src/routes.js

View workflow job for this annotation

GitHub Actions / build (18.x)

Missing JSDoc return type

Check failure on line 12 in samples/poc-subappv1-csp/src/routes.js

View workflow job for this annotation

GitHub Actions / build (20.x)

Missing JSDoc return type
* to set CSP directives pass the cspDirectives as an object
* key should be the name of the directive
* value should be the value of the directive.
*
* @param {*} param0

Check failure on line 14 in samples/poc-subappv1-csp/src/routes.js

View workflow job for this annotation

GitHub Actions / build (16.x)

Missing JSDoc parameter description for 'param0'

Check failure on line 14 in samples/poc-subappv1-csp/src/routes.js

View workflow job for this annotation

GitHub Actions / build (18.x)

Missing JSDoc parameter description for 'param0'

Check failure on line 14 in samples/poc-subappv1-csp/src/routes.js

View workflow job for this annotation

GitHub Actions / build (20.x)

Missing JSDoc parameter description for 'param0'
* @returns CSP header value as string
*/
const additionalDirective = {
"frame-src": "'self' allowed-site.example.com",
"prefetch-src": "'none'",
"manifest-src": "'none'",
"script-src": "'self"
const setCSPHeaderValues = ({styleNonce, scriptNonce}) => {
const cspHeader = `
script-src 'self' 'nonce-${scriptNonce}' 'strict-dynamic' 'unsafe-eval';
style-src 'self' 'nonce-${styleNonce}' 'strict-dynamic' 'unsafe-eval';
font-src 'self';
object-src 'none';
form-action 'self';
`;
return cspHeader;
};

/**
Expand All @@ -31,6 +34,9 @@ const additionalDirective = {
*
* Option 3 - Selectively set boolean flag for `cspNonce`. { style: true } will add nonce only
* for styles
*
* Option 4 - write a function which would return list of CSP directives and values as string
* and pass that "function" to getCSPHeader
*/

export default {
Expand All @@ -41,8 +47,8 @@ export default {
// Enable one of these to use CSP header
cspNonce: true,
// cspNonce: { style: true }, // { script: true }
// cspNonce: cspNonceValue,
cspDirectives: additionalDirective,
// cspNonce: cspNonceValue,
getCSPHeader: setCSPHeaderValues,
criticalCSS: path.join(__dirname, "./server/critical.css"),
...commonRouteOptions
}
Expand Down

0 comments on commit e644bbe

Please sign in to comment.