@dwp/govuk-casa 9.3.5 → 9.4.1

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 (134) hide show
  1. package/dist/casa.d.ts +2 -1
  2. package/dist/casa.js +20 -8
  3. package/dist/casa.js.map +1 -1
  4. package/dist/core-plugins/edit-snapshot/src/configure.d.ts +6 -0
  5. package/dist/core-plugins/edit-snapshot/src/configure.js +30 -0
  6. package/dist/core-plugins/edit-snapshot/src/configure.js.map +1 -0
  7. package/dist/core-plugins/edit-snapshot/src/index.d.ts +5 -0
  8. package/dist/core-plugins/edit-snapshot/src/index.js +12 -0
  9. package/dist/core-plugins/edit-snapshot/src/index.js.map +1 -0
  10. package/dist/core-plugins/edit-snapshot/src/post-steer-hook.d.ts +2 -0
  11. package/dist/core-plugins/edit-snapshot/src/post-steer-hook.js +23 -0
  12. package/dist/core-plugins/edit-snapshot/src/post-steer-hook.js.map +1 -0
  13. package/dist/core-plugins/edit-snapshot/src/pre-steer-hook.d.ts +2 -0
  14. package/dist/core-plugins/edit-snapshot/src/pre-steer-hook.js +33 -0
  15. package/dist/core-plugins/edit-snapshot/src/pre-steer-hook.js.map +1 -0
  16. package/dist/core-plugins/edit-snapshot/src/utils.d.ts +6 -0
  17. package/dist/core-plugins/edit-snapshot/src/utils.js +38 -0
  18. package/dist/core-plugins/edit-snapshot/src/utils.js.map +1 -0
  19. package/dist/core-plugins/index.d.ts +1 -0
  20. package/dist/core-plugins/index.js +10 -0
  21. package/dist/core-plugins/index.js.map +1 -0
  22. package/dist/lib/CasaTemplateLoader.js +0 -1
  23. package/dist/lib/CasaTemplateLoader.js.map +1 -1
  24. package/dist/lib/JourneyContext.d.ts +11 -6
  25. package/dist/lib/JourneyContext.js +17 -9
  26. package/dist/lib/JourneyContext.js.map +1 -1
  27. package/dist/lib/MutableRouter.d.ts +1 -1
  28. package/dist/lib/MutableRouter.js +0 -1
  29. package/dist/lib/MutableRouter.js.map +1 -1
  30. package/dist/lib/Plan.d.ts +3 -3
  31. package/dist/lib/ValidationError.d.ts +1 -1
  32. package/dist/lib/ValidationError.js +1 -1
  33. package/dist/lib/ValidatorFactory.js +0 -3
  34. package/dist/lib/ValidatorFactory.js.map +1 -1
  35. package/dist/lib/configuration-ingestor.d.ts +75 -14
  36. package/dist/lib/configuration-ingestor.js +119 -26
  37. package/dist/lib/configuration-ingestor.js.map +1 -1
  38. package/dist/lib/configure.js.map +1 -1
  39. package/dist/lib/context-id-generators.js +1 -1
  40. package/dist/lib/context-id-generators.js.map +1 -1
  41. package/dist/lib/field.d.ts +8 -8
  42. package/dist/lib/field.js +6 -10
  43. package/dist/lib/field.js.map +1 -1
  44. package/dist/lib/index.js +17 -7
  45. package/dist/lib/index.js.map +1 -1
  46. package/dist/lib/nunjucks-filters.d.ts +5 -1
  47. package/dist/lib/nunjucks-filters.js +13 -1
  48. package/dist/lib/nunjucks-filters.js.map +1 -1
  49. package/dist/lib/utils.js +8 -5
  50. package/dist/lib/utils.js.map +1 -1
  51. package/dist/lib/validators/dateObject.d.ts +1 -1
  52. package/dist/lib/validators/dateObject.js +0 -2
  53. package/dist/lib/validators/dateObject.js.map +1 -1
  54. package/dist/lib/validators/email.js +1 -2
  55. package/dist/lib/validators/email.js.map +1 -1
  56. package/dist/lib/validators/inArray.js +0 -1
  57. package/dist/lib/validators/inArray.js.map +1 -1
  58. package/dist/lib/validators/nino.js +0 -1
  59. package/dist/lib/validators/nino.js.map +1 -1
  60. package/dist/lib/validators/postalAddressObject.js +5 -5
  61. package/dist/lib/validators/postalAddressObject.js.map +1 -1
  62. package/dist/lib/validators/range.js +0 -1
  63. package/dist/lib/validators/range.js.map +1 -1
  64. package/dist/lib/validators/regex.js +0 -1
  65. package/dist/lib/validators/regex.js.map +1 -1
  66. package/dist/lib/validators/required.js +0 -1
  67. package/dist/lib/validators/required.js.map +1 -1
  68. package/dist/lib/validators/strlen.js +0 -1
  69. package/dist/lib/validators/strlen.js.map +1 -1
  70. package/dist/lib/validators/wordCount.js +0 -1
  71. package/dist/lib/validators/wordCount.js.map +1 -1
  72. package/dist/lib/waypoint-url.d.ts +4 -5
  73. package/dist/lib/waypoint-url.js +4 -5
  74. package/dist/lib/waypoint-url.js.map +1 -1
  75. package/dist/middleware/body-parser.d.ts +26 -4
  76. package/dist/middleware/body-parser.js +31 -0
  77. package/dist/middleware/body-parser.js.map +1 -1
  78. package/dist/middleware/csrf.d.ts +15 -1
  79. package/dist/middleware/csrf.js +13 -3
  80. package/dist/middleware/csrf.js.map +1 -1
  81. package/dist/middleware/data.d.ts +21 -4
  82. package/dist/middleware/data.js +33 -4
  83. package/dist/middleware/data.js.map +1 -1
  84. package/dist/middleware/gather-fields.js +1 -1
  85. package/dist/middleware/i18n.d.ts +11 -2
  86. package/dist/middleware/i18n.js +14 -3
  87. package/dist/middleware/i18n.js.map +1 -1
  88. package/dist/middleware/post.d.ts +3 -1
  89. package/dist/middleware/post.js +5 -0
  90. package/dist/middleware/post.js.map +1 -1
  91. package/dist/middleware/session.d.ts +27 -8
  92. package/dist/middleware/session.js +39 -12
  93. package/dist/middleware/session.js.map +1 -1
  94. package/dist/mjs/esm-wrapper.js +1 -0
  95. package/dist/routes/journey.js +2 -3
  96. package/dist/routes/journey.js.map +1 -1
  97. package/package.json +33 -33
  98. package/src/casa.js +4 -0
  99. package/src/core-plugins/edit-snapshot/src/configure.js +30 -0
  100. package/src/core-plugins/edit-snapshot/src/index.js +7 -0
  101. package/src/core-plugins/edit-snapshot/src/post-steer-hook.js +22 -0
  102. package/src/core-plugins/edit-snapshot/src/pre-steer-hook.js +43 -0
  103. package/src/core-plugins/edit-snapshot/src/utils.js +53 -0
  104. package/src/core-plugins/index.js +2 -0
  105. package/src/lib/CasaTemplateLoader.js +0 -1
  106. package/src/lib/JourneyContext.js +23 -11
  107. package/src/lib/MutableRouter.js +0 -1
  108. package/src/lib/ValidationError.js +1 -1
  109. package/src/lib/ValidatorFactory.js +0 -3
  110. package/src/lib/configuration-ingestor.js +116 -19
  111. package/src/lib/configure.js +2 -2
  112. package/src/lib/context-id-generators.js +1 -1
  113. package/src/lib/field.js +7 -10
  114. package/src/lib/nunjucks-filters.js +15 -1
  115. package/src/lib/utils.js +9 -5
  116. package/src/lib/validators/dateObject.js +1 -2
  117. package/src/lib/validators/email.js +1 -2
  118. package/src/lib/validators/inArray.js +0 -1
  119. package/src/lib/validators/nino.js +0 -1
  120. package/src/lib/validators/postalAddressObject.js +5 -5
  121. package/src/lib/validators/range.js +0 -2
  122. package/src/lib/validators/regex.js +0 -1
  123. package/src/lib/validators/required.js +0 -1
  124. package/src/lib/validators/strlen.js +0 -1
  125. package/src/lib/validators/wordCount.js +0 -1
  126. package/src/lib/waypoint-url.js +4 -5
  127. package/src/middleware/body-parser.js +34 -0
  128. package/src/middleware/csrf.js +13 -3
  129. package/src/middleware/data.js +37 -5
  130. package/src/middleware/gather-fields.js +1 -1
  131. package/src/middleware/i18n.js +15 -3
  132. package/src/middleware/post.js +6 -0
  133. package/src/middleware/session.js +24 -5
  134. package/src/routes/journey.js +6 -4
