@dwp/govuk-casa 8.0.1 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.validateEvents = exports.validatePlugins = exports.validateGlobalHooks = exports.validatePlan = exports.validatePages = exports.validateFields = exports.validatePageHooks = exports.validateSessionCookieSameSite = exports.validateSessionCookiePath = exports.validateSessionStore = exports.validateSessionSecure = exports.validateSessionName = exports.validateSessionTtl = exports.validateSessionSecret = exports.validateViews = exports.validateSessionObject = exports.validateMountUrl = exports.validateI18nLocales = exports.validateI18nDirs = exports.validateI18nObject = void 0;
6
+ exports.validateHelmetConfigurator = exports.validateEvents = exports.validatePlugins = exports.validateGlobalHooks = exports.validatePlan = exports.validatePages = exports.validateFields = exports.validatePageHooks = exports.validateSessionCookieSameSite = exports.validateSessionCookiePath = exports.validateSessionStore = exports.validateSessionSecure = exports.validateSessionName = exports.validateSessionTtl = exports.validateSessionSecret = exports.validateViews = exports.validateSessionObject = exports.validateMountUrl = exports.validateI18nLocales = exports.validateI18nDirs = exports.validateI18nObject = void 0;
7
7
  /* eslint-disable sonarjs/no-duplicate-string */
8
8
  const field_js_1 = require("./field.js");
9
9
  const Plan_js_1 = __importDefault(require("./Plan.js"));
@@ -44,6 +44,11 @@ const utils_js_1 = require("./utils.js");
44
44
  * @property {PageHook[]} [hooks=[]] Page-specific hooks (optional, default [])
45
45
  * @property {PageField[]} [fields=[]] Fields to be managed on this page (optional, default [])
46
46
  */
47
+ /**
48
+ * @callback HelmetConfigurator
49
+ * @param {object} config A default Helmet configuration provided by CASA
50
+ * @returns {object} The modified configuration object
51
+ */
47
52
  /**
48
53
  * Configure some middleware for use in creating a new CASA app.
49
54
  *
@@ -418,6 +423,20 @@ function validateEvents(events) {
418
423
  return events;
419
424
  }
420
425
  exports.validateEvents = validateEvents;
426
+ /**
427
+ * Validates helmet configuration function.
428
+ *
429
+ * @param {HelmetConfigurator} helmetConfigurator Configuration function
430
+ * @returns {HelmetConfigurator} Validated configuration function
431
+ * @throws {TypeError} when passed a non-function
432
+ */
433
+ function validateHelmetConfigurator(helmetConfigurator) {
434
+ if (helmetConfigurator !== undefined && !(helmetConfigurator instanceof Function)) {
435
+ throw new TypeError('Helmet configurator must be a function');
436
+ }
437
+ return helmetConfigurator;
438
+ }
439
+ exports.validateHelmetConfigurator = validateHelmetConfigurator;
421
440
  /**
422
441
  * Ingest, validate, sanitise and manipulate configuration parameters.
423
442
  *
@@ -456,6 +475,8 @@ function ingest(config = {}) {
456
475
  plugins: validatePlugins(config.plugins),
457
476
  // Events
458
477
  events: validateEvents(config.events),
478
+ // Helmet configuration
479
+ helmetConfigurator: validateHelmetConfigurator(config.helmetConfigurator),
459
480
  };
460
481
  // Freeze to modifications
461
482
  Object.freeze(parsed);
@@ -72,7 +72,7 @@ function configure(config = {}) {
72
72
  }, pages = [], plan = null, hooks = [], plugins = [], events = [], i18n = {
73
73
  dirs: [],
74
74
  locales: ['en', 'cy'],
75
- }, } = (0, configuration_ingestor_js_1.default)(config);
75
+ }, helmetConfigurator = undefined, } = (0, configuration_ingestor_js_1.default)(config);
76
76
  // Prepare all page hooks so they are prefixed with the `journey.` scope.
77
77
  pages.forEach((page) => {
78
78
  var _a;
@@ -92,7 +92,7 @@ function configure(config = {}) {
92
92
  // Prepare mandatory middleware
93
93
  // These _must_ be added to the ExpressJS application at the start and end
94
94
  // of all other middleware respectively.
95
- const preMiddleware = (0, pre_js_1.default)();
95
+ const preMiddleware = (0, pre_js_1.default)({ helmetConfigurator });
96
96
  const postMiddleware = (0, post_js_1.default)({ mountUrl });
97
97
  // Prepare common middleware mounted prior to the ancillaryRouter
98
98
  const cookieParserMiddleware = (0, cookie_parser_1.default)(session.secret);
package/dist/lib/index.js CHANGED
@@ -5,7 +5,11 @@
5
5
  */
