@dwp/govuk-casa 8.15.0 → 9.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.
Files changed (201) hide show
  1. package/README.md +9 -9
  2. package/dist/assets/css/casa.css +2 -1
  3. package/dist/assets/css/casa.css.map +1 -0
  4. package/dist/casa.d.ts +122 -99
  5. package/dist/casa.js +120 -88
  6. package/dist/casa.js.map +1 -1
  7. package/dist/lib/CasaTemplateLoader.d.ts +4 -4
  8. package/dist/lib/CasaTemplateLoader.js +16 -16
  9. package/dist/lib/CasaTemplateLoader.js.map +1 -1
  10. package/dist/lib/JourneyContext.d.ts +38 -40
  11. package/dist/lib/JourneyContext.js +89 -75
  12. package/dist/lib/JourneyContext.js.map +1 -1
  13. package/dist/lib/MutableRouter.d.ts +40 -41
  14. package/dist/lib/MutableRouter.js +64 -71
  15. package/dist/lib/MutableRouter.js.map +1 -1
  16. package/dist/lib/Plan.d.ts +29 -26
  17. package/dist/lib/Plan.js +85 -71
  18. package/dist/lib/Plan.js.map +1 -1
  19. package/dist/lib/ValidationError.d.ts +16 -15
  20. package/dist/lib/ValidationError.js +21 -20
  21. package/dist/lib/ValidationError.js.map +1 -1
  22. package/dist/lib/ValidatorFactory.d.ts +15 -13
  23. package/dist/lib/ValidatorFactory.js +14 -12
  24. package/dist/lib/ValidatorFactory.js.map +1 -1
  25. package/dist/lib/configuration-ingestor.d.ts +37 -40
  26. package/dist/lib/configuration-ingestor.js +93 -93
  27. package/dist/lib/configuration-ingestor.js.map +1 -1
  28. package/dist/lib/configure.d.ts +6 -6
  29. package/dist/lib/configure.js +14 -12
  30. package/dist/lib/configure.js.map +1 -1
  31. package/dist/lib/constants.d.ts +1 -3
  32. package/dist/lib/constants.js +9 -11
  33. package/dist/lib/constants.js.map +1 -1
  34. package/dist/lib/context-id-generators.d.ts +3 -5
  35. package/dist/lib/context-id-generators.js +7 -6
  36. package/dist/lib/context-id-generators.js.map +1 -1
  37. package/dist/lib/end-session.d.ts +4 -4
  38. package/dist/lib/end-session.js +5 -5
  39. package/dist/lib/field.d.ts +24 -29
  40. package/dist/lib/field.js +41 -70
  41. package/dist/lib/field.js.map +1 -1
  42. package/dist/lib/index.d.ts +13 -13
  43. package/dist/lib/logger.d.ts +7 -6
  44. package/dist/lib/logger.js +7 -7
  45. package/dist/lib/logger.js.map +1 -1
  46. package/dist/lib/mount.d.ts +5 -5
  47. package/dist/lib/mount.js +12 -17
  48. package/dist/lib/mount.js.map +1 -1
  49. package/dist/lib/nunjucks-filters.d.ts +10 -12
  50. package/dist/lib/nunjucks-filters.js +35 -35
  51. package/dist/lib/nunjucks-filters.js.map +1 -1
  52. package/dist/lib/nunjucks.d.ts +7 -5
  53. package/dist/lib/nunjucks.js +10 -8
  54. package/dist/lib/nunjucks.js.map +1 -1
  55. package/dist/lib/utils.d.ts +19 -19
  56. package/dist/lib/utils.js +62 -55
  57. package/dist/lib/utils.js.map +1 -1
  58. package/dist/lib/validators/dateObject.d.ts +29 -22
  59. package/dist/lib/validators/dateObject.js +58 -49
  60. package/dist/lib/validators/dateObject.js.map +1 -1
  61. package/dist/lib/validators/email.d.ts +4 -4
  62. package/dist/lib/validators/email.js +4 -4
  63. package/dist/lib/validators/inArray.d.ts +4 -4
  64. package/dist/lib/validators/inArray.js +7 -8
  65. package/dist/lib/validators/inArray.js.map +1 -1
  66. package/dist/lib/validators/index.d.ts +10 -10
  67. package/dist/lib/validators/index.js +1 -3
  68. package/dist/lib/validators/index.js.map +1 -1
  69. package/dist/lib/validators/nino.d.ts +9 -8
  70. package/dist/lib/validators/nino.js +14 -10
  71. package/dist/lib/validators/nino.js.map +1 -1
  72. package/dist/lib/validators/postalAddressObject.d.ts +37 -24
  73. package/dist/lib/validators/postalAddressObject.js +65 -46
  74. package/dist/lib/validators/postalAddressObject.js.map +1 -1
  75. package/dist/lib/validators/range.d.ts +12 -8
  76. package/dist/lib/validators/range.js +11 -9
  77. package/dist/lib/validators/range.js.map +1 -1
  78. package/dist/lib/validators/regex.d.ts +4 -4
  79. package/dist/lib/validators/regex.js +5 -5
  80. package/dist/lib/validators/required.d.ts +6 -6
  81. package/dist/lib/validators/required.js +9 -11
  82. package/dist/lib/validators/required.js.map +1 -1
  83. package/dist/lib/validators/strlen.d.ts +12 -8
  84. package/dist/lib/validators/strlen.js +13 -11
  85. package/dist/lib/validators/strlen.js.map +1 -1
  86. package/dist/lib/validators/wordCount.d.ts +12 -8
  87. package/dist/lib/validators/wordCount.js +15 -11
  88. package/dist/lib/validators/wordCount.js.map +1 -1
  89. package/dist/lib/waypoint-url.d.ts +16 -13
  90. package/dist/lib/waypoint-url.js +39 -36
  91. package/dist/lib/waypoint-url.js.map +1 -1
  92. package/dist/middleware/body-parser.d.ts +1 -1
  93. package/dist/middleware/body-parser.js +6 -6
  94. package/dist/middleware/body-parser.js.map +1 -1
  95. package/dist/middleware/data.d.ts +1 -1
  96. package/dist/middleware/data.js +8 -7
  97. package/dist/middleware/data.js.map +1 -1
  98. package/dist/middleware/gather-fields.d.ts +2 -2
  99. package/dist/middleware/gather-fields.js +6 -4
  100. package/dist/middleware/gather-fields.js.map +1 -1
  101. package/dist/middleware/i18n.js +13 -15
  102. package/dist/middleware/i18n.js.map +1 -1
  103. package/dist/middleware/post.js +30 -18
  104. package/dist/middleware/post.js.map +1 -1
  105. package/dist/middleware/pre.d.ts +2 -2
  106. package/dist/middleware/pre.js +46 -26
  107. package/dist/middleware/pre.js.map +1 -1
  108. package/dist/middleware/progress-journey.d.ts +1 -1
  109. package/dist/middleware/progress-journey.js +5 -5
  110. package/dist/middleware/progress-journey.js.map +1 -1
  111. package/dist/middleware/sanitise-fields.d.ts +1 -1
  112. package/dist/middleware/sanitise-fields.js +13 -11
  113. package/dist/middleware/sanitise-fields.js.map +1 -1
  114. package/dist/middleware/serve-first-waypoint.d.ts +3 -3
  115. package/dist/middleware/serve-first-waypoint.js +8 -6
  116. package/dist/middleware/serve-first-waypoint.js.map +1 -1
  117. package/dist/middleware/session.js +14 -11
  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 +16 -14
  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 +1 -1
  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 +3 -3
  132. package/dist/routes/ancillary.js +4 -4
  133. package/dist/routes/ancillary.js.map +1 -1
  134. package/dist/routes/journey.d.ts +2 -2
  135. package/dist/routes/journey.js +91 -39
  136. package/dist/routes/journey.js.map +1 -1
  137. package/dist/routes/static.d.ts +7 -5
  138. package/dist/routes/static.js +20 -20
  139. package/dist/routes/static.js.map +1 -1
  140. package/package.json +17 -16
  141. package/src/casa.js +134 -102
  142. package/src/lib/CasaTemplateLoader.js +24 -19
  143. package/src/lib/JourneyContext.js +147 -107
  144. package/src/lib/MutableRouter.js +72 -74
  145. package/src/lib/Plan.js +145 -97
  146. package/src/lib/ValidationError.js +25 -21
  147. package/src/lib/ValidatorFactory.js +17 -13
  148. package/src/lib/configuration-ingestor.js +147 -110
  149. package/src/lib/configure.js +34 -32
  150. package/src/lib/constants.js +9 -11
  151. package/src/lib/context-id-generators.js +40 -43
  152. package/src/lib/end-session.js +6 -6
  153. package/src/lib/field.js +74 -78
  154. package/src/lib/index.js +12 -12
  155. package/src/lib/logger.js +9 -9
  156. package/src/lib/mount.js +70 -80
  157. package/src/lib/nunjucks-filters.js +56 -59
  158. package/src/lib/nunjucks.js +23 -18
  159. package/src/lib/utils.js +78 -57
  160. package/src/lib/validators/dateObject.js +71 -60
  161. package/src/lib/validators/email.js +8 -8
  162. package/src/lib/validators/inArray.js +10 -11
  163. package/src/lib/validators/index.js +12 -14
  164. package/src/lib/validators/nino.js +29 -15
  165. package/src/lib/validators/postalAddressObject.js +87 -63
  166. package/src/lib/validators/range.js +14 -12
  167. package/src/lib/validators/regex.js +8 -8
  168. package/src/lib/validators/required.js +16 -16
  169. package/src/lib/validators/strlen.js +16 -14
  170. package/src/lib/validators/wordCount.js +22 -14
  171. package/src/lib/waypoint-url.js +64 -46
  172. package/src/middleware/body-parser.js +10 -10
  173. package/src/middleware/csrf.js +1 -1
  174. package/src/middleware/data.js +28 -24
  175. package/src/middleware/gather-fields.js +10 -9
  176. package/src/middleware/i18n.js +35 -37
  177. package/src/middleware/post.js +41 -21
  178. package/src/middleware/pre.js +62 -40
  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 +14 -12
  182. package/src/middleware/session.js +74 -61
  183. package/src/middleware/skip-waypoint.js +7 -9
  184. package/src/middleware/steer-journey.js +40 -28
  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 +5 -7
  188. package/src/routes/journey.js +159 -85
  189. package/src/routes/static.js +62 -30
  190. package/views/casa/components/character-count/README.md +2 -2
  191. package/views/casa/components/checkboxes/README.md +6 -6
  192. package/views/casa/components/date-input/README.md +7 -7
  193. package/views/casa/components/input/README.md +2 -2
  194. package/views/casa/components/journey-form/README.md +33 -14
  195. package/views/casa/components/postal-address-object/README.md +4 -4
  196. package/views/casa/components/radios/README.md +6 -6
  197. package/views/casa/components/select/README.md +6 -6
  198. package/views/casa/components/textarea/README.md +2 -2
  199. package/views/casa/partials/scripts.njk +5 -3
  200. package/views/casa/partials/styles.njk +1 -4
  201. package/dist/assets/css/casa-ie8.css +0 -1