@@ -0,0 +1,30 @@
1
+ import logger from "../../../lib/logger.js";
2
+ import preSteerHook from "./pre-steer-hook.js";
3
+ import postSteerHook from "./post-steer-hook.js";
4
+
5
+ /**
6
+ * @typedef {import("../../../casa.js").ConfigurationOptions} ConfigurationOptions
7
+ * @access private
8
+ */
9
+
10
+ const log = logger("lib:internal-plugin:edit-snapshot");
11
+
12
+ /**
13
+ * @param {ConfigurationOptions} config Configuration object
14
+ * @returns {void}
15
+ */
16
+ export default function (config) {
17
+ log.info("Configuring 'edit-snapshot' plugin");
18
+
19
+ config.hooks ??= [];
20
+ config.hooks.push(
21
+ {
22
+ hook: "journey.presteer",
23
+ middleware: preSteerHook,
24
+ },
25
+ {
26
+ hook: "journey.poststeer",
27
+ middleware: postSteerHook,
28
+ },
29
+ );
30
+ }
@@ -0,0 +1,7 @@
1
+ import configure from "./configure.js";
2
+
3
+ export default () => {
4
+ return {
5
+ configure,
6
+ };
7
+ };
@@ -0,0 +1,22 @@
1
+ import logger from "../../../lib/logger.js";
2
+ import { FLAG_FOR_PURGING, deleteSnapshot } from "./utils.js";
3
+
4
+ const log = logger("lib:internal-plugin:edit-snapshot:post-steer-hook");
5
+
6
+ export default (req, res, next) => {
7
+ // Snapshot purging is carried out here rather than in the presteer hook,
8
+ // because the `middleware/steer-journey.js` middleware first needs the opportunity
9
+ // to redirect if the journey cannot be traversed to the edit origin.
10
+ // eslint-disable-next-line security/detect-object-injection
11
+ if (req.casa[FLAG_FOR_PURGING] === true) {
12
+ log.debug(
13
+ `Snapshot purging flag has been set for context '${req.casa.journeyContext.identity.id}'. Snapshot will be deleted.`,
14
+ );
15
+ deleteSnapshot(log, req);
16
+ // eslint-disable-next-line security/detect-object-injection
17
+ req.casa[FLAG_FOR_PURGING] = false;
18
+ return req.session.save(next);
19
+ }
20
+
21
+ next();
22
+ };
@@ -0,0 +1,43 @@
1
+ import logger from "../../../lib/logger.js";
2
+ import {
3
+ FLAG_FOR_PURGING,
4
+ snapshotExists,
5
+ recoverSnapshot,
6
+ createSnapshot,
7
+ reachedEditOrigin,
8
+ } from "./utils.js";
9
+
10
+ const log = logger("lib:internal-plugin:edit-snapshot:pre-steer-hook");
11
+
12
+ export default (req, res, next) => {
13
+ if (req.query.editcancel && snapshotExists(req)) {
14
+ log.debug(
15
+ "Edit workflow was actively canceled. Snapshot will be recovered.",
16
+ );
17
+ recoverSnapshot(log, req);
18
+ return req.session.save(next);
19
+ }
20
+
21
+ if (!req.casa.editMode && snapshotExists(req)) {
22
+ log.debug("Edit workflow passively canceled. Snapshot will be recovered.");
23
+ recoverSnapshot(log, req);
24
+ return req.session.save(next);
25
+ }
26
+
27
+ if (req.casa.editMode && reachedEditOrigin(log, req)) {
28
+ log.trace(
29
+ `Editing workflow has reached editorigin. Purge MAY be carried out in poststeer hook on context '${req.casa.journeyContext.identity.id}'.`,
30
+ );
31
+ // eslint-disable-next-line security/detect-object-injection
32
+ req.casa[FLAG_FOR_PURGING] = true;
33
+ return next();
34
+ }
35
+
36
+ if (req.casa.editMode && !snapshotExists(req)) {
37
+ log.debug("Editing workflow has just started. Will create snapshot.");
38
+ createSnapshot(log, req);
39
+ return req.session.save(next);
40
+ }
41
+
42
+ next();
43
+ };
@@ -0,0 +1,53 @@
1
+ /* eslint-disable security/detect-object-injection */
2
+ import { JourneyContext } from "../../../casa.js";
3
+
4
+ const SESSION_KEY = "casa_edit_snapshots";
5
+
6
+ export const FLAG_FOR_PURGING = Symbol("flag_to_purge_edit_snapshot");
7
+
8
+ export const snapshotExists = (req) => {
9
+ return (
10
+ req?.session[SESSION_KEY] &&
11
+ Object.hasOwn(req.session[SESSION_KEY], req.casa.journeyContext.identity.id)
12
+ );
13
+ };
14
+
15
+ export const reachedEditOrigin = (log, req) => {
16
+ const { pathname: currentPathname } = new URL(
17
+ req.originalUrl,
18
+ "https://placeholder.test/",
19
+ );
20
+ const { pathname: editOriginPathname } = new URL(
21
+ req.casa.editOrigin,
22
+ "https://placeholder.test/",
23
+ );
24
+
25
+ return editOriginPathname === currentPathname;
26
+ };
27
+
28
+ export const createSnapshot = (log, req) => {
29
+ log.debug(
30
+ `Creating a new edit snapshot for context '${req.casa.journeyContext.identity.id}'`,
31
+ );
32
+ req.session[SESSION_KEY] ??= Object.create(null);
33
+ req.session[SESSION_KEY][req.casa.journeyContext.identity.id] =
34
+ req.casa.journeyContext.toObject();
35
+ };
36
+
37
+ export const recoverSnapshot = (log, req) => {
38
+ log.debug(
39
+ `Recovering snapshot for context '${req.casa.journeyContext.identity.id}'`,
40
+ );
41
+ req.casa.journeyContext.configureFromObject(
42
+ req.session[SESSION_KEY][req.casa.journeyContext.identity.id],
43
+ );
44
+ JourneyContext.putContext(req.session, req.casa.journeyContext);
45
+ deleteSnapshot(log, req);
46
+ };
47
+
48
+ export const deleteSnapshot = (log, req) => {
49
+ log.debug(
50
+ `Purging edit snapshot for context '${req.casa.journeyContext.identity.id}'`,
51
+ );
52
+ req.session[SESSION_KEY][req.casa.journeyContext.identity.id] = undefined;
53
+ };
@@ -0,0 +1,2 @@
1
+ // Convenience for exporting all plugins as "corePlugins" from main casa module
2
+ export { default as editSnapshot } from "./edit-snapshot/src/index.js";
@@ -97,7 +97,6 @@ export default class CasaTemplateLoader extends FileSystemLoader {
97
97
  /* eslint-disable-next-line security/detect-object-injection */
98
98
  const { block, modifier } = this.#blockModifiers[i];
99
99
  if (source.src.indexOf(`block ${block}`) > -1) {
100
- /* eslint-disable-next-line no-param-reassign */
101
100
  source.src = source.src.replace(
102
101
  `block ${block} %}`,
103
102
  `block ${block} %}${modifier(name)}`,
@@ -51,6 +51,10 @@ const clone = rfdc({ proto: false });
51
51
  * @access private
52
52
  */
53
53
 
54
+ /**
55
+ * @param {any} key Object key to validate
56
+ * @returns {string} Validated key
57
+ */
54
58
  export function validateObjectKey(key = "") {
55
59
  const keyLower = String.prototype.toLowerCase.call(key);
56
60
  if (
@@ -163,6 +167,13 @@ export default class JourneyContext {
163
167
  return new JourneyContext(data, deserialisedValidation, nav, identity);
164
168
  }
165
169
 
170
+ configureFromObject(object) {
171
+ const source = JourneyContext.fromObject(object);
172
+ this.#data = source.data;
173
+ this.#validation = source.validation;
174
+ this.#nav = source.nav;
175
+ }
176
+
166
177
  get data() {
167
178
  return this.#data;
168
179
  }
@@ -262,7 +273,7 @@ export default class JourneyContext {
262
273
  * @returns {JourneyContext} Chain.
263
274
  */
264
275
  removeValidationStateForPage(pageId) {
265
- /* eslint-disable-next-line no-unused-vars */
276
+ /* eslint-disable-next-line sonarjs/no-unused-vars,no-unused-vars */
266
277
  const { [pageId]: dummy, ...remaining } = this.#validation;
267
278
  this.#validation = { ...remaining };
268
279
  return this;
@@ -560,14 +571,14 @@ export default class JourneyContext {
560
571
  log.trace(
561
572
  "Session context list already initialised as an object (legacy structure). Will convert from object to array.",
562
573
  );
563
- /* eslint-disable-next-line no-param-reassign */
574
+
564
575
  session.journeyContextList = Object.entries(session.journeyContextList);
565
576
  }
566
577
 
567
578
  // Initialise new context list in the session
568
579
  if (!Object.hasOwn(session, "journeyContextList")) {
569
580
  log.trace("Initialising session with a default journey context list");
570
- /* eslint-disable-next-line no-param-reassign */
581
+
571
582
  session.journeyContextList = [];
572
583
 
573
584
  const defaultContext = new JourneyContext();
@@ -690,7 +701,7 @@ export default class JourneyContext {
690
701
  const list = new Map(session?.journeyContextList);
691
702
  if (list.has(id)) {
692
703
  // ESLint disabled as `id` has been verified as an "own" property
693
- /* eslint-disable-next-line security/detect-object-injection */
704
+
694
705
  return JourneyContext.fromObject(list.get(id));
695
706
  }
696
707
 
@@ -792,7 +803,7 @@ export default class JourneyContext {
792
803
 
793
804
  const list = new Map(session.journeyContextList);
794
805
  list.set(context.identity.id, context.toObject());
795
- /* eslint-disable-next-line no-param-reassign */
806
+
796
807
  session.journeyContextList = [...list.entries()];
797
808
  }
798
809
 
@@ -919,6 +930,7 @@ export default class JourneyContext {
919
930
  * @param {string} opts.to Waypoint to skip to.
920
931
  */
921
932
  setSkipped(waypoint, opts) {
933
+ /* eslint-disable security/detect-object-injection */
922
934
  // Unset, with setSkipped(a, false)
923
935
  if (opts === false) {
924
936
  this.data[waypoint] ??= Object.create(null);
@@ -941,25 +953,25 @@ export default class JourneyContext {
941
953
  `setSkipped opts must be a boolean or object with a "to" prop of waypoint to skip to, got: ${typeof opts}`,
942
954
  );
943
955
  }
956
+ /* eslint-enable security/detect-object-injection */
944
957
  }
945
958
 
946
959
  /**
947
960
  * Tests if a page has been skipped.
948
961
  *
949
- * @param {string} page Page ID (waypoint).
962
+ * @param {string} waypoint Page ID (waypoint).
950
963
  * @param {object} opts Skip ptions.
951
964
  * @param {string} opts.to Waypoint that should be skipped to.
952
965
  * @returns {boolean} True if the page has been skipped, or if it has been
953
966
  * skipped to a specific page.
954
967
  */
955
968
  isSkipped(waypoint, opts) {
969
+ const wpData = this.data[String(waypoint)];
970
+
956
971
  if (opts === undefined) {
957
- return (
958
- this.data[waypoint]?.__skipped__ === true ||
959
- this.data[waypoint]?.__skip__ !== undefined
960
- );
972
+ return wpData?.__skipped__ === true || wpData?.__skip__ !== undefined;
961
973
  } else if (typeof opts.to === "string") {
962
- return this.data[waypoint]?.__skip__?.to === opts.to;
974
+ return wpData?.__skip__?.to === opts.to;
963
975
  }
964
976
  }
965
977
  }
@@ -1,4 +1,3 @@
1
- /* eslint-disable sonarjs/no-duplicate-string,class-methods-use-this */
2
1
  import { Router } from "express";
3
2
 
4
3
  /** @memberof module:@dwp/govuk-casa */
@@ -41,7 +41,7 @@ export default class ValidationError {
41
41
  * @param {object} args Arguments
42
42
  * @param {ErrorMessageConfig} args.errorMsg Error message config to seed
43
43
  * ValidationError
44
- * @param {ValidateContext} [args.dataContext={}] Data for error msg function.
44
+ * @param {ValidateContext} [args.dataContext] Data for error msg function.
45
45
  * Default is `{}`
46
46
  * @returns {ValidationError} Error instance
47
47
  * @throws {TypeError} If errorMsg is not in a valid type
@@ -1,4 +1,3 @@
1
- /* eslint-disable class-methods-use-this */
2
1
  import lodash from "lodash";
3
2
 
4
3
  const { isPlainObject } = lodash; // CommonJS
@@ -54,7 +53,6 @@ export default class ValidatorFactory {
54
53
 
55
54
  const validator = Reflect.construct(this, [config]);
56
55
 
57
- /* eslint-disable-next-line sonarjs/prefer-object-literal */
58
56
  const instance = {};
59
57
  instance.name = validator.name || "unknown";
60
58
  instance.config = config;
@@ -96,7 +94,6 @@ export default class ValidatorFactory {
96
94
  throw new Error("validate() method has not been implemented");
97
95
  }
98
96
 
99
- /* eslint-disable-next-line jsdoc/require-returns-check */
100
97
  /**
101
98
  * Sanitise the given value.
102
99
  *
@@ -1,4 +1,3 @@
1
- /* eslint-disable sonarjs/no-duplicate-string */
2
1
  import bytes from "bytes";
3
2
  import { PageField } from "./field.js";
4
3
  import Plan from "./Plan.js";
@@ -25,6 +24,41 @@ import {
25
24
  * @access private
26
25
  */
27
26
 
27
+ /**
28
+ * @typedef {import("../casa").Page} Page
29
+ * @access private
30
+ */
31
+
32
+ /**
33
+ * @typedef {import("../casa").PageField} PageField
34
+ * @access private
35
+ */
36
+
37
+ /**
38
+ * @typedef {import("../casa").PageHook} PageHook
39
+ * @access private
40
+ */
41
+
42
+ /**
43
+ * @typedef {import("../casa").GlobalHook} GlobalHook
44
+ * @access private
45
+ */
46
+
47
+ /**
48
+ * @typedef {import("../casa").IPlugin} IPlugin
49
+ * @access private
50
+ */
51
+
52
+ /**
53
+ * @typedef {import("../casa").ContextEventHandler} ContextEventHandler
54
+ * @access private
55
+ */
56
+
57
+ /**
58
+ * @typedef {import("../casa").ContextIdGenerator} ContextIdGenerator
59
+ * @access private
60
+ */
61
+
28
62
  const log = logger("lib:configuration-ingestor");
29
63
 
30
64
  const echo = (a) => a;
@@ -58,11 +92,12 @@ export function validateI18nDirs(dirs = []) {
58
92
  if (!Array.isArray(dirs)) {
59
93
  throw new TypeError("I18n directories must be an array (i18n.dirs)");
60
94
  }
61
- for (let i = 0; i < dirs.length; i++) {
62
- const dir = dirs[i];
95
+
96
+ let i = 0;
97
+ for (const dir of dirs) {
63
98
  if (typeof dir !== "string") {
64
99
  throw new TypeError(
65
- `I18n directory must be a string, got ${typeof dir} (i18n.dirs[${i}])`,
100
+ `I18n directory must be a string, got ${typeof dir} (i18n.dirs[${i++}])`,
66
101
  );
67
102
  }
68
103
  }
@@ -82,11 +117,12 @@ export function validateI18nLocales(locales = ["en", "cy"]) {
82
117
  if (!Array.isArray(locales)) {
83
118
  throw new TypeError("I18n locales must be an array (i18n.locales)");
84
119
  }
85
- for (let i = 0; i < locales.length; i++) {
86
- const locale = locales[i];
120
+
121
+ let i = 0;
122
+ for (const locale of locales) {
87
123
  if (typeof locale !== "string") {
88
124
  throw new TypeError(
89
- `I18n locale must be a string, got ${typeof locale} (i18n.locales[${i}])`,
125
+ `I18n locale must be a string, got ${typeof locale} (i18n.locales[${i++}])`,
90
126
  );
91
127
  }
92
128
  }
@@ -148,11 +184,12 @@ export function validateViews(dirs = []) {
148
184
  if (!Array.isArray(dirs)) {
149
185
  throw new TypeError("View directories must be an array (views)");
150
186
  }
151
- for (let i = 0; i < dirs.length; i++) {
152
- const dir = dirs[i];
187
+
188
+ let i = 0;
189
+ for (const dir of dirs) {
153
190
  if (typeof dir !== "string") {
154
191
  throw new TypeError(
155
- `View directory must be a string, got ${typeof dir} (views[${i}])`,
192
+ `View directory must be a string, got ${typeof dir} (views[${i++}])`,
156
193
  );
157
194
  }
158
195
  }
@@ -196,7 +233,7 @@ export function validateSessionTtl(ttl = 3600) {
196
233
  /**
197
234
  * Validates and sanitises sessions name.
198
235
  *
199
- * @param {string} [name=casa-session] Session name. Default is `casa-session`
236
+ * @param {string} [name] Session name. Default is `casa-session`
200
237
  * @returns {string} Name.
201
238
  * @throws {ReferenceError} For missing value type.
202
239
  * @throws {TypeError} For invalid value.
@@ -300,6 +337,11 @@ export function validateErrorVisibility(
300
337
  );
301
338
  }
302
339
 
340
+ /**
341
+ * @param {boolean | string} cookieSameSite Cookie SameSite value
342
+ * @param {boolean | string} defaultFlag Default value
343
+ * @returns {boolean | string} Validated value
344
+ */
303
345
  export function validateSessionCookieSameSite(cookieSameSite, defaultFlag) {
304
346
  const validValues = [true, false, "Strict", "Lax", "None"];
305
347
 
@@ -335,12 +377,18 @@ const validatePageHook = (hook, index) => {
335
377
  }
336
378
  };
337
379
 
380
+ /**
381
+ * @param {PageHook[]} hooks Page hook functions
382
+ * @returns {PageHook[]} Validated page hooks
383
+ */
338
384
  export function validatePageHooks(hooks) {
339
385
  if (!Array.isArray(hooks)) {
340
386
  throw new TypeError("Hooks must be an array");
341
387
  }
342
- for (let i = 0; i < hooks.length; i++) {
343
- validatePageHook(hooks[i], i);
388
+
389
+ let i = 0;
390
+ for (const hook of hooks) {
391
+ validatePageHook(hook, i++);
344
392
  }
345
393
  return hooks;
346
394
  }
@@ -358,12 +406,18 @@ const validateField = (field, index) => {
358
406
  }
359
407
  };
360
408
 
409
+ /**
410
+ * @param {PageField[]} fields Page fields
411
+ * @returns {PageField[]} Validated fields
412
+ */
361
413
  export function validateFields(fields) {
362
414
  if (!Array.isArray(fields)) {
363
415
  throw new TypeError("Page fields must be an array (page[].fields)");
364
416
  }
365
- for (let i = 0; i < fields.length; i++) {
366
- validateField(fields[i], i);
417
+
418
+ let i = 0;
419
+ for (const field of fields) {
420
+ validateField(field, i++);
367
421
  }
368
422
  return fields;
369
423
  }
@@ -387,16 +441,26 @@ const validatePage = (page, index) => {
387
441
  }
388
442
  };
389
443
 
444
+ /**
445
+ * @param {Page[]} [pages] Pages
446
+ * @returns {Page[]} Validated pages
447
+ */
390
448
  export function validatePages(pages = []) {
391
449
  if (!Array.isArray(pages)) {
392
450
  throw new TypeError("Pages must be an array (pages)");
393
451
  }
394
- for (let i = 0; i < pages.length; i++) {
395
- validatePage(pages[i], i);
452
+
453
+ let i = 0;
454
+ for (const page of pages) {
455
+ validatePage(page, i++);
396
456
  }
397
457
  return pages;
398
458
  }
399
459
 
460
+ /**
461
+ * @param {Plan} plan Plan
462
+ * @returns {Plan} Validated plan
463
+ */
400
464
  export function validatePlan(plan) {
401
465
  if (plan === undefined) {
402
466
  return plan;
@@ -424,6 +488,10 @@ const validateGlobalHook = (hook, index) => {
424
488
  }
425
489
  };
426
490
 
491
+ /**
492
+ * @param {GlobalHook[]} hooks Global hook functions
493
+ * @returns {GlobalHook[]} Validated global hooks
494
+ */
427
495
  export function validateGlobalHooks(hooks) {
428
496
  if (hooks === undefined) {
429
497
  return [];
@@ -431,16 +499,26 @@ export function validateGlobalHooks(hooks) {
431
499
  if (!Array.isArray(hooks)) {
432
500
  throw new TypeError("Hooks must be an array");
433
501
  }
434
- for (let i = 0; i < hooks.length; i++) {
435
- validateGlobalHook(hooks[i], i);
502
+
503
+ let i = 0;
504
+ for (const hook of hooks) {
505
+ validateGlobalHook(hook, i++);
436
506
  }
437
507
  return hooks;
438
508
  }
439
509
 
510
+ /**
511
+ * @param {IPlugin[]} plugins Plugins
512
+ * @returns {IPlugin[]} Validated plugins
513
+ */
440
514
  export function validatePlugins(plugins) {
441
515
  return plugins;
442
516
  }
443
517
 
518
+ /**
519
+ * @param {ContextEventHandler[]} events Event handlers
520
+ * @returns {ContextEventHandler[]} Validated event handlers
521
+ */
444
522
  export function validateEvents(events) {
445
523
  return events;
446
524
  }
@@ -464,6 +542,13 @@ export function validateHelmetConfigurator(helmetConfigurator) {
464
542
  return helmetConfigurator;
465
543
  }
466
544
 
545
+ /**
546
+ * @param {number} value Max params value
547
+ * @param {number} [defaultValue] Default value
548
+ * @returns {number} Valid value
549
+ * @throws {TypeError} If not an integer
550
+ * @throws {RangeError} If out of bounds
551
+ */
467
552
  export function validateFormMaxParams(value, defaultValue = 25) {
468
553
  // CASA needs to send certain hidden form fields (see `sanitise-fields`
469
554
  // middleware), plus some padding here.
@@ -482,6 +567,13 @@ export function validateFormMaxParams(value, defaultValue = 25) {
482
567
  return value;
483
568
  }
484
569
 
570
+ /**
571
+ * @param {number} value Max bytes value
572
+ * @param {number} [defaultValue] Default value
573
+ * @returns {number} Valid value
574
+ * @throws {TypeError} If not an integer
575
+ * @throws {RangeError} If out of bounds
576
+ */
485
577
  export function validateFormMaxBytes(value, defaultValue = 1024 * 50) {
486
578
  const MIN_BYTES = 1024;
487
579
 
@@ -502,6 +594,11 @@ export function validateFormMaxBytes(value, defaultValue = 1024 * 50) {
502
594
  return parsedValue;
503
595
  }
504
596
 
597
+ /**
598
+ * @param {ContextIdGenerator} generator ID generator function
599
+ * @returns {ContextIdGenerator} Validated generator
600
+ * @throws {TypeError} If not a function
601
+ */
505
602
  export function validateContextIdGenerator(generator) {
506
603
  if (generator === undefined) {
507
604
  return contextIdGenerators.uuid();
@@ -87,7 +87,7 @@ export default function configure(config = {}) {
87
87
  for (const page of pages) {
88
88
  if (page?.hooks) {
89
89
  for (const h of page.hooks) {
90
- h.hook = `journey.${h.hook}`
90
+ h.hook = `journey.${h.hook}`;
91
91
  }
92
92
  }
93
93
  }
@@ -211,7 +211,7 @@ export default function configure(config = {}) {
211
211
 
212
212
  // Bootstrap all plugins
213
213
  for (const plugin of plugins.filter((p) => p.bootstrap)) {
214
- plugin?.bootstrap(configOutput)
214
+ plugin?.bootstrap(configOutput);
215
215
  }
216
216
 
217
217
  // Finished configuration
@@ -1,4 +1,3 @@
1
- /* eslint-disable import/no-cycle */
2
1
  import { randomUUID } from "node:crypto";
3
2
 
4
3
  /** @typedef {import("../casa.js").ContextIdGenerator} ContextIdGenerator */
@@ -50,6 +49,7 @@ const shortGuid =
50
49
  do {
51
50
  id = Array(length)
52
51
  .fill(0)
52
+ /* eslint-disable-next-line sonarjs/pseudo-random */
53
53
  .map(() => pool.charAt(Math.floor(Math.random() * poolSize)))
54
54
  .join("");
55
55
  attempts--;
package/src/lib/field.js CHANGED
@@ -66,9 +66,9 @@ export class PageField {
66
66
  *
67
67
  * @param {string} name Field name
68
68
  * @param {object} [opts] Options
69
- * @param {boolean} [opts.optional=false] Whether this field is optional.
70
- * Default is `false`
71
- * @param {boolean} [opts.persist=true] Whether this field will persist in
69
+ * @param {boolean} [opts.optional] Whether this field is optional. Default is
70
+ * `false`
71
+ * @param {boolean} [opts.persist] Whether this field will persist in
72
72
  * `req.body`. Default is `true`
73
73
  */
74
74
  constructor(
@@ -95,7 +95,7 @@ export class PageField {
95
95
  };
96
96
 
97
97
  // Apply name
98
- /* eslint-disable-next-line security/detect-non-literal-fs-filename */
98
+
99
99
  this.rename(name);
100
100
  }
101
101
 
@@ -153,13 +153,11 @@ export class PageField {
153
153
  */
154
154
  putValue(obj = Object.create(null), value = undefined) {
155
155
  if (this.#meta.complex) {
156
- /* eslint-disable-next-line no-param-reassign */
157
156
  obj[this.#meta.complexFieldName] = {
158
157
  ...(obj[this.#meta.complexFieldName] ?? {}),
159
158
  [this.#meta.complexFieldProperty]: value,
160
159
  };
161
160
  } else {
162
- /* eslint-disable-next-line no-param-reassign */
163
161
  obj[this.#name] = value;
164
162
  }
165
163
 
@@ -315,7 +313,6 @@ export class PageField {
315
313
  for (let i = 0, l = this.#validators.length; i < l; i++) {
316
314
  // ESLint disabled as `i` is an integer
317
315
  /* eslint-disable security/detect-object-injection */
318
- // TODO: Replace `value` with `context.fieldValue` here
319
316
  let fieldErrors = this.#validators[i].validate(
320
317
  context.fieldValue,
321
318
  context,
@@ -441,9 +438,9 @@ export class PageField {
441
438
  * @memberof module:@dwp/govuk-casa
442
439
  * @param {string} name Field name
443
440
  * @param {object} [opts] Options
444
- * @param {boolean} [opts.optional=false] Whether this field is optional.
445
- * Default is `false`
446
- * @param {boolean} [opts.persist=true] Whether this field will persist in
441
+ * @param {boolean} [opts.optional] Whether this field is optional. Default is
442
+ * `false`
443
+ * @param {boolean} [opts.persist] Whether this field will persist in
447
444
  * `req.body`. Default is `true`
448
445
  * @returns {PageField} A PageField
449
446
  */