6
6
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
7
  if (k2 === undefined) k2 = k;
8
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
9
13
  }) : (function(o, m, k, k2) {
10
14
  if (k2 === undefined) k2 = k;
11
15
  o[k2] = m[k];
@@ -43,6 +43,14 @@ export function validateWaypoint(waypoint: any): void;
43
43
  export function validateView(view: any): void;
44
44
  export function validateHookName(hookName: any): void;
45
45
  export function validateHookPath(path: any): void;
46
+ /**
47
+ * Checks if the given string can be used as an object key.
48
+ *
49
+ * @param {string} key Proposed Object key
50
+ * @returns {string} Same key if it's valid
51
+ * @throws {Error} if proposed key is an invalid keyword
52
+ */
53
+ export function notProto(key: string): string;
46
54
  export type GlobalHook = import('./configuration-ingestor').GlobalHook;
47
55
  export type PageHook = import('./configuration-ingestor').PageHook;
48
56
  export type Hook = GlobalHook | PageHook;
package/dist/lib/utils.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * @typedef {import('./configuration-ingestor').GlobalHook} GlobalHook
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.validateHookPath = exports.validateHookName = exports.validateView = exports.validateWaypoint = exports.resolveMiddlewareHooks = exports.isEmpty = exports.stringifyInput = exports.isStringable = void 0;
6
+ exports.notProto = exports.validateHookPath = exports.validateHookName = exports.validateView = exports.validateWaypoint = exports.resolveMiddlewareHooks = exports.isEmpty = exports.stringifyInput = exports.isStringable = void 0;
7
7
  /**
8
8
  * @typedef {import('./configuration-ingestor').PageHook} PageHook
9
9
  */
@@ -111,3 +111,17 @@ function validateHookPath(path) {
111
111
  }
112
112
  }
113
113
  exports.validateHookPath = validateHookPath;
114
+ /**
115
+ * Checks if the given string can be used as an object key.
116
+ *
117
+ * @param {string} key Proposed Object key
118
+ * @returns {string} Same key if it's valid
119
+ * @throws {Error} if proposed key is an invalid keyword
120
+ */
121
+ function notProto(key) {
122
+ if (['__proto__', 'constructor', 'prototype'].includes(String(key).toLowerCase())) {
123
+ throw new Error('Attempt to use prototype key disallowed');
124
+ }
125
+ return key;
126
+ }
127
+ exports.notProto = notProto;
@@ -1,4 +1,5 @@
1
1
  export default class DateObject extends ValidatorFactory {
2
2
  name: string;
3
+ validate(value: any, dataContext?: {}): object[];
3
4
  }
4
5
  import ValidatorFactory from "../ValidatorFactory.js";
@@ -1,4 +1,6 @@
1
1
  export default class Email extends ValidatorFactory {
2
2
  name: string;
3
+ validate(value: any, dataContext?: {}): object[];
4
+ sanitise(value: any): string | undefined;
3
5
  }
4
6
  import ValidatorFactory from "../ValidatorFactory.js";
@@ -1,4 +1,6 @@
1
1
  export default class InArray extends ValidatorFactory {
2
2
  name: string;
3
+ validate(value: any, dataContext?: {}): object[];
4
+ sanitise(value: any): string | string[] | undefined;
3
5
  }
4
6
  import ValidatorFactory from "../ValidatorFactory.js";
@@ -1,4 +1,6 @@
1
1
  export default class Nino extends ValidatorFactory {
2
2
  name: string;
3
+ validate(value: any, dataContext?: {}): object[];
4
+ sanitise(value: any): string | undefined;
3
5
  }
4
6
  import ValidatorFactory from "../ValidatorFactory.js";
@@ -1,4 +1,5 @@
1
1
  export default class PostalAddressObject extends ValidatorFactory {
2
2
  name: string;
3
+ validate(value: any, dataContext?: {}): object[];
3
4
  }
4
5
  import ValidatorFactory from "../ValidatorFactory.js";
@@ -1,4 +1,6 @@
1
1
  export default class Regex extends ValidatorFactory {
2
2
  name: string;
3
+ validate(value?: string, dataContext?: {}): object[];
4
+ sanitise(value: any): string | undefined;
3
5
  }
4
6
  import ValidatorFactory from "../ValidatorFactory.js";
@@ -1,4 +1,8 @@
1
1
  export default class Required extends ValidatorFactory {
2
2
  name: string;
3
+ validate(value: any, dataContext?: {}): object[];
4
+ sanitise(value: any): string | (string | undefined)[] | {
5
+ [k: string]: string | undefined;
6
+ } | undefined;
3
7
  }
4
8
  import ValidatorFactory from "../ValidatorFactory.js";
@@ -1,4 +1,6 @@
1
1
  export default class Strlen extends ValidatorFactory {
2
2
  name: string;
3
+ validate(inputValue?: string, dataContext?: {}): object[];
4
+ sanitise(value: any): string | undefined;
3
5
  }
4
6
  import ValidatorFactory from "../ValidatorFactory.js";
@@ -1,5 +1,7 @@
1
1
  export default class WordCount extends ValidatorFactory {
2
2
  name: string;
3
3
  count(input: any): any;
4
+ validate(inputValue?: string, dataContext?: {}): object[];
5
+ sanitise(value: any): string | undefined;
4
6
  }
5
7
  import ValidatorFactory from "../ValidatorFactory.js";
@@ -9,7 +9,7 @@ const log = (0, logger_js_1.default)('middleware:post');
9
9
  function postMiddleware({ mountUrl, }) {
10
10
  return [
11
11
  (req, res) => {
12
- res.render('casa/errors/404.njk');
12
+ res.status(404).render('casa/errors/404.njk');
13
13
  },
14
14
  /* eslint-disable-next-line no-unused-vars */
15
15
  (err, req, res, next) => {
@@ -1,3 +1,5 @@
1
- /// <reference types="node" />
2
- declare function _default(): ((req: import("http").IncomingMessage, res: import("http").ServerResponse, next: (err?: unknown) => void) => void)[];
1
+ declare function _default({ helmetConfigurator, }?: {
2
+ helmetConfigurator: HelmetConfigurator;
3
+ }): Function[];
3
4
  export default _default;
5
+ export type HelmetConfigurator = import('../lib/configuration-ingestor').HelmetConfigurator;
@@ -5,7 +5,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const crypto_1 = require("crypto");
7
7
  const helmet_1 = __importDefault(require("helmet"));
8
- exports.default = () => [
8
+ const GA_DOMAIN = 'www.google-analytics.com';
9
+ const GTM_DOMAIN = 'www.googletagmanager.com';
10
+ /**
11
+ * @typedef {import('../lib/configuration-ingestor').HelmetConfigurator} HelmetConfigurator
12
+ */
13
+ /**
14
+ * Pre middleware.
15
+ *
16
+ * @param {object} opts Options
17
+ * @param {HelmetConfigurator} opts.helmetConfigurator Function to customise Helmet configuration
18
+ * @returns {Function[]} List of middleware
19
+ */
20
+ exports.default = ({ helmetConfigurator = (config) => (config), } = {}) => [
9
21
  // Only allow certain request methods
10
22
  (req, res, next) => {
11
23
  if (req.method !== 'GET' && req.method !== 'POST') {
@@ -35,17 +47,23 @@ exports.default = () => [
35
47
  next();
36
48
  },
37
49
  // Helmet suite of headers
38
- (0, helmet_1.default)({
50
+ (0, helmet_1.default)(helmetConfigurator({
39
51
  // Allows GA which is typically used, and a known inline script nonce
40
52
  contentSecurityPolicy: {
41
53
  useDefaults: true,
42
54
  directives: {
43
- 'script-src': ["'self'", 'www.google-analytics.com', 'www.googletagmanager.com', (req, res) => `'nonce-${res.locals.cspNonce}'`],
44
- 'style-src': ["'self'", 'https:', (req, res) => `'nonce-${res.locals.cspNonce}'`],
55
+ 'default-src': ["'none'"],
56
+ 'script-src': ["'self'", GA_DOMAIN, GTM_DOMAIN, (req, res) => `'nonce-${res.locals.cspNonce}'`],
57
+ 'img-src': ["'self'", GA_DOMAIN],
58
+ 'connect-src': ["'self'", GA_DOMAIN],
59
+ 'frame-src': ["'self'", GTM_DOMAIN],
60
+ 'frame-ancestors': ["'self'"],
45
61
  'form-action': ["'self'"],
62
+ 'style-src': ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`],
63
+ 'font-src': ["'self'"],
46
64
  },
47
65
  },
48
66
  // // Require referrer to aid navigation
49
67
  // referrerPolicy: 'no-referrer, same-origin',
50
- }),
68
+ })),
51
69
  ];
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dwp/govuk-casa",
3
- "version": "8.0.1",
3
+ "version": "8.1.0",
4
4
  "description": "A framework for building GOVUK Collect-And-Submit-Applications",
5
5
  "repository": {
6
6
  "type": "git",
@@ -45,48 +45,48 @@
45
45
  "csurf": "1.11.0",
46
46
  "debug": "4.3.3",
47
47
  "deepmerge": "4.2.2",
48
- "express": "4.17.2",
48
+ "express": "4.17.3",
49
49
  "express-session": "1.17.2",
50
- "govuk-frontend": "4.0.0",
50
+ "govuk-frontend": "4.0.1",
51
51
  "graphlib": "2.1.8",
52
- "helmet": "5.0.1",
53
- "i18next": "21.6.6",
54
- "i18next-http-middleware": "3.1.5",
52
+ "helmet": "5.0.2",
53
+ "i18next": "21.6.14",
54
+ "i18next-http-middleware": "3.2.0",
55
55
  "js-yaml": "4.1.0",
56
56
  "lodash": "4.17.21",
57
- "luxon": "2.3.0",
57
+ "luxon": "2.3.1",
58
58
  "nunjucks": "3.2.3",
59
59
  "uuid": "8.3.2",
60
- "validator": "^13.7.0"
60
+ "validator": "13.7.0"
61
61
  },
62
62
  "devDependencies": {
63
- "@babel/core": "7.16.7",
64
- "@babel/eslint-parser": "7.16.5",
65
- "@babel/preset-env": "7.16.8",
66
- "@commitlint/config-conventional": "16.0.0",
67
- "@dwp/casa-spiderplan": "2.0.0",
68
- "@dwp/casa-spiderplan-a11y-plugin": "0.1.3",
63
+ "@babel/core": "7.17.5",
64
+ "@babel/eslint-parser": "7.17.0",
65
+ "@babel/preset-env": "7.16.11",
66
+ "@commitlint/config-conventional": "16.2.1",
67
+ "@dwp/casa-spiderplan": "2.4.0",
68
+ "@dwp/casa-spiderplan-a11y-plugin": "0.1.4",
69
69
  "@dwp/casa-spiderplan-zap-plugin": "0.1.1",
70
70
  "@dwp/eslint-config-base": "6.0.0",
71
71
  "@types/express": "4.17.13",
72
- "@types/node": "17.0.8",
72
+ "@types/node": "17.0.21",
73
73
  "@types/nunjucks": "3.2.1",
74
74
  "babel-eslint": "10.1.0",
75
75
  "c8": "7.11.0",
76
- "chai": "4.3.4",
77
- "commitlint": "16.0.2",
78
- "eslint": "8.6.0",
76
+ "chai": "4.3.6",
77
+ "commitlint": "16.2.1",
78
+ "eslint": "8.10.0",
79
79
  "eslint-plugin-no-unsafe-regex": "1.0.0",
80
80
  "eslint-plugin-security": "1.4.0",
81
- "eslint-plugin-sonarjs": "0.11.0",
82
- "fast-check": "2.21.0",
81
+ "eslint-plugin-sonarjs": "0.12.0",
82
+ "fast-check": "2.22.0",
83
83
  "husky": "7.0.4",
84
- "mocha": "9.1.3",
85
- "sass": "1.47.0",
86
- "sinon": "12.0.1",
84
+ "mocha": "9.2.1",
85
+ "sass": "1.49.9",
86
+ "sinon": "13.0.1",
87
87
  "sinon-chai": "3.7.0",
88
88
  "standard-version": "9.3.2",
89
- "supertest": "6.2.1",
90
- "typescript": "4.5.4"
89
+ "supertest": "6.2.2",
90
+ "typescript": "4.6.2"
91
91
  }
92
92
  }