@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.
- package/dist/casa.d.ts +2 -1
- package/dist/casa.js +20 -8
- package/dist/casa.js.map +1 -1
- package/dist/core-plugins/edit-snapshot/src/configure.d.ts +6 -0
- package/dist/core-plugins/edit-snapshot/src/configure.js +30 -0
- package/dist/core-plugins/edit-snapshot/src/configure.js.map +1 -0
- package/dist/core-plugins/edit-snapshot/src/index.d.ts +5 -0
- package/dist/core-plugins/edit-snapshot/src/index.js +12 -0
- package/dist/core-plugins/edit-snapshot/src/index.js.map +1 -0
- package/dist/core-plugins/edit-snapshot/src/post-steer-hook.d.ts +2 -0
- package/dist/core-plugins/edit-snapshot/src/post-steer-hook.js +23 -0
- package/dist/core-plugins/edit-snapshot/src/post-steer-hook.js.map +1 -0
- package/dist/core-plugins/edit-snapshot/src/pre-steer-hook.d.ts +2 -0
- package/dist/core-plugins/edit-snapshot/src/pre-steer-hook.js +33 -0
- package/dist/core-plugins/edit-snapshot/src/pre-steer-hook.js.map +1 -0
- package/dist/core-plugins/edit-snapshot/src/utils.d.ts +6 -0
- package/dist/core-plugins/edit-snapshot/src/utils.js +38 -0
- package/dist/core-plugins/edit-snapshot/src/utils.js.map +1 -0
- package/dist/core-plugins/index.d.ts +1 -0
- package/dist/core-plugins/index.js +10 -0
- package/dist/core-plugins/index.js.map +1 -0
- package/dist/lib/CasaTemplateLoader.js +0 -1
- package/dist/lib/CasaTemplateLoader.js.map +1 -1
- package/dist/lib/JourneyContext.d.ts +11 -6
- package/dist/lib/JourneyContext.js +17 -9
- package/dist/lib/JourneyContext.js.map +1 -1
- package/dist/lib/MutableRouter.d.ts +1 -1
- package/dist/lib/MutableRouter.js +0 -1
- package/dist/lib/MutableRouter.js.map +1 -1
- package/dist/lib/Plan.d.ts +3 -3
- package/dist/lib/ValidationError.d.ts +1 -1
- package/dist/lib/ValidationError.js +1 -1
- package/dist/lib/ValidatorFactory.js +0 -3
- package/dist/lib/ValidatorFactory.js.map +1 -1
- package/dist/lib/configuration-ingestor.d.ts +75 -14
- package/dist/lib/configuration-ingestor.js +119 -26
- package/dist/lib/configuration-ingestor.js.map +1 -1
- package/dist/lib/configure.js.map +1 -1
- package/dist/lib/context-id-generators.js +1 -1
- package/dist/lib/context-id-generators.js.map +1 -1
- package/dist/lib/field.d.ts +8 -8
- package/dist/lib/field.js +6 -10
- package/dist/lib/field.js.map +1 -1
- package/dist/lib/index.js +17 -7
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/nunjucks-filters.d.ts +5 -1
- package/dist/lib/nunjucks-filters.js +13 -1
- package/dist/lib/nunjucks-filters.js.map +1 -1
- package/dist/lib/utils.js +8 -5
- package/dist/lib/utils.js.map +1 -1
- package/dist/lib/validators/dateObject.d.ts +1 -1
- package/dist/lib/validators/dateObject.js +0 -2
- package/dist/lib/validators/dateObject.js.map +1 -1
- package/dist/lib/validators/email.js +1 -2
- package/dist/lib/validators/email.js.map +1 -1
- package/dist/lib/validators/inArray.js +0 -1
- package/dist/lib/validators/inArray.js.map +1 -1
- package/dist/lib/validators/nino.js +0 -1
- package/dist/lib/validators/nino.js.map +1 -1
- package/dist/lib/validators/postalAddressObject.js +5 -5
- package/dist/lib/validators/postalAddressObject.js.map +1 -1
- package/dist/lib/validators/range.js +0 -1
- package/dist/lib/validators/range.js.map +1 -1
- package/dist/lib/validators/regex.js +0 -1
- package/dist/lib/validators/regex.js.map +1 -1
- package/dist/lib/validators/required.js +0 -1
- package/dist/lib/validators/required.js.map +1 -1
- package/dist/lib/validators/strlen.js +0 -1
- package/dist/lib/validators/strlen.js.map +1 -1
- package/dist/lib/validators/wordCount.js +0 -1
- package/dist/lib/validators/wordCount.js.map +1 -1
- package/dist/lib/waypoint-url.d.ts +4 -5
- package/dist/lib/waypoint-url.js +4 -5
- package/dist/lib/waypoint-url.js.map +1 -1
- package/dist/middleware/body-parser.d.ts +26 -4
- package/dist/middleware/body-parser.js +31 -0
- package/dist/middleware/body-parser.js.map +1 -1
- package/dist/middleware/csrf.d.ts +15 -1
- package/dist/middleware/csrf.js +13 -3
- package/dist/middleware/csrf.js.map +1 -1
- package/dist/middleware/data.d.ts +21 -4
- package/dist/middleware/data.js +33 -4
- package/dist/middleware/data.js.map +1 -1
- package/dist/middleware/gather-fields.js +1 -1
- package/dist/middleware/i18n.d.ts +11 -2
- package/dist/middleware/i18n.js +14 -3
- package/dist/middleware/i18n.js.map +1 -1
- package/dist/middleware/post.d.ts +3 -1
- package/dist/middleware/post.js +5 -0
- package/dist/middleware/post.js.map +1 -1
- package/dist/middleware/session.d.ts +27 -8
- package/dist/middleware/session.js +39 -12
- package/dist/middleware/session.js.map +1 -1
- package/dist/mjs/esm-wrapper.js +1 -0
- package/dist/routes/journey.js +2 -3
- package/dist/routes/journey.js.map +1 -1
- package/package.json +33 -33
- package/src/casa.js +4 -0
- package/src/core-plugins/edit-snapshot/src/configure.js +30 -0
- package/src/core-plugins/edit-snapshot/src/index.js +7 -0
- package/src/core-plugins/edit-snapshot/src/post-steer-hook.js +22 -0
- package/src/core-plugins/edit-snapshot/src/pre-steer-hook.js +43 -0
- package/src/core-plugins/edit-snapshot/src/utils.js +53 -0
- package/src/core-plugins/index.js +2 -0
- package/src/lib/CasaTemplateLoader.js +0 -1
- package/src/lib/JourneyContext.js +23 -11
- package/src/lib/MutableRouter.js +0 -1
- package/src/lib/ValidationError.js +1 -1
- package/src/lib/ValidatorFactory.js +0 -3
- package/src/lib/configuration-ingestor.js +116 -19
- package/src/lib/configure.js +2 -2
- package/src/lib/context-id-generators.js +1 -1
- package/src/lib/field.js +7 -10
- package/src/lib/nunjucks-filters.js +15 -1
- package/src/lib/utils.js +9 -5
- package/src/lib/validators/dateObject.js +1 -2
- package/src/lib/validators/email.js +1 -2
- package/src/lib/validators/inArray.js +0 -1
- package/src/lib/validators/nino.js +0 -1
- package/src/lib/validators/postalAddressObject.js +5 -5
- package/src/lib/validators/range.js +0 -2
- package/src/lib/validators/regex.js +0 -1
- package/src/lib/validators/required.js +0 -1
- package/src/lib/validators/strlen.js +0 -1
- package/src/lib/validators/wordCount.js +0 -1
- package/src/lib/waypoint-url.js +4 -5
- package/src/middleware/body-parser.js +34 -0
- package/src/middleware/csrf.js +13 -3
- package/src/middleware/data.js +37 -5
- package/src/middleware/gather-fields.js +1 -1
- package/src/middleware/i18n.js +15 -3
- package/src/middleware/post.js +6 -0
- package/src/middleware/session.js +24 -5
- 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,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
|
+
};
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
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
|
|
974
|
+
return wpData?.__skip__?.to === opts.to;
|
|
963
975
|
}
|
|
964
976
|
}
|
|
965
977
|
}
|
package/src/lib/MutableRouter.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
152
|
-
|
|
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
|
|
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
|
-
|
|
343
|
-
|
|
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
|
-
|
|
366
|
-
|
|
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
|
-
|
|
395
|
-
|
|
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
|
-
|
|
435
|
-
|
|
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();
|
package/src/lib/configure.js
CHANGED
|
@@ -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
|
|
70
|
-
*
|
|
71
|
-
* @param {boolean} [opts.persist
|
|
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
|
-
|
|
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
|
|
445
|
-
*
|
|
446
|
-
* @param {boolean} [opts.persist
|
|
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
|
*/
|