@dwp/govuk-casa 8.0.0-alpha1 → 8.0.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +1 -1
  3. package/dist/assets/css/casa-ie8.css +1 -1
  4. package/dist/assets/css/casa.css +1 -1
  5. package/dist/casa.d.ts +2 -1
  6. package/dist/casa.js +3 -1
  7. package/dist/lib/CasaTemplateLoader.d.ts +11 -3
  8. package/dist/lib/CasaTemplateLoader.js +38 -2
  9. package/dist/lib/JourneyContext.d.ts +51 -8
  10. package/dist/lib/JourneyContext.js +73 -147
  11. package/dist/lib/MutableRouter.d.ts +19 -19
  12. package/dist/lib/MutableRouter.js +30 -23
  13. package/dist/lib/Plan.d.ts +41 -6
  14. package/dist/lib/Plan.js +84 -17
  15. package/dist/lib/ValidationError.d.ts +6 -2
  16. package/dist/lib/ValidationError.js +7 -0
  17. package/dist/lib/ValidatorFactory.d.ts +72 -13
  18. package/dist/lib/ValidatorFactory.js +33 -14
  19. package/dist/lib/configuration-ingestor.d.ts +262 -0
  20. package/dist/lib/configuration-ingestor.js +464 -0
  21. package/dist/lib/configure.d.ts +39 -154
  22. package/dist/lib/configure.js +35 -59
  23. package/dist/lib/dirname.cjs +1 -1
  24. package/dist/lib/dirname.d.cts +2 -0
  25. package/dist/lib/end-session.d.ts +4 -3
  26. package/dist/lib/end-session.js +30 -8
  27. package/dist/lib/field.d.ts +53 -55
  28. package/dist/lib/field.js +96 -54
  29. package/dist/lib/index.d.ts +14 -0
  30. package/dist/lib/index.js +54 -0
  31. package/dist/lib/logger.d.ts +2 -1
  32. package/dist/lib/logger.js +3 -4
  33. package/dist/lib/nunjucks-filters.d.ts +11 -11
  34. package/dist/lib/nunjucks-filters.js +22 -36
  35. package/dist/lib/nunjucks.d.ts +7 -6
  36. package/dist/lib/nunjucks.js +6 -6
  37. package/dist/lib/utils.d.ts +26 -0
  38. package/dist/lib/utils.js +70 -1
  39. package/dist/lib/validators/dateObject.js +1 -1
  40. package/dist/lib/validators/email.js +1 -1
  41. package/dist/lib/validators/inArray.js +1 -1
  42. package/dist/lib/validators/index.js +0 -22
  43. package/dist/lib/validators/postalAddressObject.js +7 -3
  44. package/dist/lib/validators/required.js +1 -1
  45. package/dist/lib/waypoint-url.d.ts +13 -7
  46. package/dist/lib/waypoint-url.js +13 -7
  47. package/dist/middleware/body-parser.d.ts +2 -1
  48. package/dist/middleware/body-parser.js +20 -11
  49. package/dist/middleware/csrf.d.ts +1 -1
  50. package/dist/middleware/csrf.js +2 -2
  51. package/dist/middleware/data.d.ts +1 -2
  52. package/dist/middleware/data.js +19 -15
  53. package/dist/middleware/dirname.cjs +1 -1
  54. package/dist/middleware/dirname.d.cts +2 -0
  55. package/dist/middleware/gather-fields.d.ts +3 -2
  56. package/dist/middleware/gather-fields.js +14 -7
  57. package/dist/middleware/i18n.d.ts +1 -1
  58. package/dist/middleware/i18n.js +16 -11
  59. package/dist/middleware/post.d.ts +3 -1
  60. package/dist/middleware/post.js +24 -9
  61. package/dist/middleware/pre.js +15 -2
  62. package/dist/middleware/progress-journey.js +15 -17
  63. package/dist/middleware/sanitise-fields.js +15 -10
  64. package/dist/middleware/session.d.ts +2 -1
  65. package/dist/middleware/session.js +65 -52
  66. package/dist/middleware/skip-waypoint.js +10 -7
  67. package/dist/middleware/steer-journey.d.ts +3 -2
  68. package/dist/middleware/steer-journey.js +26 -8
  69. package/dist/middleware/validate-fields.js +15 -21
  70. package/dist/mjs/esm-wrapper.js +18 -7
  71. package/dist/routes/ancillary.d.ts +8 -1
  72. package/dist/routes/ancillary.js +7 -1
  73. package/dist/routes/dirname.cjs +1 -1
  74. package/dist/routes/dirname.d.cts +2 -0
  75. package/dist/routes/journey.js +20 -24
  76. package/dist/routes/static.js +10 -9
  77. package/package.json +41 -22
  78. package/views/casa/errors/static.njk +11 -0
  79. package/views/casa/layouts/main.njk +3 -3