@@ -1,15 +1,15 @@
1
- import { randomBytes } from 'crypto';
2
- import helmet from 'helmet';
1
+ import { randomBytes } from "crypto";
2
+ import helmet from "helmet";
3
3
 
4
4
  /**
5
+ * @typedef {import("../casa").HelmetConfigurator} HelmetConfigurator
5
6
  * @access private
6
- * @typedef {import('../casa').HelmetConfigurator} HelmetConfigurator
7
7
  */
8
8
 
9
- const GA_DOMAIN = '*.google-analytics.com';
10
- const GA_ANALYTICS_DOMAIN = '*.analytics.google.com';
11
- const GTM_DOMAIN = '*.googletagmanager.com';
12
- const GTM_PREVIEW_DOMAIN = 'https://tagmanager.google.com';
9
+ const GA_DOMAIN = "*.google-analytics.com";
10
+ const GA_ANALYTICS_DOMAIN = "*.analytics.google.com";
11
+ const GTM_DOMAIN = "*.googletagmanager.com";
12
+ const GTM_PREVIEW_DOMAIN = "https://tagmanager.google.com";
13
13
 
14
14
  /**
15
15
  * Extracts the CSP nonce used in every template, and makes it available as a
@@ -19,9 +19,9 @@ const GTM_PREVIEW_DOMAIN = 'https://tagmanager.google.com';
19
19
  * to identify this function specifically, most likely to remove it from CSP
20
20
  * headers for custom purposes.
21
21
  *
22
- * @param {import('express').Request} req Request
23
- * @param {import('express').Response} res Response
24
- * @returns {string} nonce value suitable for use in CSP header
22
+ * @param {import("express").Request} req Request
23
+ * @param {import("express").Response} res Response
24
+ * @returns {string} Nonce value suitable for use in CSP header
25
25
  */
