@dwp/govuk-casa 8.16.2 → 8.16.4

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 (189) hide show
  1. package/dist/assets/css/casa-ie8.css +1 -1
  2. package/dist/assets/css/casa.css +1 -1
  3. package/dist/casa.d.ts +13 -13
  4. package/dist/casa.js +17 -7
  5. package/dist/casa.js.map +1 -1
  6. package/dist/lib/CasaTemplateLoader.d.ts +1 -1
  7. package/dist/lib/CasaTemplateLoader.js +13 -14
  8. package/dist/lib/CasaTemplateLoader.js.map +1 -1
  9. package/dist/lib/JourneyContext.d.ts +10 -4
  10. package/dist/lib/JourneyContext.js +57 -47
  11. package/dist/lib/JourneyContext.js.map +1 -1
  12. package/dist/lib/MutableRouter.d.ts +1 -1
  13. package/dist/lib/MutableRouter.js +22 -23
  14. package/dist/lib/MutableRouter.js.map +1 -1
  15. package/dist/lib/Plan.d.ts +5 -5
  16. package/dist/lib/Plan.js +49 -36
  17. package/dist/lib/Plan.js.map +1 -1
  18. package/dist/lib/ValidationError.d.ts +1 -1
  19. package/dist/lib/ValidationError.js +9 -9
  20. package/dist/lib/ValidationError.js.map +1 -1
  21. package/dist/lib/ValidatorFactory.js +4 -7
  22. package/dist/lib/ValidatorFactory.js.map +1 -1
  23. package/dist/lib/configuration-ingestor.d.ts +75 -14
  24. package/dist/lib/configuration-ingestor.js +156 -64
  25. package/dist/lib/configuration-ingestor.js.map +1 -1
  26. package/dist/lib/configure.js +11 -10
  27. package/dist/lib/configure.js.map +1 -1
  28. package/dist/lib/constants.js +8 -8
  29. package/dist/lib/context-id-generators.d.ts +1 -1
  30. package/dist/lib/context-id-generators.js +7 -4
  31. package/dist/lib/context-id-generators.js.map +1 -1
  32. package/dist/lib/end-session.js +2 -2
  33. package/dist/lib/field.d.ts +6 -6
  34. package/dist/lib/field.js +15 -21
  35. package/dist/lib/field.js.map +1 -1
  36. package/dist/lib/index.d.ts +13 -13
  37. package/dist/lib/index.js +17 -7
  38. package/dist/lib/index.js.map +1 -1
  39. package/dist/lib/logger.js +7 -7
  40. package/dist/lib/logger.js.map +1 -1
  41. package/dist/lib/mount.js +3 -3
  42. package/dist/lib/mount.js.map +1 -1
  43. package/dist/lib/nunjucks-filters.d.ts +5 -1
  44. package/dist/lib/nunjucks-filters.js +37 -23
  45. package/dist/lib/nunjucks-filters.js.map +1 -1
  46. package/dist/lib/nunjucks.d.ts +2 -2
  47. package/dist/lib/nunjucks.js +6 -7
  48. package/dist/lib/nunjucks.js.map +1 -1
  49. package/dist/lib/utils.js +52 -42
  50. package/dist/lib/utils.js.map +1 -1
  51. package/dist/lib/validators/dateObject.d.ts +3 -3
  52. package/dist/lib/validators/dateObject.js +44 -37
  53. package/dist/lib/validators/dateObject.js.map +1 -1
  54. package/dist/lib/validators/email.d.ts +2 -2
  55. package/dist/lib/validators/email.js +4 -5
  56. package/dist/lib/validators/email.js.map +1 -1
  57. package/dist/lib/validators/inArray.d.ts +2 -2
  58. package/dist/lib/validators/inArray.js +5 -6
  59. package/dist/lib/validators/inArray.js.map +1 -1
  60. package/dist/lib/validators/index.d.ts +10 -10
  61. package/dist/lib/validators/index.js.map +1 -1
  62. package/dist/lib/validators/nino.d.ts +2 -2
  63. package/dist/lib/validators/nino.js +10 -7
  64. package/dist/lib/validators/nino.js.map +1 -1
  65. package/dist/lib/validators/postalAddressObject.d.ts +2 -2
  66. package/dist/lib/validators/postalAddressObject.js +52 -39
  67. package/dist/lib/validators/postalAddressObject.js.map +1 -1
  68. package/dist/lib/validators/range.d.ts +2 -2
  69. package/dist/lib/validators/range.js +6 -7
  70. package/dist/lib/validators/range.js.map +1 -1
  71. package/dist/lib/validators/regex.d.ts +2 -2
  72. package/dist/lib/validators/regex.js +4 -5
  73. package/dist/lib/validators/regex.js.map +1 -1
  74. package/dist/lib/validators/required.d.ts +2 -2
  75. package/dist/lib/validators/required.js +6 -9
  76. package/dist/lib/validators/required.js.map +1 -1
  77. package/dist/lib/validators/strlen.d.ts +2 -2
  78. package/dist/lib/validators/strlen.js +8 -9
  79. package/dist/lib/validators/strlen.js.map +1 -1
  80. package/dist/lib/validators/wordCount.d.ts +2 -2
  81. package/dist/lib/validators/wordCount.js +10 -9
  82. package/dist/lib/validators/wordCount.js.map +1 -1
  83. package/dist/lib/waypoint-url.d.ts +4 -4
  84. package/dist/lib/waypoint-url.js +23 -23
  85. package/dist/lib/waypoint-url.js.map +1 -1
  86. package/dist/middleware/body-parser.d.ts +27 -5
  87. package/dist/middleware/body-parser.js +37 -6
  88. package/dist/middleware/body-parser.js.map +1 -1
  89. package/dist/middleware/csrf.d.ts +3 -0
  90. package/dist/middleware/csrf.js +3 -0
  91. package/dist/middleware/csrf.js.map +1 -1
  92. package/dist/middleware/data.d.ts +22 -5
  93. package/dist/middleware/data.js +37 -7
  94. package/dist/middleware/data.js.map +1 -1
  95. package/dist/middleware/gather-fields.d.ts +1 -1
  96. package/dist/middleware/gather-fields.js +4 -3
  97. package/dist/middleware/gather-fields.js.map +1 -1
  98. package/dist/middleware/i18n.d.ts +11 -2
  99. package/dist/middleware/i18n.js +26 -17
  100. package/dist/middleware/i18n.js.map +1 -1
  101. package/dist/middleware/post.d.ts +3 -1
  102. package/dist/middleware/post.js +35 -18
  103. package/dist/middleware/post.js.map +1 -1
  104. package/dist/middleware/pre.d.ts +1 -1
  105. package/dist/middleware/pre.js +43 -21
  106. package/dist/middleware/pre.js.map +1 -1
  107. package/dist/middleware/progress-journey.d.ts +1 -1
  108. package/dist/middleware/progress-journey.js +5 -5
  109. package/dist/middleware/progress-journey.js.map +1 -1
  110. package/dist/middleware/sanitise-fields.d.ts +2 -2
  111. package/dist/middleware/sanitise-fields.js +13 -11
  112. package/dist/middleware/sanitise-fields.js.map +1 -1
  113. package/dist/middleware/serve-first-waypoint.d.ts +1 -1
  114. package/dist/middleware/serve-first-waypoint.js +6 -4
  115. package/dist/middleware/serve-first-waypoint.js.map +1 -1
  116. package/dist/middleware/session.d.ts +27 -8
  117. package/dist/middleware/session.js +53 -25
  118. package/dist/middleware/session.js.map +1 -1
  119. package/dist/middleware/skip-waypoint.d.ts +1 -1
  120. package/dist/middleware/skip-waypoint.js +3 -3
  121. package/dist/middleware/skip-waypoint.js.map +1 -1
  122. package/dist/middleware/steer-journey.d.ts +1 -1
  123. package/dist/middleware/steer-journey.js +15 -13
  124. package/dist/middleware/steer-journey.js.map +1 -1
  125. package/dist/middleware/strip-proxy-path.d.ts +1 -1
  126. package/dist/middleware/strip-proxy-path.js +3 -3
  127. package/dist/middleware/strip-proxy-path.js.map +1 -1
  128. package/dist/middleware/validate-fields.d.ts +2 -2
  129. package/dist/middleware/validate-fields.js +2 -5
  130. package/dist/middleware/validate-fields.js.map +1 -1
  131. package/dist/routes/ancillary.d.ts +2 -2
  132. package/dist/routes/ancillary.js +3 -3
  133. package/dist/routes/ancillary.js.map +1 -1
  134. package/dist/routes/journey.d.ts +1 -1
  135. package/dist/routes/journey.js +85 -31
  136. package/dist/routes/journey.js.map +1 -1
  137. package/dist/routes/static.d.ts +13 -4
  138. package/dist/routes/static.js +21 -19
  139. package/dist/routes/static.js.map +1 -1
  140. package/package.json +33 -36
  141. package/src/casa.js +13 -13
  142. package/src/lib/CasaTemplateLoader.js +21 -17
  143. package/src/lib/JourneyContext.js +118 -79
  144. package/src/lib/MutableRouter.js +30 -26
  145. package/src/lib/Plan.js +109 -62
  146. package/src/lib/ValidationError.js +13 -10
  147. package/src/lib/ValidatorFactory.js +7 -8
  148. package/src/lib/configuration-ingestor.js +200 -74
  149. package/src/lib/configure.js +31 -30
  150. package/src/lib/constants.js +8 -8
  151. package/src/lib/context-id-generators.js +39 -38
  152. package/src/lib/end-session.js +3 -3
  153. package/src/lib/field.js +48 -32
  154. package/src/lib/index.js +12 -12
  155. package/src/lib/logger.js +9 -9
  156. package/src/lib/mount.js +68 -73
  157. package/src/lib/nunjucks-filters.js +57 -44
  158. package/src/lib/nunjucks.js +20 -16
  159. package/src/lib/utils.js +69 -44
  160. package/src/lib/validators/dateObject.js +57 -48
  161. package/src/lib/validators/email.js +8 -9
  162. package/src/lib/validators/inArray.js +8 -9
  163. package/src/lib/validators/index.js +11 -11
  164. package/src/lib/validators/nino.js +25 -12
  165. package/src/lib/validators/postalAddressObject.js +73 -55
  166. package/src/lib/validators/range.js +9 -11
  167. package/src/lib/validators/regex.js +7 -8
  168. package/src/lib/validators/required.js +13 -14
  169. package/src/lib/validators/strlen.js +11 -12
  170. package/src/lib/validators/wordCount.js +17 -12
  171. package/src/lib/waypoint-url.js +48 -33
  172. package/src/middleware/body-parser.js +44 -10
  173. package/src/middleware/csrf.js +4 -1
  174. package/src/middleware/data.js +62 -25
  175. package/src/middleware/gather-fields.js +8 -8
  176. package/src/middleware/i18n.js +49 -39
  177. package/src/middleware/post.js +47 -21
  178. package/src/middleware/pre.js +59 -35
  179. package/src/middleware/progress-journey.js +32 -18
  180. package/src/middleware/sanitise-fields.js +43 -20
  181. package/src/middleware/serve-first-waypoint.js +12 -10
  182. package/src/middleware/session.js +97 -65
  183. package/src/middleware/skip-waypoint.js +7 -9
  184. package/src/middleware/steer-journey.js +39 -27
  185. package/src/middleware/strip-proxy-path.js +8 -7
  186. package/src/middleware/validate-fields.js +5 -12
  187. package/src/routes/ancillary.js +4 -6
  188. package/src/routes/journey.js +158 -78
  189. package/src/routes/static.js +64 -26
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  /** @access private */
7
- const reUrlProtocolExtract = /^url:\/\/(.+)$/i
7
+ const reUrlProtocolExtract = /^url:\/\/(.+)$/i;
8
8
 