package/dist/lib/utils.js CHANGED
@@ -1,6 +1,15 @@
1
1
  "use strict";
2
+ /**
3
+ * @typedef {import('./configuration-ingestor').GlobalHook} GlobalHook
4
+ */
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isEmpty = exports.stringifyInput = exports.isStringable = void 0;
6
+ exports.validateHookPath = exports.validateHookName = exports.validateView = exports.validateWaypoint = exports.resolveMiddlewareHooks = exports.isEmpty = exports.stringifyInput = exports.isStringable = void 0;
7
+ /**
8
+ * @typedef {import('./configuration-ingestor').PageHook} PageHook
9
+ */
10
+ /**
11
+ * @typedef {GlobalHook | PageHook} Hook
12
+ */
4
13
  /**
5
14
  * Test is a value can be stringifed (numbers or strings)
6
15
  *
@@ -37,8 +46,68 @@ function isEmpty(val) {
37
46
  return true;
38
47
  }
39
48
  if (Array.isArray(val) || typeof val === 'object') {
49
+ // ESLint disabled as `k` is an "own property" (thanks to `Object.keys()`)
50
+ /* eslint-disable-next-line security/detect-object-injection */
40
51
  return Object.keys(val).filter((k) => !isEmpty(val[k])).length === 0;
41
52
  }
42
53
  return false;
43
54
  }
44
55
  exports.isEmpty = isEmpty;
56
+ /**
57
+ * Extract the middleware functions that are relevant for the given hook and
58
+ * path.
59
+ *
60
+ * @param {string} hookName Hook name (including scope prefix)
61
+ * @param {string} path URL path to match (relative to mountUrl)
62
+ * @param {Hook[]} hooks Hooks to be applied at the page level
63
+ * @returns {Function[]} An array of middleware that should be applied
64
+ */
65
+ function resolveMiddlewareHooks(hookName, path, hooks = []) {
66
+ /* eslint-disable-next-line max-len */
67
+ const pathMatch = (h) => h.path === undefined || (h.path instanceof RegExp && h.path.test(path)) || h.path === path;
68
+ return hooks.filter((h) => h.hook === hookName).filter(pathMatch).map((h) => h.middleware);
69
+ }
70
+ exports.resolveMiddlewareHooks = resolveMiddlewareHooks;
71
+ /* ------------------------------------------------ validation / sanitisation */
72
+ function validateWaypoint(waypoint) {
73
+ if (typeof waypoint !== 'string') {
74
+ throw new TypeError('Waypoint must be a string');
75
+ }
76
+ if (!waypoint.length) {
77
+ throw new SyntaxError('Waypoint must not be empty');
78
+ }
79
+ if (waypoint.match(/[^/a-z0-9_-]/)) {
80
+ throw new SyntaxError('Waypoint must contain only a-z, 0-9, -, _ and / characters');
81
+ }
82
+ }
83
+ exports.validateWaypoint = validateWaypoint;
84
+ function validateView(view) {
85
+ if (typeof view !== 'string') {
86
+ throw new TypeError('View must be a string');
87
+ }
88
+ if (!view.length) {
89
+ throw new SyntaxError('View must not be empty');
90
+ }
91
+ if (!view.match(/^[a-z0-9/_-]+\.njk$/i)) {
92
+ throw new SyntaxError('View must contain only a-z, 0-9, -, _ and / characters, and end in .njk');
93
+ }
94
+ }
95
+ exports.validateView = validateView;
96
+ function validateHookName(hookName) {
97
+ if (typeof hookName !== 'string') {
98
+ throw new TypeError('Hook name must be a string');
99
+ }
100
+ if (!hookName.length) {
101
+ throw new SyntaxError('Hook name must not be empty');
102
+ }
103
+ if (!hookName.match(/^([a-z]+\.|)[a-z]+$/i)) {
104
+ throw new SyntaxError('Hook name must match either <scope>.<hookname> or <hookname> formats');
105
+ }
106
+ }
107
+ exports.validateHookName = validateHookName;
108
+ function validateHookPath(path) {
109
+ if (typeof path !== 'string' && !(path instanceof RegExp)) {
110
+ throw new TypeError('Hook path must be a string or RegExp');
111
+ }
112
+ }
113
+ exports.validateHookPath = validateHookPath;
@@ -27,10 +27,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
27
27
  * luxon.DateTime now = Override the notion of "now" (useful for testing)
28
28
  */
29
29
  const luxon_1 = require("luxon");
30
+ const lodash_1 = __importDefault(require("lodash"));
30
31
  const ValidationError_js_1 = __importDefault(require("../ValidationError.js"));