26
26
  function casaCspNonce(req, res) {
27
27
  return `'nonce-${res.locals.cspNonce}'`;
@@ -31,17 +31,18 @@ function casaCspNonce(req, res) {
31
31
  * Pre middleware.
32
32
  *
33
33
  * @param {object} opts Options
34
- * @param {HelmetConfigurator} opts.helmetConfigurator Function to customise Helmet configuration
34
+ * @param {HelmetConfigurator} opts.helmetConfigurator Function to customise
35
+ * Helmet configuration
35
36
  * @returns {Function[]} List of middleware
36
37
  */
37
- export default ({
38
- helmetConfigurator = (config) => (config),
39
- } = {}) => [
38
+ export default ({ helmetConfigurator = (config) => config } = {}) => [
40
39
  // Only allow certain request methods
41
40
  (req, res, next) => {
42
- if (req.method !== 'GET' && req.method !== 'POST') {
43
- const err = new Error(`Unaccepted request method, "${String(req.method).substr(0, 7)}"`);
44
- err.code = 'unaccepted_request_method';
41
+ if (req.method !== "GET" && req.method !== "POST") {
42
+ const err = new Error(
43
+ `Unaccepted request method, "${String(req.method).substr(0, 7)}"`,
44
+ );
45
+ err.code = "unaccepted_request_method";
45
46
  next(err);
46
47
  } else {
47
48
  next();
@@ -53,39 +54,60 @@ export default ({
53
54
  // The `no-store` setting is to specifically disable the bfcache and prevent
54
55
  // possible leakage of information.
55
56
  (req, res, next) => {
56
- res.set('cache-control', 'no-cache, no-store, must-revalidate, private');
57
- res.set('pragma', 'no-cache');
58
- res.set('expires', 0);
59
- res.set('x-robots-tag', 'noindex, nofollow');
57
+ res.set("cache-control", "no-cache, no-store, must-revalidate, private");
58
+ res.set("pragma", "no-cache");
59
+ res.set("expires", 0);
60
+ res.set("x-robots-tag", "noindex, nofollow");
60
61
  next();
61
62
  },
62
63
 
63
64
  // Generate nonces ready for use in Content-Security-Policy header and
64
65
  // govuk-frontend template. This same none can be used wherever required.
65
66
  (req, res, next) => {
66
- res.locals.cspNonce = randomBytes(16).toString('hex');
67
+ res.locals.cspNonce = randomBytes(16).toString("hex");
67
68
  next();
68
69
  },
69
70
 
70
71
  // Helmet suite of headers
71
- helmet(helmetConfigurator({
72
- // Allows GA which is typically used, and a known inline script nonce
73
- contentSecurityPolicy: {
74
- useDefaults: true,
75
- directives: {
76
- 'default-src': ["'none'"],
77
- 'script-src': ["'self'", GA_DOMAIN, GTM_DOMAIN, GTM_PREVIEW_DOMAIN, casaCspNonce],
78
- 'img-src': ["'self'", GA_DOMAIN, GA_ANALYTICS_DOMAIN, GTM_DOMAIN, 'https://ssl.gstatic.com', 'https://www.gstatic.com'],
79
- 'connect-src': ["'self'", GA_DOMAIN, GA_ANALYTICS_DOMAIN, GTM_DOMAIN],
80
- 'frame-src': ["'self'", GTM_DOMAIN],
81
- 'frame-ancestors': ["'self'"],
82
- 'form-action': ["'self'"],
83
- 'style-src': ["'self'", 'https://fonts.googleapis.com', GTM_PREVIEW_DOMAIN, casaCspNonce],
84
- 'font-src': ["'self'", 'data:', 'https://fonts.gstatic.com'],
72
+ helmet(
73
+ helmetConfigurator({
74
+ // Allows GA which is typically used, and a known inline script nonce
75
+ contentSecurityPolicy: {
76
+ useDefaults: true,
77
+ directives: {
78
+ "default-src": ["'none'"],
79
+ "script-src": [
80
+ "'self'",
81
+ GA_DOMAIN,
82
+ GTM_DOMAIN,
83
+ GTM_PREVIEW_DOMAIN,
84
+ casaCspNonce,
85
+ ],
86
+ "img-src": [
87
+ "'self'",
88
+ GA_DOMAIN,
89
+ GA_ANALYTICS_DOMAIN,
90
+ GTM_DOMAIN,
91
+ "https://ssl.gstatic.com",
92
+ "https://www.gstatic.com",
93
+ ],
94
+ "connect-src": ["'self'", GA_DOMAIN, GA_ANALYTICS_DOMAIN, GTM_DOMAIN],
95
+ "frame-src": ["'self'", GTM_DOMAIN],
96
+ "frame-ancestors": ["'self'"],
97
+ "form-action": ["'self'"],
98
+ "style-src": [
99
+ "'self'",
100
+ "https://fonts.googleapis.com",
101
+ GTM_PREVIEW_DOMAIN,
102
+ casaCspNonce,
103
+ ],
104
+ "font-src": ["'self'", "data:", "https://fonts.gstatic.com"],
105
+ "manifest-src": ["'self'"],
106
+ },
85
107
  },
86
- },
87
108
 
88
- // // Require referrer to aid navigation
89
- // referrerPolicy: 'no-referrer, same-origin',
90
- })),
109
+ // // Require referrer to aid navigation
110
+ // referrerPolicy: 'no-referrer, same-origin',
111
+ }),
112
+ ),
91
113
  ];
@@ -2,13 +2,13 @@
2
2
  // We assume that the waypoint has been validated prior to reaching this
3
3
  // middleware.
4
4
 
5
- import Plan from '../lib/Plan.js';
6
- import JourneyContext from '../lib/JourneyContext.js';
7
- import waypointUrl from '../lib/waypoint-url.js';
8
- import logger from '../lib/logger.js';
9
- import { REQUEST_PHASE_REDIRECT } from '../lib/constants.js';
5
+ import Plan from "../lib/Plan.js";
6
+ import JourneyContext from "../lib/JourneyContext.js";
7
+ import waypointUrl from "../lib/waypoint-url.js";
8
+ import logger from "../lib/logger.js";
9
+ import { REQUEST_PHASE_REDIRECT } from "../lib/constants.js";
10
10
 
11
- const log = logger('middleware:progress-journey');
11
+ const log = logger("middleware:progress-journey");
12
12
 
13
13
  const saveAndRedirect = (session, journeyContext, url, res, next) => {
14
14
  JourneyContext.putContext(session, journeyContext, {
@@ -25,10 +25,7 @@ const saveAndRedirect = (session, journeyContext, url, res, next) => {
25
25
  });
26
26
  };
27
27
 
28
- export default ({
29
- waypoint,
30
- plan,
31
- }) => [
28
+ export default ({ waypoint, plan }) => [
32
29
  (req, res, next) => {
33
30
  // Determine the next available waypoint after the current one
34
31
  const traversed = plan.traverse(req.casa.journeyContext);
@@ -38,7 +35,9 @@ export default ({
38
35
  Math.min(currentIndex + 1, traversed.length - 1),
39
36
  );
40
37
  const nextWaypoint = traversed[parseInt(nextIndex, 10)];
41
- log.trace(`currentIndex = ${currentIndex}, nextIndex = ${nextIndex}, currentWaypoint = ${waypoint}, nextWaypoint = ${nextWaypoint}`);
38
+ log.trace(
39
+ `currentIndex = ${currentIndex}, nextIndex = ${nextIndex}, currentWaypoint = ${waypoint}, nextWaypoint = ${nextWaypoint}`,
40
+ );
42
41
 
43
42
  // Edit mode
44
43
  // Attempt to take the user back to their original URL. We rely on the
@@ -55,14 +54,21 @@ export default ({
55
54
  // they want to force the user to re-visit particular waypoints during this
56
55
  // "jumping" phase.
57
56
  if (req.casa.editMode && req.casa.editOrigin) {
58
- const url = new URL(req.casa.editOrigin, 'https://placeholder.test/');
59
- url.searchParams.append('edit', 'true');
60
- url.searchParams.append('editorigin', req.casa.editOrigin);
61
- const redirectUrl = waypointUrl({ waypoint: url.pathname }) + url.search.toString();
57
+ const url = new URL(req.casa.editOrigin, "https://placeholder.test/");
58
+ url.searchParams.append("edit", "true");
59
+ url.searchParams.append("editorigin", req.casa.editOrigin);
60
+ const redirectUrl =
61
+ waypointUrl({ waypoint: url.pathname }) + url.search.toString();
62
62
 
63
63
  log.debug(`Edit mode detected; redirecting to ${redirectUrl}`);
64
64
 
65
- return saveAndRedirect(req.session, req.casa.journeyContext, redirectUrl, res, next);
65
+ return saveAndRedirect(
66
+ req.session,
67
+ req.casa.journeyContext,
68
+ redirectUrl,
69
+ res,
70
+ next,
71
+ );
66
72
  }
67
73
 
68
74
  // If the next URL is an "exit node", we need to flag that node as
@@ -76,7 +82,9 @@ export default ({
76
82
  // setRoute('b', 'url:///otherapp/')
77
83
  // setRoute('url:////otherapp/', 'c', (r, c) => checkIfOtherAppIsFinished())
78
84
  if (Plan.isExitNode(nextWaypoint)) {
79
- log.trace(`Next waypoint is an exit node; clearing validation state on ${nextWaypoint}`);
85
+ log.trace(
86
+ `Next waypoint is an exit node; clearing validation state on ${nextWaypoint}`,
87
+ );
80
88
  req.casa.journeyContext.clearValidationErrorsForPage(nextWaypoint);
81
89
  }
82
90
 
@@ -91,6 +99,12 @@ export default ({
91
99
 
92
100
  // Save and move on
93
101
  log.trace(`Redirecting to ${nextUrl}`);
94
- return saveAndRedirect(req.session, req.casa.journeyContext, nextUrl, res, next);
102
+ return saveAndRedirect(
103
+ req.session,
104
+ req.casa.journeyContext,
105
+ nextUrl,
106
+ res,
107
+ next,
108
+ );
95
109
  },
96
110
  ];
@@ -2,19 +2,32 @@
2
2
  // - Coerce each field to its correct type
3
3
  // - Remove an extraneous fields that are not know to the application
4
4
 
5
- import _ from 'lodash';
6
- import fieldFactory from '../lib/field.js';
7
- import JourneyContext from '../lib/JourneyContext.js';
5
+ import _ from "lodash";
6
+ import fieldFactory from "../lib/field.js";
7
+ import JourneyContext from "../lib/JourneyContext.js";
8
8
 
9
- export default ({
10
- waypoint,
11
- fields = [],
12
- }) => {
9
+ export default ({ waypoint, fields = [] }) => {
13
10
  // Add some common, transient fields to ensure they survive beyond this sanitisation process
14
- fields.push(fieldFactory('_csrf', { persist: false }).processor((value) => String(value)));
15
- fields.push(fieldFactory('contextid', { persist: false }).processor((value) => String(value)));
16
- fields.push(fieldFactory('edit', { persist: false }).processor((value) => String(value)));
17
- fields.push(fieldFactory('editorigin', { persist: false }).processor((value) => String(value)));
11
+ fields.push(
12
+ fieldFactory("_csrf", { persist: false }).processor((value) =>
13
+ String(value),
14
+ ),
15
+ );
16
+ fields.push(
17
+ fieldFactory("contextid", { persist: false }).processor((value) =>
18
+ String(value),
19
+ ),
20
+ );
21
+ fields.push(
22
+ fieldFactory("edit", { persist: false }).processor((value) =>
23
+ String(value),
24
+ ),
25
+ );
26
+ fields.push(
27
+ fieldFactory("editorigin", { persist: false }).processor((value) =>
28
+ String(value),
29
+ ),
30
+ );
18
31
 
19
32
  // Middleware
20
33
  return [
@@ -25,27 +38,37 @@ export default ({
25
38
  /* eslint-disable security/detect-object-injection */
26
39
  const prunedBody = Object.create(null);
27
40
  for (let i = 0, l = fields.length; i < l; i++) {
28
- if (_.has(req.body, fields[i].name) && req.body[fields[i].name] !== undefined) {
41
+ if (
42
+ _.has(req.body, fields[i].name) &&
43
+ req.body[fields[i].name] !== undefined
44
+ ) {
29
45
  prunedBody[fields[i].name] = req.body[fields[i].name];
30
46
  }
31
47
  }
32
48
  /* eslint-enable security/detect-object-injection */
33
49
 
34
- const journeyContext = JourneyContext.fromContext(req.casa.journeyContext, req);
50
+ const journeyContext = JourneyContext.fromContext(
51
+ req.casa.journeyContext,
52
+ req,
53
+ );
35
54
  journeyContext.setDataForPage(waypoint, prunedBody);
36
55
 
37
56
  // Second, prune any fields that do not pass the validation conditional,
38
57
  // and process those that do.
39
58
  const sanitisedBody = Object.create(null);
40
59
  for (let i = 0, l = fields.length; i < l; i++) {
41
- const field = fields[i]; /* eslint-disable-line security/detect-object-injection */
60
+ const field =
61
+ fields[i]; /* eslint-disable-line security/detect-object-injection */
42
62
  const fieldValue = field.getValue(prunedBody);
43
63
 
44
- if (fieldValue !== undefined && field.testConditions({
45
- fieldValue,
46
- waypoint,
47
- journeyContext,
48
- })) {
64
+ if (
65
+ fieldValue !== undefined &&
66
+ field.testConditions({
67
+ fieldValue,
68
+ waypoint,
69
+ journeyContext,
70
+ })
71
+ ) {
49
72
  field.putValue(sanitisedBody, field.applyProcessors(fieldValue));
50
73
  }
51
74
  }
@@ -55,4 +78,4 @@ export default ({
55
78
  next();
56
79
  },
57
80
  ];
58
- }
81
+ };
@@ -1,13 +1,13 @@
1
- import { validateUrlPath } from '../lib/utils.js';
1
+ import { validateUrlPath } from "../lib/utils.js";
2
2
 
3
3
  /**
4
+ * @typedef {import("express").RequestHandler} ExpressRequestHandler
4
5
  * @access private
5
- * @typedef {import('express').RequestHandler} ExpressRequestHandler
6
6
  */
7
7
 
8
8
  /**
9
+ * @typedef {import("../casa").Plan} Plan
9
10
  * @access private
10
- * @typedef {import('../casa').Plan} Plan
11
11
  */
12
12
 
13
13
  /**
@@ -17,12 +17,14 @@ import { validateUrlPath } from '../lib/utils.js';
17
17
  * @param {Plan} plan CASA Plan
18
18
  * @returns {ExpressRequestHandler[]} Array of middleware
19
19
  */
20
- export default ({
21
- plan,
22
- }) => [(req, res) => {
23
- const reqUrl = new URL(req.url, 'https://placeholder.test/');
24
- const reqPath = validateUrlPath(`${req.baseUrl}${reqUrl.pathname}${plan.getWaypoints()[0]}`);
25
- let reqParams = reqUrl.searchParams.toString();
26
- reqParams = reqParams ? `?${reqParams}` : '';
27
- res.redirect(302, `${reqPath}${reqParams}`);
28
- }];
20
+ export default ({ plan }) => [
21
+ (req, res) => {
22
+ const reqUrl = new URL(req.url, "https://placeholder.test/");
23
+ const reqPath = validateUrlPath(
24
+ `${req.baseUrl}${reqUrl.pathname}${plan.getWaypoints()[0]}`,
25
+ );
26
+ let reqParams = reqUrl.searchParams.toString();
27
+ reqParams = reqParams ? `?${reqParams}` : "";
28
+ res.redirect(302, `${reqPath}${reqParams}`);
29
+ },
30
+ ];
@@ -1,64 +1,70 @@
1
1
  // A last-modified cookie is used to control whether the end user sees a
2
2
  // session-timeout page, or they are simply given a new session without
3
3
  // interrupting their journey.
4
- import expressSession, { MemoryStore } from 'express-session';
5
- import logger from '../lib/logger.js';
6
- import { validateUrlPath } from '../lib/utils.js';
4
+ import expressSession, { MemoryStore } from "express-session";
5
+ import logger from "../lib/logger.js";
6
+ import { validateUrlPath } from "../lib/utils.js";
7
7
 
8
- const log = logger('middleware:session');
8
+ const log = logger("middleware:session");
9
9
 
10
- const sessionExpiryMiddleware = (
11
- ttl,
12
- getCookie,
13
- touchCookie,
14
- removeCookie,
15
- ) => (req, res, next) => {
16
- const lastModified = getCookie(req);
17
- const age = Math.floor(Date.now() * 0.001) - lastModified;
10
+ const sessionExpiryMiddleware =
11
+ (ttl, getCookie, touchCookie, removeCookie) => (req, res, next) => {
12
+ const lastModified = getCookie(req);
13
+ const age = Math.floor(Date.now() * 0.001) - lastModified;
18
14
 
19
- if (lastModified === 0) {
20
- // New session, or grace period cookie no longer available after
21
- // expiring; generate a new session, and create grace-period cookie.
22
- // This will invalidate any CSRF tokens, so by letting the request POST
23
- // requests through the user may see a 500 error response.
24
- log.info('Session is new, or grace period has expired. Regenerating session.');
25
- req.session.regenerate((err) => {
26
- if (err) {
27
- next(err);
28
- } else {
29
- touchCookie(res);
30
- if (req.method === 'POST') {
31
- log.info('The CSRF token for this POST request will now be invalid for this regenerated session. Redirecting to app mount point.');
32
- res.redirect(302, validateUrlPath(`${req.baseUrl}/`));
15
+ if (lastModified === 0) {
16
+ // New session, or grace period cookie no longer available after
17
+ // expiring; generate a new session, and create grace-period cookie.
18
+ // This will invalidate any CSRF tokens, so by letting the request POST
19
+ // requests through the user may see a 500 error response.
20
+ log.info(
21
+ "Session is new, or grace period has expired. Regenerating session.",
22
+ );
23
+ req.session.regenerate((err) => {
24
+ if (err) {
25
+ next(err);
33
26
  } else {
34
- next();
27
+ touchCookie(res);
28
+ if (req.method === "POST") {
29
+ log.info(
30
+ "The CSRF token for this POST request will now be invalid for this regenerated session. Redirecting to app mount point.",
31
+ );
32
+ res.redirect(302, validateUrlPath(`${req.baseUrl}/`));
33
+ } else {
34
+ next();
35
+ }
35
36
  }
36
- }
37
- });
38
- } else if (age > ttl) {
39
- // Cookie has become stale and server session will have been removed;
40
- // redirect to session-timeout
41
- log.info('Session has timed out within grace period. Destroying session and redirecting to timeout page.');
42
- const language = req.session.language ?? 'en';
43
- req.session.destroy((err) => {
44
- if (err) {
45
- next(err);
46
- } else {
47
- removeCookie(res);
48
- const params = new URLSearchParams({
49
- referrer: req.originalUrl,
50
- lang: language,
51
- });
52
- /* eslint-disable-next-line prefer-template */
53
- res.redirect(302, validateUrlPath(`${req.baseUrl}/session-timeout`) + `?${params.toString()}`);
54
- }
55
- });
56
- } else {
57
- // Touch cookie and continue
58
- touchCookie(res);
59
- next();
60
- }
61
- };
37
+ });
38
+ } else if (age > ttl) {
39
+ // Cookie has become stale and server session will have been removed;
40
+ // redirect to session-timeout
41
+ log.info(
42
+ "Session has timed out within grace period. Destroying session and redirecting to timeout page.",
43
+ );
44
+ const language = req.session.language ?? "en";
45
+ req.session.destroy((err) => {
46
+ if (err) {
47
+ next(err);
48
+ } else {
49
+ removeCookie(res);
50
+ const params = new URLSearchParams({
51
+ referrer: req.originalUrl,
52
+ lang: language,
53
+ });
54
+ /* eslint-disable-next-line prefer-template */
55
+ res.redirect(
56
+ 302,
57
+ validateUrlPath(`${req.baseUrl}/session-timeout`) +
58
+ `?${params.toString()}`,
59
+ );
60
+ }
61
+ });
62
+ } else {
63
+ // Touch cookie and continue
64
+ touchCookie(res);
65
+ next();
66
+ }
67
+ };
62
68
 
63
69
  // 3 middleware:
64
70
  // - set the session cookie
@@ -71,7 +77,7 @@ export default function sessionMiddleware({
71
77
  secure,
72
78
  ttl,
73
79
  cookieSameSite = true,
74
- cookiePath = '/',
80
+ cookiePath = "/",
75
81
  store = new MemoryStore(),
76
82
  }) {
77
83
  const commonCookieOptions = {
@@ -81,7 +87,8 @@ export default function sessionMiddleware({
81
87
  };
82
88
 
83
89
  if (cookieSameSite !== false) {
84
- commonCookieOptions.sameSite = cookieSameSite === true ? 'Strict' : cookieSameSite;
90
+ commonCookieOptions.sameSite =
91
+ cookieSameSite === true ? "Strict" : cookieSameSite;
85
92
  }
86
93
 
87
94
  const ttlGrace = 1800; // user will see session-timeout if session expires within 30mins
@@ -94,22 +101,28 @@ export default function sessionMiddleware({
94
101
 
95
102
  const getCookie = (req) => {
96
103
  // Disabled eslint as `touchCookieName` is a constant, known value
97
- /* eslint-disable-next-line security/detect-object-injection */
98
- const lastModified = Date.parse(String(req.signedCookies[touchCookieName] ?? '1970-01-01T00:00:00+0000'));
104
+ const lastModified = Date.parse(
105
+ /* eslint-disable-next-line security/detect-object-injection */
106
+ String(req.signedCookies[touchCookieName] ?? "1970-01-01T00:00:00+0000"),
107
+ );
99
108
  return Number.isNaN(lastModified) ? 0 : Math.floor(lastModified * 0.001);
100
- }
109
+ };
101
110
 
102
111
  const touchCookie = (res) => {
103
112
  // Touch cookie expiry is a short period after the session ttl. This gives
104
113
  // a small period of time where a user will see the session-timeout message,
105
114
  // which is important to avoid the confusion of simply being redirected back
106
115
  // to the start of their journey.
107
- res.cookie(touchCookieName, (new Date(Date.now())).toUTCString(), touchCookieOptions);
108
- }
116
+ res.cookie(
117
+ touchCookieName,
118
+ new Date(Date.now()).toUTCString(),
119
+ touchCookieOptions,
120
+ );
121
+ };
109
122
 
110
123
  const removeCookie = (res) => {
111
124
  res.clearCookie(touchCookieName, touchCookieOptions);
112
- }
125
+ };
113
126
 
114
127
  return [
115
128
  expressSession({
@@ -1,19 +1,17 @@
1
1
  // Mark a waypoint as skipped
2
2
 
3
- import lodash from 'lodash';
4
- import JourneyContext from '../lib/JourneyContext.js';
5
- import waypointUrl from '../lib/waypoint-url.js';
6
- import logger from '../lib/logger.js';
3
+ import lodash from "lodash";
4
+ import JourneyContext from "../lib/JourneyContext.js";
5
+ import waypointUrl from "../lib/waypoint-url.js";
6
+ import logger from "../lib/logger.js";
7
7
 
8
8
  const { has } = lodash;
9
9
 
10
- const log = logger('middleware:skip-waypoint');
10
+ const log = logger("middleware:skip-waypoint");
11
11
 
12
- export default ({
13
- waypoint,
14
- }) => [
12
+ export default ({ waypoint }) => [
15
13
  (req, res, next) => {
16
- if (!has(req.query, 'skipto')) {
14
+ if (!has(req.query, "skipto")) {
17
15
  return next();
18
16
  }
19
17
  const skipTo = String(req.query.skipto);