9
9
  /**
10
10
  * Sanitise a waypoint string.
@@ -13,7 +13,8 @@ const reUrlProtocolExtract = /^url:\/\/(.+)$/i
13
13
  * @param {string} w Waypoint
14
14
  * @returns {string} Sanitised waypoint
15
15
  */
16
- const sanitiseWaypoint = (w) => w.replace(/[^/a-z0-9_-]/ig, '').replace(/\/+/g, '/');
16
+ const sanitiseWaypoint = (w) =>
17
+ w.replace(/[^/a-z0-9_-]/gi, "").replace(/\/+/g, "/");
17
18
 
18
19
  /**
19
20
  * Sanitise a waypoint string, with allowed URL parameters:
@@ -25,7 +26,7 @@ const sanitiseWaypoint = (w) => w.replace(/[^/a-z0-9_-]/ig, '').replace(/\/+/g,
25
26
  */
26
27
  const sanitiseWaypointWithAllowedParams = (w) => {
27
28
  // Extract URL params
28
- const parts = w.split('?');
29
+ const parts = w.split("?");
29
30
  if (parts.length !== 2) {
30
31
  return sanitiseWaypoint(w);
31
32
  }
@@ -34,14 +35,17 @@ const sanitiseWaypointWithAllowedParams = (w) => {
34
35
 
35
36
  // Strip all but those parameters allowed
36
37
  const validatedUrlSearchParams = new URLSearchParams();
37
- for (const pk of ['contextid']) {
38
+ for (const pk of ["contextid"]) {
38
39
  if (urlSearchParams.has(pk)) {
39
40
  validatedUrlSearchParams.set(pk, urlSearchParams.get(pk));
40
41
  }
41
42
  }
42
43
 
43
- return `${sanitiseWaypoint(waypoint)}?${validatedUrlSearchParams.toString()}`.replace(/\?$/, '');
44
- }
44
+ return `${sanitiseWaypoint(waypoint)}?${validatedUrlSearchParams.toString()}`.replace(
45
+ /\?$/,
46
+ "",
47
+ );
48
+ };
45
49
 
46
50
  /**
47
51
  * Generate a URL pointing at a particular waypoint.
@@ -56,41 +60,49 @@ const sanitiseWaypointWithAllowedParams = (w) => {
56
60
  * })
57
61
  * @memberof module:@dwp/govuk-casa
58
62
  * @param {object} obj Options
59
- * @param {string} [obj.waypoint=""] Waypoint
60
- * @param {string} [obj.mountUrl="/"] Mount URL
63
+ * @param {string} [obj.waypoint] Waypoint
64
+ * @param {string} [obj.mountUrl] Mount URL
61
65
  * @param {JourneyContext} [obj.journeyContext] JourneyContext
62
- * @param {boolean} [obj.edit=false] Turn edit mode on or off
66
+ * @param {boolean} [obj.edit] Turn edit mode on or off
63
67
  * @param {string} [obj.editOrigin] Edit mode original URL
64
68
  * @param {boolean} [obj.skipTo] Skip to this waypoint from the current one
65
- * @param {string} [obj.routeName=next] Plan route name; next | prev
69
+ * @param {string} [obj.routeName] Plan route name; next | prev
66
70
  * @returns {string} URL
67
71
  */
68
- export default function waypointUrl({
69
- waypoint = '',
70
- mountUrl = '/',
71
- journeyContext,
72
- edit = false,
73
- editOrigin,
74
- skipTo,
75
- routeName = 'next',
76
- } = Object.create(null)) {
77
- const url = new URL('https://placeholder.test');
72
+ export default function waypointUrl(
73
+ {
74
+ waypoint = "",
75
+ mountUrl = "/",
76
+ journeyContext,
77
+ edit = false,
78
+ editOrigin,
79
+ skipTo,
80
+ routeName = "next",
81
+ } = Object.create(null),
82
+ ) {
83
+ const url = new URL("https://placeholder.test");
78
84
 
79
85
  // Handle url:// protocol
80
86
  // - This will generate a link to the root handler "_" for the given mount path
81
- if (String(waypoint).substr(0, 7) === 'url:///') {
87
+ if (String(waypoint).substr(0, 7) === "url:///") {
82
88
  const m = waypoint.match(reUrlProtocolExtract);
83
89
 
84
- const u = new URL(sanitiseWaypointWithAllowedParams(m[1]), 'https://placeholder.test/');
90
+ const u = new URL(
91
+ sanitiseWaypointWithAllowedParams(m[1]),
92
+ "https://placeholder.test/",
93
+ );
85
94
  url.pathname = `${sanitiseWaypoint(u.pathname)}/_/`;
86
95
 
87
- url.searchParams.set('refmount', `url://${mountUrl}`);
88
- url.searchParams.set('route', routeName);
96
+ url.searchParams.set("refmount", `url://${mountUrl}`);
97
+ url.searchParams.set("route", routeName);
89
98
  for (const [uk, uv] of u.searchParams.entries()) {
90
99
  url.searchParams.append(uk, uv);
91
100
  }
92
101
  } else {
93
- const u = new URL(sanitiseWaypointWithAllowedParams(`${mountUrl}${waypoint}`), 'https://placeholder.test/');
102
+ const u = new URL(
103
+ sanitiseWaypointWithAllowedParams(`${mountUrl}${waypoint}`),
104
+ "https://placeholder.test/",
105
+ );
94
106
  url.pathname = u.pathname;
95
107
  url.search = u.search;
96
108
  }
@@ -100,26 +112,29 @@ export default function waypointUrl({
100
112
  // added if the context ID already appears in the url path, i.e. to avoid
101
113
  // `/path/1234-abcd/waypoint?contextid=1234-abcd` scenarios
102
114
  if (
103
- journeyContext
104
- && !journeyContext.isDefault()
105
- && journeyContext.identity.id
106
- && !mountUrl.includes(`/${journeyContext.identity.id}/`)
115
+ journeyContext &&
116
+ !journeyContext.isDefault() &&
117
+ journeyContext.identity.id &&
118
+ !mountUrl.includes(`/${journeyContext.identity.id}/`)
107
119
  ) {
108
- url.searchParams.set('contextid', journeyContext.identity.id);
120
+ url.searchParams.set("contextid", journeyContext.identity.id);
109
121
  }
110
122
 
111
123
  // Attach edit mode flag
112
124
  if (edit === true) {
113
- url.searchParams.set('edit', 'true');
125
+ url.searchParams.set("edit", "true");
114
126
  }
115
127
 
116
128
  if (edit && editOrigin) {
117
- url.searchParams.set('editorigin', sanitiseWaypointWithAllowedParams(editOrigin));
129
+ url.searchParams.set(
130
+ "editorigin",
131
+ sanitiseWaypointWithAllowedParams(editOrigin),
132
+ );
118
133
  }
119
134
 
120
135
  // Skipto
121
136
  if (skipTo) {
122
- url.searchParams.set('skipto', sanitiseWaypointWithAllowedParams(skipTo));
137
+ url.searchParams.set("skipto", sanitiseWaypointWithAllowedParams(skipTo));
123
138
  }
124
139
 
125
140
  return `${sanitiseWaypoint(url.pathname)}${url.search}`;
@@ -1,30 +1,64 @@
1
- import { urlencoded as expressBodyParser } from 'express';
1
+ import { urlencoded as expressBodyParser } from "express";
2
+
3
+ /**
4
+ * @typedef {import("express").RequestHandler} RequestHandler
5
+ * @access private
6
+ */
7
+
8
+ /**
9
+ * @typedef {import("express").Request} Request
10
+ * @access private
11
+ */
12
+
13
+ /**
14
+ * @typedef {import("express").Response} Response
15
+ * @access private
16
+ */
2
17
 
3
18
  const rProto = /__proto__/i;
4
19
  const rPrototype = /prototype[='"[\]]/i;
5
20
  const rConstructor = /constructor[='"[\]]/i;
6
21
 
22
+ /**
23
+ * Verify request body.
24
+ *
25
+ * @param {Request} req HTTP request
26
+ * @param {Response} res HTTP response
27
+ * @param {Buffer} buf Buffer
28
+ * @param {string} encoding Character encoding
29
+ * @returns {void}
30
+ * @throws {Error} For invalid bodies
31
+ */
7
32
  export function verifyBody(req, res, buf, encoding) {
8
- const body = decodeURI(buf.toString(encoding)).replace(/[\s\u200B-\u200D\uFEFF]/g, '');
33
+ const body = decodeURI(buf.toString(encoding)).replace(
34
+ /[\s\u200B-\u200D\uFEFF]/g,
35
+ "",
36
+ );
9
37
  if (rProto.test(body)) {
10
- throw new Error('Request body verification failed (__proto__)');
38
+ throw new Error("Request body verification failed (__proto__)");
11
39
  }
12
40
  if (rPrototype.test(body)) {
13
- throw new Error('Request body verification failed (prototype)');
41
+ throw new Error("Request body verification failed (prototype)");
14
42
  }
15
43
  if (rConstructor.test(body)) {
16
- throw new Error('Request body verification failed (constructor)');
44
+ throw new Error("Request body verification failed (constructor)");
17
45
  }
18
46
  }
19
47
 
20
- export default function bodyParserMiddleware({
21
- formMaxParams,
22
- formMaxBytes,
23
- }) {
48
+ /**
49
+ * Body parsing middleware.
50
+ *
51
+ * @param {object} opts Options
52
+ * @param {number} opts.formMaxParams Max number of parameters that should be
53
+ * parsed
54
+ * @param {number} opts.formMaxBytes Max bytes that should be read
55
+ * @returns {RequestHandler[]} Middleware functions
56
+ */
57
+ export default function bodyParserMiddleware({ formMaxParams, formMaxBytes }) {
24
58
  return [
25
59
  expressBodyParser({
26
60
  extended: true,
27
- type: 'application/x-www-form-urlencoded',
61
+ type: "application/x-www-form-urlencoded",
28
62
  inflate: true,
29
63
  parameterLimit: formMaxParams,
30
64
  limit: formMaxBytes,
@@ -1,9 +1,12 @@
1
- import { csrfSync } from 'csrf-sync';
1
+ import { csrfSync } from "csrf-sync";
2
2
 
3
3
  // 2 middleware: one to generate the csrf token and check its validity (POST
4
4
  // only), and one to provide that token to templates via the `casa.csrfToken`
5
5
  // variable.
6
6
 
7
+ /**
8
+ *
9
+ */
7
10
  export default function csrfMiddleware() {
8
11
  const { csrfSynchronisedProtection } = csrfSync({
9
12
  getTokenFromRequest: (req) => req.body._csrf,
@@ -1,28 +1,57 @@
1
1
  // Decorates the request with some contextual data about the user's journey
2
2
  // through the application. This is used by downstream middleware and templates.
3
3
 
4
- import lodash from 'lodash';
5
- import JourneyContext from '../lib/JourneyContext.js';
6
- import { validateUrlPath } from '../lib/utils.js';
7
- import waypointUrl from '../lib/waypoint-url.js';
4
+ import lodash from "lodash";
5
+ import JourneyContext from "../lib/JourneyContext.js";
6
+ import { validateUrlPath } from "../lib/utils.js";
7
+ import waypointUrl from "../lib/waypoint-url.js";
8
+
9
+ /**
10
+ * @typedef {import("express").RequestHandler} RequestHandler
11
+ * @access private
12
+ */
13
+
14
+ /**
15
+ * @typedef {import("../casa.js").Plan} Plan
16
+ * @access private
17
+ */
18
+
19
+ /**
20
+ * @typedef {import("../casa.js").ContextEventHandler} ContextEventHandler
21
+ * @access private
22
+ */
23
+
24
+ /**
25
+ * @typedef {import("../casa.js").ContextIdGenerator} ContextIdGenerator
26
+ * @access private
27
+ */
8
28
 
9
29
  const { has } = lodash;
10
30
 
11
31
  const editOrigin = (req) => {
12
- if (has(req.query, 'editorigin')) {
32
+ if (has(req.query, "editorigin")) {
13
33
  return waypointUrl({ waypoint: req.query.editorigin });
14
34
  }
15
- if (has(req?.body, 'editorigin')) {
35
+ if (has(req?.body, "editorigin")) {
16
36
  return waypointUrl({ waypoint: req.body.editorigin });
17
37
  }
18
- return '';
19
- }
20
-
21
- export default function dataMiddleware({
22
- plan,
23
- events,
24
- contextIdGenerator,
25
- }) {
38
+ return "";
39
+ };
40
+
41
+ /**
42
+ * Data middleware.
43
+ *
44
+ * Decorates the request with some contextual data about the user's journey
45
+ * through the application. This is used by downstream middleware and
46
+ * templates.
47
+ *
48
+ * @param {object} opts Options
49
+ * @param {Plan} opts.plan CASA Plan
50
+ * @param {ContextEventHandler[]} opts.events Event handlers
51
+ * @param {ContextIdGenerator} opts.contextIdGenerator Content ID generator
52
+ * @returns {RequestHandler[]} Middleware functions
53
+ */
54
+ export default function dataMiddleware({ plan, events, contextIdGenerator }) {
26
55
  return [
27
56
  (req, res, next) => {
28
57
  /* ------------------------------------------------ Request decorations */
@@ -36,10 +65,15 @@ export default function dataMiddleware({
36
65
 
37
66
  // Current journey context, loaded from session, specified by
38
67
  // `contextid` request parameter
39
- journeyContext: JourneyContext.extractContextFromRequest(req).addEventListeners(events),
68
+ journeyContext:
69
+ JourneyContext.extractContextFromRequest(req).addEventListeners(
70
+ events,
71
+ ),
40
72
 
41
73
  // Edit mode
42
- editMode: (has(req?.query, 'edit') && has(req?.query, 'editorigin')) || (has(req?.body, 'edit') && has(req?.body, 'editorigin')),
74
+ editMode:
75
+ (has(req?.query, "edit") && has(req?.query, "editorigin")) ||
76
+ (has(req?.body, "edit") && has(req?.body, "editorigin")),
43
77
  editOrigin: editOrigin(req),
44
78
  };
45
79
 
@@ -56,7 +90,7 @@ export default function dataMiddleware({
56
90
  /* ------------------------------------------------- Template variables */
57
91
 
58
92
  // Capture mount URL that will be used in generating all browser URLs
59
- const mountUrl = validateUrlPath(`${req.baseUrl}/`.replace(/\/+/g, '/'));
93
+ const mountUrl = validateUrlPath(`${req.baseUrl}/`.replace(/\/+/g, "/"));
60
94
 
61
95
  // If this CASA app is mounted on a parameterised route, then all of its
62
96
  // static assets (served by `staticRouter`) will, by default, be served
@@ -77,7 +111,9 @@ export default function dataMiddleware({
77
111
  // Router, the `baseUrl` is different in each case, so we cannot rely
78
112
  // on it to be consistent. Hence the need for this property, which will
79
113
  // always be the non-parameterised version of the baseUrl.
80
- const staticMountUrl = validateUrlPath(`${req.unparameterisedBaseUrl}/`.replace(/\/+/g, '/'));
114
+ const staticMountUrl = validateUrlPath(
115
+ `${req.unparameterisedBaseUrl}/`.replace(/\/+/g, "/"),
116
+ );
81
117
 
82
118
  // CASA and userland templates
83
119
  res.locals.casa = {
@@ -99,13 +135,14 @@ export default function dataMiddleware({
99
135
  // the template author does not have to be concerned about the current
100
136
  // "state" when generating URLs, but still has the ability to override
101
137
  // these curried defaults if needs be.
102
- res.locals.waypointUrl = (args) => waypointUrl({
103
- mountUrl,
104
- journeyContext: req.casa.journeyContext,
105
- edit: req.casa.editMode,
106
- editOrigin: req.casa.editOrigin,
107
- ...args,
108
- });
138
+ res.locals.waypointUrl = (args) =>
139
+ waypointUrl({
140
+ mountUrl,
141
+ journeyContext: req.casa.journeyContext,
142
+ edit: req.casa.editMode,
143
+ editOrigin: req.casa.editOrigin,
144
+ ...args,
145
+ });
109
146
 
110
147
  next();
111
148
  },
@@ -3,8 +3,8 @@
3
3
  // - Update the user's journey context with the new data
4
4
  // - Remove validation date of JourneyContext so it can re-evaluted
5
5
 
6
- import JourneyContext from '../lib/JourneyContext.js';
7
- import { REQUEST_PHASE_GATHER } from '../lib/constants.js';
6
+ import JourneyContext from "../lib/JourneyContext.js";
7
+ import { REQUEST_PHASE_GATHER } from "../lib/constants.js";
8
8
 
9
9
  /**
10
10
  * @access private
@@ -19,13 +19,10 @@ import { REQUEST_PHASE_GATHER } from '../lib/constants.js';
19
19
  *
20
20
  * @param {object} obj Options
21
21
  * @param {string} obj.waypoint Waypoint
22
- * @param {PageField[]} [obj.fields=[]] Fields
22
+ * @param {PageField[]} [obj.fields] Fields
23
23
  * @returns {Array} Array of middleware
24
24
  */
25
- export default ({
26
- waypoint,
27
- fields = [],
28
- }) => [
25
+ export default ({ waypoint, fields = [] }) => [
29
26
  (req, res, next) => {
30
27
  // Store a copy of the journey context before modifying it. This is useful
31
28
  // for any comparison work that may be done in subsequent middleware.
@@ -39,7 +36,10 @@ export default ({
39
36
  /* eslint-disable security/detect-object-injection */
40
37
  const persistentBody = Object.create(null);
41
38
  for (let i = 0, l = fields.length; i < l; i++) {
42
- if (fields[i].meta.persist && fields[i].getValue(req.body) !== undefined) {
39
+ if (
40
+ fields[i].meta.persist &&
41
+ fields[i].getValue(req.body) !== undefined
42
+ ) {
43
43
  persistentBody[fields[i].name] = fields[i].getValue(req.body);
44
44
  }
45
45
  }
@@ -1,33 +1,38 @@
1
- import { createInstance } from 'i18next';
2
- import { LanguageDetector, handle } from 'i18next-http-middleware';
3
- import { resolve, basename } from 'path';
4
- import { existsSync, readFileSync, readdirSync } from 'fs';
5
- import deepmerge from 'deepmerge';
6
- import yaml from 'js-yaml';
7
- import logger from '../lib/logger.js';
1
+ import { createInstance } from "i18next";
2
+ import { LanguageDetector, handle } from "i18next-http-middleware";
3
+ import { resolve, basename } from "path";
4
+ import { existsSync, readFileSync, readdirSync } from "fs";
5
+ import deepmerge from "deepmerge";
6
+ import { load as jsYamlLoad } from "js-yaml";
7
+ import logger from "../lib/logger.js";
8
8
 
9
- const log = logger('middleware:i18n');
9
+ /**
10
+ * @typedef {import("express").RequestHandler} RequestHandler
11
+ * @access private
12
+ */
13
+
14
+ const log = logger("middleware:i18n");
10
15
 
11
16
  const loadJson = (file) => {
12
17
  // Strip out newlines (this is a legacy feature which we're keeping for
13
18
  // backwards compatibility).
14
19
  /* eslint-disable-next-line security/detect-non-literal-fs-filename */
15
- const json = readFileSync(file, 'utf8');
16
- return JSON.parse(json.replace(/[\r\n]/g, ''));
17
- }
20
+ const json = readFileSync(file, "utf8");
21
+ return JSON.parse(json.replace(/[\r\n]/g, ""));
22
+ };
18
23
 
19
24
  /* eslint-disable-next-line security/detect-non-literal-fs-filename */
20
- const loadYaml = (file) => yaml.load(readFileSync(file, 'utf8'))
25
+ const loadYaml = (file) => jsYamlLoad(readFileSync(file, "utf8"));
21
26
 
22
27
  const extract = (file) => {
23
- const ext = /.yaml$/i.test(file) ? '.yaml' : '.json';
24
- const data = ext === '.yaml' ? loadYaml(file) : loadJson(file);
28
+ const ext = /.yaml$/i.test(file) ? ".yaml" : ".json";
29
+ const data = ext === ".yaml" ? loadYaml(file) : loadJson(file);
25
30
 
26
31
  return {
27
32
  ns: basename(file, ext),
28
33
  data,
29
34
  };
30
- }
35
+ };
31
36
 
32
37
  const loadResources = (languages, directories) => {
33
38
  const store = Object.create(null);
@@ -45,7 +50,7 @@ const loadResources = (languages, directories) => {
45
50
  return;
46
51
  }
47
52
 
48
- log.info('Loading %s language from %s ...', language, dir);
53
+ log.info("Loading %s language from %s ...", language, dir);
49
54
  /* eslint-disable-next-line security/detect-non-literal-fs-filename */
50
55
  readdirSync(dir).forEach((file) => {
51
56
  const { ns, data } = extract(resolve(dir, file));
@@ -61,10 +66,18 @@ const loadResources = (languages, directories) => {
61
66
  });
62
67
 
63
68
  return store;
64
- }
65
-
69
+ };
70
+
71
+ /**
72
+ * Internationalisation middleware.
73
+ *
74
+ * @param {object} opts Options
75
+ * @param {string[]} [opts.languages] Language codes
76
+ * @param {string[]} [opts.directories] Source translations directories
77
+ * @returns {RequestHandler[]} Middleware functions
78
+ */
66
79
  export default function i18nMiddleware({
67
- languages = ['en', 'cy'],
80
+ languages = ["en", "cy"],
68
81
  directories = [],
69
82
  }) {
70
83
  // Load _all_ translations, from all directories into memory.
@@ -72,32 +85,29 @@ export default function i18nMiddleware({
72
85
 
73
86
  // Configure i18next
74
87
  const i18nInstance = createInstance();
75
- i18nInstance
76
- .use(LanguageDetector)
77
- .init({
78
- initImmediate: false, // because we need synchronous loading
79
- supportedLngs: languages,
80
- fallbackLng: false,
81
- defaultNS: 'common',
82
- // debug: true,
83
-
84
- // All translation resources
85
- resources,
86
-
87
- // LanguageDetector options
88
- detection: {
89
- lookupQuerystring: 'lang',
90
- lookupSession: 'language',
91
- order: ['querystring', 'session'],
92
- },
93
- });
88
+ i18nInstance.use(LanguageDetector).init({
89
+ initImmediate: false, // because we need synchronous loading
90
+ supportedLngs: languages,
91
+ fallbackLng: false,
92
+ defaultNS: "common",
93
+ // debug: true,
94
+
95
+ // All translation resources
96
+ resources,
97
+
98
+ // LanguageDetector options
99
+ detection: {
100
+ lookupQuerystring: "lang",
101
+ lookupSession: "language",
102
+ order: ["querystring", "session"],
103
+ },
104
+ });
94
105
 
95
106
  // 2 middleware: one to read/set the session language, and one to enhance the
96
107
  // req/res objects with i18n features
97
108
  return [
98
109
  (req, res, next) => {
99
110
  if (!req.session.language) {
100
- /* eslint-disable-next-line prefer-destructuring */
101
111
  req.session.language = languages[0];
102
112
  }
103
113
  if (req?.query.lang && languages.includes(req.query.lang)) {
@@ -1,61 +1,87 @@
1
1
  // 2 middleware: one as a fallback 404 handler, one to handle thrown errors
2
- import logger from '../lib/logger.js';
2
+ import logger from "../lib/logger.js";
3
3
 
4
- const log = logger('middleware:post');
4
+ /**
5
+ * @typedef {import("express").RequestHandler} RequestHandler
6
+ * @access private
7
+ */
5
8
 
9
+ const log = logger("middleware:post");
10
+
11
+ /** @returns {RequestHandler[]} Middleware functions */
6
12
  export default function postMiddleware() {
7
13
  return [
8
14
  (req, res) => {
9
- res.status(404).render('casa/errors/404.njk');
15
+ res.status(404).render("casa/errors/404.njk");
10
16
  },
11
17
  /* eslint-disable-next-line no-unused-vars */
12
18
  (err, req, res, next) => {
13
19
  // In some cases, an error may have been thrown before the template assets
14
20
  // have had a chance to initialise. So we use a hardcoded template in
15
21
  // these cases to ensure the user sees an appropriate message.
16
- let TEMPLATE = 'casa/errors/500.njk';
22
+ let TEMPLATE = "casa/errors/500.njk";
17
23
  if (!res.locals.t) {
18
- res.locals.t = () => ('');
24
+ res.locals.t = () => "";
19
25
  res.locals.casa = {
20
26
  ...res.locals?.casa,
21
27
  mountUrl: `${req.baseUrl}/`,
22
28
  };
23
- TEMPLATE = 'casa/errors/static.njk';
29
+ TEMPLATE = "casa/errors/static.njk";
24
30
  }
25
31
 
26
32
  // CSRF token is invalid in some way
27
- if (err?.code === 'EBADCSRFTOKEN') {
28
- log.info('CSRF validation has failed. This may be caused by the user submitting a stale form from a previous session [EBADCSRFTOKEN]');
29
- return res.status(403).render(TEMPLATE, { errorCode: 'bad_csrf_token', error: err });
33
+ if (err?.code === "EBADCSRFTOKEN") {
34
+ log.info(
35
+ "CSRF validation has failed. This may be caused by the user submitting a stale form from a previous session [EBADCSRFTOKEN]",
36
+ );
37
+ return res
38
+ .status(403)
39
+ .render(TEMPLATE, { errorCode: "bad_csrf_token", error: err });
30
40
  }
31
41
 
32
42
  // Body parsing verification check failed
33
- if (err?.type === 'entity.verify.failed') {
34
- log.info('Body parser verification has failed. This has been caused by the user submitting a payload containing invalid data [entity.verify.failed]');
35
- return res.status(403).render(TEMPLATE, { errorCode: 'invalid_payload', error: err });
43
+ if (err?.type === "entity.verify.failed") {
44
+ log.info(
45
+ "Body parser verification has failed. This has been caused by the user submitting a payload containing invalid data [entity.verify.failed]",
46
+ );
47
+ return res
48
+ .status(403)
49
+ .render(TEMPLATE, { errorCode: "invalid_payload", error: err });
36
50
  }
37
51
 
38
52
  // Too many parameters submitted
39
- if (err?.type === 'parameters.too.many') {
40
- log.info('The request contains more parameters than is currently allowed [parameters.too.many]');
41
- return res.status(413).render(TEMPLATE, { errorCode: 'parameter_limit_exceeded', error: err });
53
+ if (err?.type === "parameters.too.many") {
54
+ log.info(
55
+ "The request contains more parameters than is currently allowed [parameters.too.many]",
56
+ );
57
+ return res.status(413).render(TEMPLATE, {
58
+ errorCode: "parameter_limit_exceeded",
59
+ error: err,
60
+ });
42
61
  }
43
62
 
44
63
  // Overall payload too large
45
- if (err?.type === 'entity.too.large') {
46
- log.info(`The request payload is too large. Received ${err.length}b with a maximum of ${err.limit}b [parameters.too.many]`);
47
- return res.status(413).render(TEMPLATE, { errorCode: 'payload_size_exceeded', error: err });
64
+ if (err?.type === "entity.too.large") {
65
+ log.info(
66
+ `The request payload is too large. Received ${err.length}b with a maximum of ${err.limit}b [parameters.too.many]`,
67
+ );
68
+ return res
69
+ .status(413)
70
+ .render(TEMPLATE, { errorCode: "payload_size_exceeded", error: err });
48
71
  }
49
72
 
50
73
  // Unaccept request method
51
- if (err?.code === 'unaccepted_request_method') {
74
+ if (err?.code === "unaccepted_request_method") {
52
75
  log.info(err.message);
53
- return res.status(400).render(TEMPLATE, { errorCode: 'unaccepted_request_method', error: err });
76
+ return res.status(400).render(TEMPLATE, {
77
+ errorCode: "unaccepted_request_method",
78
+ error: err,
79
+ });
54
80
  }
55
81
 
56
82
  // Unknown error
57
83
  log.error(`Unknown error: ${err.message}; stacktrace: ${err.stack}`);
58
84
  return res.status(200).render(TEMPLATE, { error: err });
59
85
  },
60
- ]
86
+ ];
61
87
  }