@dwp/govuk-casa 8.16.1 → 8.16.3

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 +44 -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 +2 -2
  138. package/dist/routes/static.js +18 -18
  139. package/dist/routes/static.js.map +1 -1
  140. package/package.json +39 -42
  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 +60 -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 +61 -28
@@ -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
5
  * @access private
6
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
@@ -34,14 +34,14 @@ function casaCspNonce(req, res) {
34
34
  * @param {HelmetConfigurator} opts.helmetConfigurator Function to customise Helmet configuration
35
35
  * @returns {Function[]} List of middleware
36
36
  */
37
- export default ({
38
- helmetConfigurator = (config) => (config),
39
- } = {}) => [
37
+ export default ({ helmetConfigurator = (config) => config } = {}) => [
40
38
  // Only allow certain request methods
41
39
  (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';
40
+ if (req.method !== "GET" && req.method !== "POST") {
41
+ const err = new Error(
42
+ `Unaccepted request method, "${String(req.method).substr(0, 7)}"`,
43
+ );
44
+ err.code = "unaccepted_request_method";
45
45
  next(err);
46
46
  } else {
47
47
  next();
@@ -53,39 +53,64 @@ export default ({
53
53
  // The `no-store` setting is to specifically disable the bfcache and prevent
54
54
  // possible leakage of information.
55
55
  (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');
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");
60
60
  next();
61
61
  },
62
62
 
63
63
  // Generate nonces ready for use in Content-Security-Policy header and
64
64
  // govuk-frontend template. This same none can be used wherever required.
65
65
  (req, res, next) => {
66
- res.locals.cspNonce = randomBytes(16).toString('hex');
66
+ res.locals.cspNonce = randomBytes(16).toString("hex");
67
67
  next();
68
68
  },
69
69
 
70
70
  // 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', 'https://fonts.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, GTM_DOMAIN, casaCspNonce, "'sha256-xWGOGGMGQQ+IV0Om4xzgbDHXUh/+L1c375p0Pb6vF9A='", "'sha256-9HGruJg4WccHXas5I1NmLn7tI1TDh6N26o6+/dy8sm4='", "'sha256-oM0kKtU+nugIwjuYHkXXVoKGVNhC/DCUnIVdSVBMkaQ='"],
84
- 'font-src': ["'self'", 'data:', 'https://fonts.gstatic.com'],
71
+ helmet(
72
+ helmetConfigurator({
73
+ // Allows GA which is typically used, and a known inline script nonce
74
+ contentSecurityPolicy: {
75
+ useDefaults: true,
76
+ directives: {
77
+ "default-src": ["'none'"],
78
+ "script-src": [
79
+ "'self'",
80
+ GA_DOMAIN,
81
+ GTM_DOMAIN,
82
+ GTM_PREVIEW_DOMAIN,
83
+ casaCspNonce,
84
+ ],
85
+ "img-src": [
86
+ "'self'",
87
+ GA_DOMAIN,
88
+ GA_ANALYTICS_DOMAIN,
89
+ GTM_DOMAIN,
90
+ "https://ssl.gstatic.com",
91
+ "https://www.gstatic.com",
92
+ "https://fonts.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
+ GTM_DOMAIN,
103
+ casaCspNonce,
104
+ "'sha256-xWGOGGMGQQ+IV0Om4xzgbDHXUh/+L1c375p0Pb6vF9A='",
105
+ "'sha256-9HGruJg4WccHXas5I1NmLn7tI1TDh6N26o6+/dy8sm4='",
106
+ "'sha256-oM0kKtU+nugIwjuYHkXXVoKGVNhC/DCUnIVdSVBMkaQ='",
107
+ ],
108
+ "font-src": ["'self'", "data:", "https://fonts.gstatic.com"],
109
+ },
85
110
  },
86
- },
87
111
 
88
- // // Require referrer to aid navigation
89
- // referrerPolicy: 'no-referrer, same-origin',
90
- })),
112
+ // // Require referrer to aid navigation
113
+ // referrerPolicy: 'no-referrer, same-origin',
114
+ }),
115
+ ),
91
116
  ];
@@ -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,4 +1,4 @@
1
- import { validateUrlPath } from '../lib/utils.js';
1
+ import { validateUrlPath } from "../lib/utils.js";
2
2
 
3
3
  /**
4
4
  * @access private
@@ -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,69 +1,94 @@
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
+ /**
9
+ * @typedef {import("express").RequestHandler} RequestHandler
10
+ * @access private
11
+ */
9
12
 
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;
13
+ const log = logger("middleware:session");
14
+
15
+ const sessionExpiryMiddleware =
16
+ (ttl, getCookie, touchCookie, removeCookie) => (req, res, next) => {
17
+ const lastModified = getCookie(req);
18
+ const age = Math.floor(Date.now() * 0.001) - lastModified;
18
19
 
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}/`));
20
+ if (lastModified === 0) {
21
+ // New session, or grace period cookie no longer available after
22
+ // expiring; generate a new session, and create grace-period cookie.
23
+ // This will invalidate any CSRF tokens, so by letting the request POST
24
+ // requests through the user may see a 500 error response.
25
+ log.info(
26
+ "Session is new, or grace period has expired. Regenerating session.",
27
+ );
28
+ req.session.regenerate((err) => {
29
+ if (err) {
30
+ next(err);
33
31
  } else {
34
- next();
32
+ touchCookie(res);
33
+ if (req.method === "POST") {
34
+ log.info(
35
+ "The CSRF token for this POST request will now be invalid for this regenerated session. Redirecting to app mount point.",
36
+ );
37
+ res.redirect(302, validateUrlPath(`${req.baseUrl}/`));
38
+ } else {
39
+ next();
40
+ }
35
41
  }
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
- };
42
+ });
43
+ } else if (age > ttl) {
44
+ // Cookie has become stale and server session will have been removed;
45
+ // redirect to session-timeout
46
+ log.info(
47
+ "Session has timed out within grace period. Destroying session and redirecting to timeout page.",
48
+ );
49
+ const language = req.session.language ?? "en";
50
+ req.session.destroy((err) => {
51
+ if (err) {
52
+ next(err);
53
+ } else {
54
+ removeCookie(res);
55
+ const params = new URLSearchParams({
56
+ referrer: req.originalUrl,
57
+ lang: language,
58
+ });
59
+
60
+ res.redirect(
61
+ 302,
62
+ validateUrlPath(`${req.baseUrl}/session-timeout`) +
63
+ `?${params.toString()}`,
64
+ );
65
+ }
66
+ });
67
+ } else {
68
+ // Touch cookie and continue
69
+ touchCookie(res);
70
+ next();
71
+ }
72
+ };
62
73
 
63
- // 3 middleware:
64
- // - set the session cookie
65
- // - parse request cookies
66
- // - handle expiry of server-side session
74
+ /**
75
+ * Produces three middleware functions:
76
+ *
77
+ * - Set the session cookie
78
+ * - Parse request cookies
79
+ * - Handle expiry of server-side session
80
+ *
81
+ * @param {object} opts Options
82
+ * @param {RequestHandler} opts.cookieParserMiddleware Cookie parsing middleware
83
+ * @param {string} opts.secret Session encryption secret
84
+ * @param {string} opts.name Session cookie name
85
+ * @param {boolean} opts.secure Secure cookies only
86
+ * @param {number} opts.ttl Session data time-to-live
87
+ * @param {boolean | string} [opts.cookieSameSite] Cooke SameSite setting
88
+ * @param {string} [opts.cookiePath] Cookie path
89
+ * @param {object} [opts.store] Storage instance
90
+ * @returns {RequestHandler[]} Middleware functions
91
+ */
67
92
  export default function sessionMiddleware({
68
93
  cookieParserMiddleware,
69
94
  secret,
@@ -71,7 +96,7 @@ export default function sessionMiddleware({
71
96
  secure,
72
97
  ttl,
73
98
  cookieSameSite = true,
74
- cookiePath = '/',
99
+ cookiePath = "/",
75
100
  store = new MemoryStore(),
76
101
  }) {
77
102
  const commonCookieOptions = {
@@ -81,7 +106,8 @@ export default function sessionMiddleware({
81
106
  };
82
107
 
83
108
  if (cookieSameSite !== false) {
84
- commonCookieOptions.sameSite = cookieSameSite === true ? 'Strict' : cookieSameSite;
109
+ commonCookieOptions.sameSite =
110
+ cookieSameSite === true ? "Strict" : cookieSameSite;
85
111
  }
86
112
 
87
113
  const ttlGrace = 1800; // user will see session-timeout if session expires within 30mins
@@ -94,22 +120,28 @@ export default function sessionMiddleware({
94
120
 
95
121
  const getCookie = (req) => {
96
122
  // 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'));
123
+
124
+ const lastModified = Date.parse(
125
+ String(req.signedCookies[touchCookieName] ?? "1970-01-01T00:00:00+0000"),
126
+ );
99
127
  return Number.isNaN(lastModified) ? 0 : Math.floor(lastModified * 0.001);
100
- }
128
+ };
101
129
 
102
130
  const touchCookie = (res) => {
103
131
  // Touch cookie expiry is a short period after the session ttl. This gives
104
132
  // a small period of time where a user will see the session-timeout message,
105
133
  // which is important to avoid the confusion of simply being redirected back
106
134
  // to the start of their journey.
107
- res.cookie(touchCookieName, (new Date(Date.now())).toUTCString(), touchCookieOptions);
108
- }
135
+ res.cookie(
136
+ touchCookieName,
137
+ new Date(Date.now()).toUTCString(),
138
+ touchCookieOptions,
139
+ );
140
+ };
109
141
 
110
142
  const removeCookie = (res) => {
111
143
  res.clearCookie(touchCookieName, touchCookieOptions);
112
- }
144
+ };
113
145
 
114
146
  return [
115
147
  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);