31
32
  const ValidatorFactory_js_1 = __importDefault(require("../ValidatorFactory.js"));
32
33
  const utils_js_1 = require("../utils.js");
33
- const lodash_1 = __importDefault(require("lodash"));
34
34
  const { isPlainObject } = lodash_1.default;
35
35
  class DateObject extends ValidatorFactory_js_1.default {
36
36
  constructor() {
@@ -12,10 +12,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
+ const validator_1 = __importDefault(require("validator"));
15
16
  const ValidationError_js_1 = __importDefault(require("../ValidationError.js"));
16
17
  const ValidatorFactory_js_1 = __importDefault(require("../ValidatorFactory.js"));
17
18
  const utils_js_1 = require("../utils.js");
18
- const validator_1 = __importDefault(require("validator"));
19
19
  const { isEmail } = validator_1.default; // CommonJS
20
20
  class Email extends ValidatorFactory_js_1.default {
21
21
  constructor() {
@@ -31,7 +31,7 @@ class InArray extends ValidatorFactory_js_1.default {
31
31
  if (value !== null && typeof value !== 'undefined') {
32
32
  const search = Array.isArray(value) ? value : [value];
33
33
  for (let i = 0, l = search.length; i < l; i += 1) {
34
- if (source.indexOf(search[i]) > -1) {
34
+ if (source.indexOf(search[parseInt(i, 10)]) > -1) {
35
35
  valid = true;
36
36
  }
37
37
  else {
@@ -23,25 +23,3 @@ exports.default = {
23
23
  strlen: strlen_js_1.default,
24
24
  wordCount: wordCount_js_1.default,
25
25
  };
26
- // const dateObject = require('./dateObject.js');
27
- // const email = require('./email.js');
28
- // const inArray = require('./inArray.js');
29
- // const nino = require('./nino.js');
30
- // const optional = require('./optional.js');
31
- // const postalAddressObject = require('./postalAddressObject.js');
32
- // const regex = require('./regex.js');
33
- // const required = require('./required.js');
34
- // const strlen = require('./strlen.js');
35
- // const wordCount = require('./wordCount.js');
36
- // module.exports = {
37
- // dateObject,
38
- // email,
39
- // inArray,
40
- // nino,
41
- // optional,
42
- // postalAddressObject,
43
- // regex,
44
- // required,
45
- // strlen,
46
- // wordCount,
47
- // };
@@ -21,10 +21,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
21
21
  * int strlenmax = Max. String length for each of the inputs appress[1-4]
22
22
  * array requiredFields = Field parts required (others become optional)
23
23
  */
24
+ const lodash_1 = __importDefault(require("lodash"));
24
25
  const ValidationError_js_1 = __importDefault(require("../ValidationError.js"));
25
26
  const ValidatorFactory_js_1 = __importDefault(require("../ValidatorFactory.js"));
26
27
  const utils_js_1 = require("../utils.js");
27
- const lodash_1 = __importDefault(require("lodash"));
28
28
  const { isPlainObject } = lodash_1.default; // CommonjS
29
29
  class PostalAddressObject extends ValidatorFactory_js_1.default {
30
30
  constructor() {
@@ -66,6 +66,8 @@ class PostalAddressObject extends ValidatorFactory_js_1.default {
66
66
  const reqF = Object.create(null);
67
67
  const reqC = cfg.requiredFields;
68
68
  ['address1', 'address2', 'address3', 'address4', 'postcode'].forEach((k) => {
69
+ // ESLint disabled as `k` is a known value from a constant list
70
+ /* eslint-disable-next-line security/detect-object-injection */
69
71
  reqF[k] = reqC.indexOf(k) > -1;
70
72
  });
71
73
  let valid = true;
@@ -75,8 +77,7 @@ class PostalAddressObject extends ValidatorFactory_js_1.default {
75
77
  const reAddrLine1 = /^\d+|[^\s]+[a-z0-9\-,.&#()/\\:;'" ]+$/i;
76
78
  // UK Postcode regex taken from the dwp java pc checker
77
79
  // https://github.com/dwp/postcode-format-validation
78
- const pc = /^(?![QVX])[A-Z]((?![IJZ])[A-Z][0-9](([0-9]?)|([ABEHMNPRVWXY]?))|([0-9]([0-9]?|[ABCDEFGHJKPSTUW]?))) ?[0-9]((?![CIKMOV])[A-Z]){2}$|^(BFPO)[ ]?[0-9]{1,4}$/i;
79
- const rePostcode = new RegExp(pc, 'i');
80
+ const rePostcode = new RegExp(/^(?![QVX])[A-Z]((?![IJZ])[A-Z][0-9](([0-9]?)|([ABEHMNPRVWXY]?))|([0-9]([0-9]?|[ABCDEFGHJKPSTUW]?))) ?[0-9]((?![CIKMOV])[A-Z]){2}$|^(BFPO)[ ]?[0-9]{1,4}$/i, 'i');
80
81
  // [required, regex, strlenmax, error message]
81
82
  const attributes = {
82
83
  address1: [reqF.address1, reAddrLine1, cfg.strlenmax, cfg.errorMsgAddress1],
@@ -85,6 +86,8 @@ class PostalAddressObject extends ValidatorFactory_js_1.default {
85
86
  address4: [reqF.address4, reAddr, cfg.strlenmax, cfg.errorMsgAddress4],
86
87
  postcode: [reqF.postcode, rePostcode, null, cfg.errorMsgPostcode],
87
88
  };
89
+ // ESLint disabled as `k` is a known value from the constant list above
90
+ /* eslint-disable security/detect-object-injection */
88
91
  Object.keys(attributes).forEach((k) => {
89
92
  const attr = attributes[k];
90
93
  const hasProperty = Object.prototype.hasOwnProperty.call(value, k);
@@ -100,6 +103,7 @@ class PostalAddressObject extends ValidatorFactory_js_1.default {
100
103
  }));
101
104
  }
102
105
  });
106
+ /* eslint-enable security/detect-object-injection */
103
107
  }
104
108
  else {
105
109
  valid = false;
@@ -10,10 +10,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  * Value is required. The following values will fail this rule:
11
11
  * (all values that satisify `isEmpty()`) plus '\s'
12
12
  */
13
+ const lodash_1 = __importDefault(require("lodash"));
13
14
  const utils_js_1 = require("../utils.js");
14
15
  const ValidatorFactory_js_1 = __importDefault(require("../ValidatorFactory.js"));
15
16
  const ValidationError_js_1 = __importDefault(require("../ValidationError.js"));
16
- const lodash_1 = __importDefault(require("lodash"));
17
17
  const { isPlainObject } = lodash_1.default; // CommonJS
18
18
  class Required extends ValidatorFactory_js_1.default {
19
19
  constructor() {
@@ -1,17 +1,23 @@
1
1
  /**
2
+ * Generate a URL pointing at a particular waypoint.
2
3
  *
3
- * @param {Object} obj
4
- * @param {string} obj.waypoint
5
- * @param {string} obj.mountUrl
4
+ * @param {object} obj Options
5
+ * @param {string} obj.waypoint Waypoint
6
+ * @param {string} obj.mountUrl Mount URL
6
7
  * @param {JourneyContext} obj.journeyContext JourneyContext
7
- * @param {boolean} obj.edit
8
- * @param {string} obj.editOrigin
9
- * @returns
8
+ * @param {boolean} obj.edit Turn edit mode on or off
9
+ * @param {string} obj.editOrigin Edit mode original URL
10
+ * @param {boolean} obj.skipTo Skip to this waypoint from the current one
11
+ * @param {string} obj.routeName Plan route name; next | prev
12
+ * @returns {string} URL
10
13
  */
11
14
  export default function waypointUrl({ waypoint, mountUrl, journeyContext, edit, editOrigin, skipTo, routeName, }?: {
12
15
  waypoint: string;
13
16
  mountUrl: string;
14
- journeyContext: any;
17
+ journeyContext: JourneyContext;
15
18
  edit: boolean;
16
19
  editOrigin: string;
20
+ skipTo: boolean;
21
+ routeName: string;
17
22
  }): string;
23
+ export type JourneyContext = import('./index').JourneyContext;
@@ -1,16 +1,22 @@
1
1
  "use strict";
2
+ /**
3
+ * @typedef {import('./index').JourneyContext} JourneyContext
4
+ */
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  const reUrlProtocolExtract = /^url:\/\/(.+)$/i;
4
- const sanitiseWaypoint = (w) => w.replace(/[^\/a-z0-9_-]/ig, '').replace(/\/+/g, '/');
7
+ const sanitiseWaypoint = (w) => w.replace(/[^/a-z0-9_-]/ig, '').replace(/\/+/g, '/');
5
8
  /**
9
+ * Generate a URL pointing at a particular waypoint.
6
10
  *
7
- * @param {Object} obj
8
- * @param {string} obj.waypoint
9
- * @param {string} obj.mountUrl
11
+ * @param {object} obj Options
12
+ * @param {string} obj.waypoint Waypoint
13
+ * @param {string} obj.mountUrl Mount URL
10
14
  * @param {JourneyContext} obj.journeyContext JourneyContext
11
- * @param {boolean} obj.edit
12
- * @param {string} obj.editOrigin
13
- * @returns
15
+ * @param {boolean} obj.edit Turn edit mode on or off
16
+ * @param {string} obj.editOrigin Edit mode original URL
17
+ * @param {boolean} obj.skipTo Skip to this waypoint from the current one
18
+ * @param {string} obj.routeName Plan route name; next | prev
19
+ * @returns {string} URL
14
20
  */
15
21
  function waypointUrl({ waypoint = '', mountUrl = '/', journeyContext, edit = false, editOrigin, skipTo, routeName = 'next', } = Object.create(null)) {
16
22
  const url = new URL('https://placeholder.test');
@@ -1 +1,2 @@
1
- export default function _default(): import("connect").NextHandleFunction[];
1
+ export function verifyBody(req: any, res: any, buf: any, encoding: any): void;
2
+ export default function bodyParserMiddleware(): import("connect").NextHandleFunction[];
@@ -1,10 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyBody = void 0;
3
4
  const express_1 = require("express");
4
- function default_1() {
5
- const rProto = /__proto__/i;
6
- const rPrototype = /prototype[=[\]]/i;
7
- const rConstructor = /constructor[=[\]]/i;
5
+ const rProto = /__proto__/i;
6
+ const rPrototype = /prototype[='"[\]]/i;
7
+ const rConstructor = /constructor[='"[\]]/i;
8
+ function verifyBody(req, res, buf, encoding) {
9
+ const body = decodeURI(buf.toString(encoding));
10
+ if (rProto.test(body)) {
11
+ throw new Error('Request body verification failed (__proto__)');
12
+ }
13
+ if (rPrototype.test(body)) {
14
+ throw new Error('Request body verification failed (prototype)');
15
+ }
16
+ if (rConstructor.test(body)) {
17
+ throw new Error('Request body verification failed (constructor)');
18
+ }
19
+ }
20
+ exports.verifyBody = verifyBody;
21
+ function bodyParserMiddleware() {
8
22
  return [
9
23
  (0, express_1.urlencoded)({
10
24
  extended: true,
@@ -12,13 +26,8 @@ function default_1() {
12
26
  inflate: true,
13
27
  parameterLimit: 25,
14
28
  limit: 1024 * 50,
15
- verify: (req, res, buf, encoding) => {
16
- const body = decodeURI(buf.toString(encoding));
17
- if (rProto.test(body) || rPrototype.test(body) || rConstructor.test(body)) {
18
- throw new Error('Request body verification failed');
19
- }
20
- },
29
+ verify: verifyBody,
21
30
  }),
22
31
  ];
23
32
  }
24
- exports.default = default_1;
33
+ exports.default = bodyParserMiddleware;
@@ -1 +1 @@
1
- export default function _default(): any[];
1
+ export default function csrfMiddleware(): any[];
@@ -7,7 +7,7 @@ const csurf_1 = __importDefault(require("csurf"));
7
7
  // 2 middleware: one to generate the csrf token and check its validity (POST
8
8
  // only), and one to provide that token to templates via the `casa.csrfToken`
9
9
  // variable.
10
- function default_1() {
10
+ function csrfMiddleware() {
11
11
  return [
12
12
  (0, csurf_1.default)({
13
13
  cookie: false,
@@ -28,4 +28,4 @@ function default_1() {
28
28
  },
29
29
  ];
30
30
  }
31
- exports.default = default_1;
31
+ exports.default = csrfMiddleware;
@@ -1,6 +1,5 @@
1
- export default function _default({ plan, mountUrl, serviceName, events, }: {
1
+ export default function dataMiddleware({ plan, mountUrl, events, }: {
2
2
  plan: any;
3
3
  mountUrl: any;
4
- serviceName: any;
5
4
  events: any;
6
5
  }): ((req: any, res: any, next: any) => void)[];
@@ -5,18 +5,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  return (mod && mod.__esModule) ? mod : { "default": mod };
6
6
  };
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- const lodash_1 = require("lodash");
8
+ const lodash_1 = __importDefault(require("lodash"));
9
9
  const JourneyContext_js_1 = __importDefault(require("../lib/JourneyContext.js"));
10
10
  const waypoint_url_js_1 = __importDefault(require("../lib/waypoint-url.js"));
11
+ const { has } = lodash_1.default;
11
12
  const editOrigin = (req) => {
12
- if ((0, lodash_1.has)(req.query, 'editorigin')) {
13
+ if (has(req.query, 'editorigin')) {
13
14
  return (0, waypoint_url_js_1.default)({ waypoint: req.query.editorigin });
14
15
  }
15
- else if ((0, lodash_1.has)(req === null || req === void 0 ? void 0 : req.body, 'editorigin')) {
16
+ if (has(req === null || req === void 0 ? void 0 : req.body, 'editorigin')) {
16
17
  return (0, waypoint_url_js_1.default)({ waypoint: req.body.editorigin });
17
18
  }
19
+ return '';
18
20
  };
19
- function default_1({ plan, mountUrl, serviceName, events, }) {
21
+ function dataMiddleware({ plan, mountUrl, events, }) {
20
22
  return [
21
23
  (req, res, next) => {
22
24
  /* ------------------------------------------------ Request decorations */
@@ -27,27 +29,29 @@ function default_1({ plan, mountUrl, serviceName, events, }) {
27
29
  // Current journey context, loaded from session, specified by
28
30
  // `contextid` request parameter
29
31
  journeyContext: JourneyContext_js_1.default.extractContextFromRequest(req).addEventListeners(events),
30
- // Edit mode attributes
31
- editMode: (0, lodash_1.has)(req.query, 'edit') || (0, lodash_1.has)(req === null || req === void 0 ? void 0 : req.body, 'edit'), editOrigin: editOrigin(req) });
32
+ // Edit mode
33
+ editMode: (has(req === null || req === void 0 ? void 0 : req.query, 'edit') && has(req === null || req === void 0 ? void 0 : req.query, 'editorigin')) || (has(req === null || req === void 0 ? void 0 : req.body, 'edit') && has(req === null || req === void 0 ? void 0 : req.body, 'editorigin')), editOrigin: editOrigin(req) });
34
+ // Grab chosen language from session
35
+ req.casa.journeyContext.nav.language = req.session.language;
32
36
  /* ------------------------------------------------- Template variables */
33
37
  // CASA and userland templates
34
38
  res.locals.casa = {
35
39
  mountUrl,
36
- serviceName,
40
+ editMode: req.casa.editMode,
41
+ editOrigin: req.casa.editOrigin,
37
42
  };
38
43
  res.locals.locale = req.language;
39
44
  // Used by govuk-frontend template
40
45
  // - req.language is provided by i18n-http-middleware
41
46
  res.locals.htmlLang = req.language;
42
- // res.locals.makeLink
43
- // res.locals.casa.journeyPreviousUrl
44
- // req.session.journeyContext
45
- // req.session.language
46
- // req.editOriginUrl
47
- // req.inEditMode
48
- // req.editSearchParams
47
+ // Function for building URLs. This will be curried with the `mountUrl`,
48
+ // `journeyContext`, `edit` and `editOrigin` for convenience. This means
49
+ // the template author does not have to be concerned about the current
50
+ // "state" when generating URLs, but still has the ability to override
51
+ // these curried defaults if needs be.
52
+ res.locals.waypointUrl = (args) => (0, waypoint_url_js_1.default)(Object.assign({ mountUrl, journeyContext: req.casa.journeyContext, edit: req.casa.editMode, editOrigin: req.casa.editOrigin }, args));
49
53
  next();
50
54
  },
51
55
  ];
52
56
  }
53
- exports.default = default_1;
57
+ exports.default = dataMiddleware;
@@ -1 +1 @@
1
- module.exports = __dirname;
1
+ module.exports = __dirname;
@@ -0,0 +1,2 @@
1
+ declare const _exports: string;
2
+ export = _exports;
@@ -1,5 +1,6 @@
1
1
  declare function _default({ waypoint, fields, }: {
2
2
  waypoint: string;
3
- fields?: any[] | undefined;
4
- }): ((req: any, res: any, next: any) => void)[];
3
+ fields?: import("../lib/field").PageField[] | undefined;
4
+ }): any[];
5
5
  export default _default;
6
+ export type PageField = import('../lib/field').PageField;
@@ -9,9 +9,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  const JourneyContext_js_1 = __importDefault(require("../lib/JourneyContext.js"));
11
11
  /**
12
- * @param {Object} obj
12
+ * @typedef {import('../lib/field').PageField} PageField
13
+ */
14
+ /**
15
+ * Gather the field data from `req.body` into the current JourneyContext
16
+ * - Store in the current session
17
+ * - Update the user's journey context with the new data
18
+ * - Remove validation date of JourneyContext so it can re-evaluted
19
+ *
20
+ * @param {object} obj Options
13
21
  * @param {string} obj.waypoint Waypoint
14
22
  * @param {PageField[]} [obj.fields=[]] Fields
23
+ * @returns {Array} Array of middleware
15
24
  */
16
25
  exports.default = ({ waypoint, fields = [], }) => [
17
26
  (req, res, next) => {
@@ -19,21 +28,19 @@ exports.default = ({ waypoint, fields = [], }) => [
19
28
  // for any comparison work that may be done in subseqent middleware.
20
29
  req.casa.archivedJourneyContext = JourneyContext_js_1.default.fromContext(req.casa.journeyContext);
21
30
  // Ignore data for any non-persistent fields
31
+ // ESLint disabled as `fields`, `i` and `name` are dev-controlled
32
+ /* eslint-disable security/detect-object-injection */
22
33
  const persistentBody = Object.create(null);
23
34
  for (let i = 0, l = fields.length; i < l; i++) {
24
35
  if (fields[i].meta.persist && fields[i].getValue(req.body) !== undefined) {
25
36
  persistentBody[fields[i].name] = fields[i].getValue(req.body);
26
37
  }
27
38
  }
39
+ /* eslint-enable security/detect-object-injection */
28
40
  // Update data and validation context in the current request, and store
29
41
  req.casa.journeyContext.setDataForPage(waypoint, persistentBody);
30
42
  req.casa.journeyContext.removeValidationStateForPage(waypoint);
31
43
  JourneyContext_js_1.default.putContext(req.session, req.casa.journeyContext);
32
- // TODO: Once feature we might like here is to invalidate specific pages
33
- // based on the change that has just happened, to force those pages to be
34
- // visited again. The main use case is to cater for a change in content on
35
- // those pages that fundamentally alter the context of the question being asked,
36
- // and so should be asked again. For example, "Your address" vs "You and your partner's address"
37
44
  next();
38
- }
45
+ },
39
46
  ];
@@ -1,4 +1,4 @@
1
- export default function _default({ languages, directories, }: {
1
+ export default function i18nMiddleware({ languages, directories, }: {
2
2
  languages?: string[] | undefined;
3
3
  directories?: any[] | undefined;
4
4
  }): import("express-serve-static-core").Handler[];
@@ -10,16 +10,14 @@ const fs_1 = require("fs");
10
10
  const deepmerge_1 = __importDefault(require("deepmerge"));
11
11
  const js_yaml_1 = __importDefault(require("js-yaml"));
12
12
  const logger_js_1 = __importDefault(require("../lib/logger.js"));
13
- const log = (0, logger_js_1.default)('middleware.i18n');
13
+ const log = (0, logger_js_1.default)('middleware:i18n');
14
14
  const loadJson = (file) => {
15
15
  // Strip out newlines (this is a legacy feature which we're keeping for
16
16
  // backwards compatibility).
17
- let json = (0, fs_1.readFileSync)(file, 'utf8');
17
+ const json = (0, fs_1.readFileSync)(file, 'utf8');
18
18
  return JSON.parse(json.replace(/[\r\n]/g, ''));
19
19
  };
20
- const loadYaml = (file) => {
21
- return js_yaml_1.default.load((0, fs_1.readFileSync)(file, 'utf8'));
22
- };
20
+ const loadYaml = (file) => js_yaml_1.default.load((0, fs_1.readFileSync)(file, 'utf8'));
23
21
  const extract = (file) => {
24
22
  const ext = /.yaml$/i.test(file) ? '.yaml' : '.json';
25
23
  const data = ext === '.yaml' ? loadYaml(file) : loadJson(file);
@@ -31,11 +29,15 @@ const extract = (file) => {
31
29
  const loadResources = (languages, directories) => {
32
30
  const store = Object.create(null);
33
31
  languages.forEach((language) => {
32
+ // ESLint disabled as `store`, `language` and `ns` are all dev-controlled,
33
+ // and this function is only called once, at boot-time.
34
+ /* eslint-disable security/detect-object-injection */
34
35
  store[language] = Object.create(null);
35
- directories.forEach((dir) => {
36
- dir = (0, path_1.resolve)(dir, language);
37
- if (!(0, fs_1.existsSync)(dir))
36
+ directories.forEach((basedir) => {
37
+ const dir = (0, path_1.resolve)(basedir, language);
38
+ if (!(0, fs_1.existsSync)(dir)) {
38
39
  return;
40
+ }
39
41
  log.info('Loading %s language from %s ...', language, dir);
40
42
  (0, fs_1.readdirSync)(dir).forEach((file) => {
41
43
  const { ns, data } = extract((0, path_1.resolve)(dir, file));
@@ -45,10 +47,11 @@ const loadResources = (languages, directories) => {
45
47
  store[language][ns] = (0, deepmerge_1.default)(store[language][ns], data);
46
48
  });
47
49
  });
50
+ /* eslint-enable security/detect-object-injection */
48
51
  });
49
52
  return store;
50
53
  };
51
- function default_1({ languages = ['en', 'cy'], directories = [], }) {
54
+ function i18nMiddleware({ languages = ['en', 'cy'], directories = [], }) {
52
55
  // Load _all_ translations, from all directories into memory.
53
56
  const resources = loadResources(languages, directories);
54
57
  // Configure i18next
@@ -70,10 +73,12 @@ function default_1({ languages = ['en', 'cy'], directories = [], }) {
70
73
  order: ['querystring', 'session'],
71
74
  },
72
75
  });
73
- // 2 middleware: one to read/set the session language, and one to enhance the req/res objects with i18n features
76
+ // 2 middleware: one to read/set the session language, and one to enhance the
77
+ // req/res objects with i18n features
74
78
  return [
75
79
  (req, res, next) => {
76
80
  if (!req.session.language) {
81
+ /* eslint-disable-next-line prefer-destructuring */
77
82
  req.session.language = languages[0];
78
83
  }
79
84
  if ((req === null || req === void 0 ? void 0 : req.query.lang) && languages.includes(req.query.lang)) {
@@ -84,4 +89,4 @@ function default_1({ languages = ['en', 'cy'], directories = [], }) {
84
89
  (0, i18next_http_middleware_1.handle)(i18nInstance),
85
90
  ];
86
91
  }
87
- exports.default = default_1;
92
+ exports.default = i18nMiddleware;
@@ -1 +1,3 @@
1
- export default function _default(): ((err: any, req: any, res: any, next: any) => any)[];
1
+ export default function postMiddleware({ mountUrl, }: {
2
+ mountUrl: any;
3
+ }): ((err: any, req: any, res: any, next: any) => any)[];
@@ -6,37 +6,52 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  // 2 middleware: one as a fallback 404 handler, one to handle thrown errors
7
7
  const logger_js_1 = __importDefault(require("../lib/logger.js"));
8
8
  const log = (0, logger_js_1.default)('middleware:post');
9
- function default_1() {
9
+ function postMiddleware({ mountUrl, }) {
10
10
  return [
11
- (req, res, next) => {
11
+ (req, res) => {
12
12
  res.render('casa/errors/404.njk');
13
13
  },
14
+ /* eslint-disable-next-line no-unused-vars */
14
15
  (err, req, res, next) => {
16
+ var _a;
17
+ // In some cases, an error may have been thrown before the template assets
18
+ // have had a chance to initialise. So we use a hardcoded template in
19
+ // these cases to ensure the user sees an appropriate message.
20
+ let TEMPLATE = 'casa/errors/500.njk';
21
+ if (!res.locals.t) {
22
+ res.locals.t = () => ('');
23
+ res.locals.casa = Object.assign(Object.assign({}, (_a = res.locals) === null || _a === void 0 ? void 0 : _a.casa), { mountUrl });
24
+ TEMPLATE = 'casa/errors/static.njk';
25
+ }
15
26
  // CSRF token is invalid in some way
16
27
  if ((err === null || err === void 0 ? void 0 : err.code) === 'EBADCSRFTOKEN') {
17
28
  log.info('CSRF validation has failed. This may be caused by the user submitting a stale form from a previous session [EBADCSRFTOKEN]');
18
- return res.status(403).render('casa/errors/500.njk');
29
+ return res.status(403).render(TEMPLATE, { errorCode: 'bad_csrf_token' });
19
30
  }
20
31
  // Body parsing verification check failed
21
32
  if ((err === null || err === void 0 ? void 0 : err.type) === 'entity.verify.failed') {
22
33
  log.info('Body parser verification has failed. This has been caused by the user submitting a payload containing invalid data [entity.verify.failed]');
23
- return res.status(403).render('casa/errors/500.njk');
34
+ return res.status(403).render(TEMPLATE, { errorCode: 'invalid_payload' });
24
35
  }
25
36
  // Too many parameters submitted
26
37
  if ((err === null || err === void 0 ? void 0 : err.type) === 'parameters.too.many') {
27
38
  log.info('The request contains more parameters than is currently allowed [parameters.too.many]');
28
- return res.status(413).render('casa/errors/500.njk');
39
+ return res.status(413).render(TEMPLATE, { errorCode: 'parameter_limit_exceeded' });
29
40
  }
30
41
  // Overall payload too large
31
42
  if ((err === null || err === void 0 ? void 0 : err.type) === 'entity.too.large') {
32
43
  log.info(`The request payload is too large. Received ${err.length}b with a maximum of ${err.limit}b [parameters.too.many]`);
33
- return res.status(413).render('casa/errors/500.njk');
44
+ return res.status(413).render(TEMPLATE, { errorCode: 'payload_size_exceeded' });
45
+ }
46
+ // Unaccept request method
47
+ if ((err === null || err === void 0 ? void 0 : err.code) === 'unaccepted_request_method') {
48
+ log.info(err.message);
49
+ return res.status(400).render(TEMPLATE, { errorCode: 'unaccepted_request_method' });
34
50
  }
35
51
  // Unknown error
36
52
  log.error(`Unknown error: ${err.message}; stacktrace: ${err.stack}`);
37
- res.render('casa/errors/500.njk');
53
+ return res.status(200).render(TEMPLATE);
38
54
  },
39
55
  ];
40
56
  }
41
- exports.default = default_1;
42
- ;
57
+ exports.default = postMiddleware;