diff --git a/helpers.js b/helpers.js index 69eb254..cdc1e52 100644 --- a/helpers.js +++ b/helpers.js @@ -57,6 +57,7 @@ const helpersList = [ 'truncate', 'unless', 'earlyHint', + 'nonce', ]; const deprecatedHelpersList = [ diff --git a/helpers/nonce.js b/helpers/nonce.js new file mode 100644 index 0000000..fa90c4d --- /dev/null +++ b/helpers/nonce.js @@ -0,0 +1,16 @@ +'use strict'; + +const factory = globals => { + return function() { + const params = globals.getRequestParams(); + if (params && params.security && params.security.nonce) { + return ` nonce="${params.security.nonce}"`; + } + return '' + }; +}; + +module.exports = [{ + name: 'nonce', + factory: factory, +}]; diff --git a/index.js b/index.js index 99c9e68..70d5ce7 100644 --- a/index.js +++ b/index.js @@ -35,7 +35,7 @@ class HandlebarsRenderer { * @param {Object} logger - A console-like object to use for logging * @param {String} logLevel - log level which will be overriden by renderer */ - constructor(siteSettings, themeSettings, hbVersion, logger = console, logLevel = 'info') { + constructor(siteSettings, themeSettings, hbVersion, logger = console, logLevel = 'info', params = {}) { // Figure out which version of Handlebars to use. switch(hbVersion) { case 'v4': @@ -56,12 +56,14 @@ class HandlebarsRenderer { this.setContent({}); this.resetDecorators(); this.setLoggerLevel(logLevel); + this.setRequestParams(params); // Build global context for helpers this.helperContext = { handlebars: this.handlebars, getSiteSettings: this.getSiteSettings.bind(this), getThemeSettings: this.getThemeSettings.bind(this), + getRequestParams: this.getRequestParams.bind(this), getTranslator: this.getTranslator.bind(this), getContent: this.getContent.bind(this), getLogger: this.getLogger.bind(this), @@ -134,6 +136,25 @@ class HandlebarsRenderer { return this._themeSettings; }; + + /** + * Set the request params object containing the request parameters. + * + * @param {object} params + */ + setRequestParams(params) { + this._params = params; + } + + /** + * Get the request params object containing the request parameters. + * + * @returns {object} params + */ + getRequestParams() { + return this._params; + } + /** * Reset decorator list. */ diff --git a/spec/helpers.js b/spec/helpers.js index d3f475c..610fc1f 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -184,6 +184,7 @@ describe('helper registration', () => { 'getShortMonth', 'pick', 'earlyHint', + 'nonce' ].sort(); expect(helpers.map(helper => helper.name).sort()).to.be.equal(expectedHelpers); diff --git a/spec/helpers/nonce.js b/spec/helpers/nonce.js new file mode 100644 index 0000000..8a3b788 --- /dev/null +++ b/spec/helpers/nonce.js @@ -0,0 +1,38 @@ +const Lab = require('lab'); +const lab = exports.lab = Lab.script(); +const { describe, it} = lab; +const {testRunner, buildRenderer, randomString} = require('../spec-helpers'); + +describe('nonce helper', function () { + const context = {} + + it('should render a nonce html attribute with the correct value from request params', function (done) { + const requestParams = { + security: { + nonce: randomString() + }, + } + const renderer = buildRenderer({}, {}, {}, null, requestParams); + const runTestCases = testRunner({context, renderer}); + runTestCases([ + { + input: '{{nonce}}', + output: ' nonce="' + requestParams.security.nonce + '"', + }, + ], done); + }); + + it('should not render a nonce since request param is empty', function (done) { + const requestParams = { + security: {}, + } + const renderer = buildRenderer({}, {}, {}, null, requestParams); + const runTestCases = testRunner({context, renderer}); + runTestCases([ + { + input: '{{nonce}}', + output: '', + }, + ], done); + }); +}); \ No newline at end of file diff --git a/spec/spec-helpers.js b/spec/spec-helpers.js index 5611d43..5ab7bad 100644 --- a/spec/spec-helpers.js +++ b/spec/spec-helpers.js @@ -2,11 +2,12 @@ const Crypto = require('crypto'); const Renderer = require('../index'); const expect = require('code').expect; -function buildRenderer(siteSettings, themeSettings, templates, hbVersion) { +function buildRenderer(siteSettings, themeSettings, templates, hbVersion, requestParams) { siteSettings = siteSettings || {}; themeSettings = themeSettings || {}; hbVersion = hbVersion || 'v3'; - const renderer = new Renderer(siteSettings, themeSettings, hbVersion); + requestParams = requestParams || {}; + const renderer = new Renderer(siteSettings, themeSettings, hbVersion, console, 'info', requestParams); // Register templates if (typeof templates !== 'undefined') { @@ -17,18 +18,18 @@ function buildRenderer(siteSettings, themeSettings, templates, hbVersion) { return renderer; } -function renderString(template, context, siteSettings, themeSettings, templates) { - const renderer = buildRenderer(siteSettings, themeSettings, templates); +function renderString(template, context, siteSettings, themeSettings, templates, hbVersion, requestParams) { + const renderer = buildRenderer(siteSettings, themeSettings, templates, hbVersion, requestParams); return renderer.renderString(template, context); } -function render(template, context, siteSettings, themeSettings, templates) { - const renderer = buildRenderer(siteSettings, themeSettings, templates); +function render(template, context, siteSettings, themeSettings, templates, hbVersion, requestParams) { + const renderer = buildRenderer(siteSettings, themeSettings, templates, hbVersion, requestParams); return renderer.render(template, context); } // Return a function that is used to run through a set of test cases using renderString -function testRunner({context, siteSettings, themeSettings, templates, renderer, hbVersion}) { +function testRunner({context, siteSettings, themeSettings, templates, renderer, hbVersion, requestParams}) { return (testCases, done) => { const promises = testCases.map(testCase => { const render = testCase.renderer || @@ -36,7 +37,8 @@ function testRunner({context, siteSettings, themeSettings, templates, renderer, buildRenderer(testCase.siteSettings || siteSettings, testCase.themeSettings || themeSettings, testCase.templates || templates, - testCase.hbVersion || hbVersion); + testCase.hbVersion || hbVersion, + testCase.requestParams || requestParams); return render.renderString(testCase.input, testCase.context || context).then(result => { expect(result).to.be.equal(testCase.output);