@dwp/govuk-casa 9.4.0 → 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.js +17 -7
- package/dist/casa.js.map +1 -1
- package/dist/core-plugins/edit-snapshot/src/configure.d.ts +6 -1
- package/dist/core-plugins/edit-snapshot/src/configure.js +8 -0
- package/dist/core-plugins/edit-snapshot/src/configure.js.map +1 -1
- package/dist/core-plugins/edit-snapshot/src/post-steer-hook.js.map +1 -1
- package/dist/core-plugins/edit-snapshot/src/pre-steer-hook.js.map +1 -1
- package/dist/core-plugins/edit-snapshot/src/utils.js +4 -2
- package/dist/core-plugins/edit-snapshot/src/utils.js.map +1 -1
- package/dist/lib/CasaTemplateLoader.js +0 -1
- package/dist/lib/CasaTemplateLoader.js.map +1 -1
- package/dist/lib/JourneyContext.d.ts +10 -6
- package/dist/lib/JourneyContext.js +11 -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/routes/journey.js +2 -3
- package/dist/routes/journey.js.map +1 -1
- package/package.json +27 -30
- package/src/core-plugins/edit-snapshot/src/configure.js +19 -7
- package/src/core-plugins/edit-snapshot/src/post-steer-hook.js +3 -1
- package/src/core-plugins/edit-snapshot/src/pre-steer-hook.js +14 -4
- package/src/core-plugins/edit-snapshot/src/utils.js +19 -7
- package/src/lib/CasaTemplateLoader.js +0 -1
- package/src/lib/JourneyContext.js +16 -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
- package/src/core-plugins/edit-snapshot/readme.md +0 -19
- package/src/core-plugins/readme.md +0 -3
|
@@ -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 (
|
|
@@ -269,7 +273,7 @@ export default class JourneyContext {
|
|
|
269
273
|
* @returns {JourneyContext} Chain.
|
|
270
274
|
*/
|
|
271
275
|
removeValidationStateForPage(pageId) {
|
|
272
|
-
/* eslint-disable-next-line no-unused-vars */
|
|
276
|
+
/* eslint-disable-next-line sonarjs/no-unused-vars,no-unused-vars */
|
|
273
277
|
const { [pageId]: dummy, ...remaining } = this.#validation;
|
|
274
278
|
this.#validation = { ...remaining };
|
|
275
279
|
return this;
|
|
@@ -567,14 +571,14 @@ export default class JourneyContext {
|
|
|
567
571
|
log.trace(
|
|
568
572
|
"Session context list already initialised as an object (legacy structure). Will convert from object to array.",
|
|
569
573
|
);
|
|
570
|
-
|
|
574
|
+
|
|
571
575
|
session.journeyContextList = Object.entries(session.journeyContextList);
|
|
572
576
|
}
|
|
573
577
|
|
|
574
578
|
// Initialise new context list in the session
|
|
575
579
|
if (!Object.hasOwn(session, "journeyContextList")) {
|
|
576
580
|
log.trace("Initialising session with a default journey context list");
|
|
577
|
-
|
|
581
|
+
|
|
578
582
|
session.journeyContextList = [];
|
|
579
583
|
|
|
580
584
|
const defaultContext = new JourneyContext();
|
|
@@ -697,7 +701,7 @@ export default class JourneyContext {
|
|
|
697
701
|
const list = new Map(session?.journeyContextList);
|
|
698
702
|
if (list.has(id)) {
|
|
699
703
|
// ESLint disabled as `id` has been verified as an "own" property
|
|
700
|
-
|
|
704
|
+
|
|
701
705
|
return JourneyContext.fromObject(list.get(id));
|
|
702
706
|
}
|
|
703
707
|
|
|
@@ -799,7 +803,7 @@ export default class JourneyContext {
|
|
|
799
803
|
|
|
800
804
|
const list = new Map(session.journeyContextList);
|
|
801
805
|
list.set(context.identity.id, context.toObject());
|
|
802
|
-
|
|
806
|
+
|
|
803
807
|
session.journeyContextList = [...list.entries()];
|
|
804
808
|
}
|
|
805
809
|
|
|
@@ -926,6 +930,7 @@ export default class JourneyContext {
|
|
|
926
930
|
* @param {string} opts.to Waypoint to skip to.
|
|
927
931
|
*/
|
|
928
932
|
setSkipped(waypoint, opts) {
|
|
933
|
+
/* eslint-disable security/detect-object-injection */
|
|
929
934
|
// Unset, with setSkipped(a, false)
|
|
930
935
|
if (opts === false) {
|
|
931
936
|
this.data[waypoint] ??= Object.create(null);
|
|
@@ -948,25 +953,25 @@ export default class JourneyContext {
|
|
|
948
953
|
`setSkipped opts must be a boolean or object with a "to" prop of waypoint to skip to, got: ${typeof opts}`,
|
|
949
954
|
);
|
|
950
955
|
}
|
|
956
|
+
/* eslint-enable security/detect-object-injection */
|
|
951
957
|
}
|
|
952
958
|
|
|
953
959
|
/**
|
|
954
960
|
* Tests if a page has been skipped.
|
|
955
961
|
*
|
|
956
|
-
* @param {string}
|
|
962
|
+
* @param {string} waypoint Page ID (waypoint).
|
|
957
963
|
* @param {object} opts Skip ptions.
|
|
958
964
|
* @param {string} opts.to Waypoint that should be skipped to.
|
|
959
965
|
* @returns {boolean} True if the page has been skipped, or if it has been
|
|
960
966
|
* skipped to a specific page.
|
|
961
967
|
*/
|
|
962
968
|
isSkipped(waypoint, opts) {
|
|
969
|
+
const wpData = this.data[String(waypoint)];
|
|
970
|
+
|
|
963
971
|
if (opts === undefined) {
|
|
964
|
-
return
|
|
965
|
-
this.data[waypoint]?.__skipped__ === true ||
|
|
966
|
-
this.data[waypoint]?.__skip__ !== undefined
|
|
967
|
-
);
|
|
972
|
+
return wpData?.__skipped__ === true || wpData?.__skip__ !== undefined;
|
|
968
973
|
} else if (typeof opts.to === "string") {
|
|
969
|
-
return
|
|
974
|
+
return wpData?.__skip__?.to === opts.to;
|
|
970
975
|
}
|
|
971
976
|
}
|
|
972
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
|
*/
|
|
@@ -12,9 +12,10 @@ const combineMerge = (target, source, options) => {
|
|
|
12
12
|
const destination = target.slice();
|
|
13
13
|
|
|
14
14
|
for (let index = 0; index < source.length; index++) {
|
|
15
|
-
const item = source[index];
|
|
16
15
|
// ESLint disabled as `index` is only an integer
|
|
17
16
|
/* eslint-disable security/detect-object-injection */
|
|
17
|
+
const item = source[index];
|
|
18
|
+
|
|
18
19
|
if (typeof destination[index] === "undefined") {
|
|
19
20
|
destination[index] = options.cloneUnlessOtherwiseSpecified(item, options);
|
|
20
21
|
} else if (options.isMergeableObject(item)) {
|
|
@@ -30,10 +31,18 @@ const combineMerge = (target, source, options) => {
|
|
|
30
31
|
// Allows objects to be deepmerged and retain their type, without becoming [object Object]
|
|
31
32
|
// ref: https://github.com/jonschlinkert/is-plain-object/blob/master/is-plain-object.js
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* @param {any} o Value to test
|
|
36
|
+
* @returns {boolean} True if an object
|
|
37
|
+
*/
|
|
33
38
|
function isObject(o) {
|
|
34
39
|
return Object.prototype.toString.call(o) === "[object Object]";
|
|
35
40
|
}
|
|
36
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @param {any} o Value to test
|
|
44
|
+
* @returns {boolean} True if a plain object or array
|
|
45
|
+
*/
|
|
37
46
|
function isPlainObjectOrArray(o) {
|
|
38
47
|
if (Array.isArray(o)) {
|
|
39
48
|
return true;
|
|
@@ -52,12 +61,17 @@ function isPlainObjectOrArray(o) {
|
|
|
52
61
|
return Object.hasOwn(prot, "isPrototypeOf");
|
|
53
62
|
}
|
|
54
63
|
|
|
64
|
+
/**
|
|
65
|
+
* @param {...any} objects Objects to merge
|
|
66
|
+
* @returns {object} Merged object
|
|
67
|
+
*/
|
|
55
68
|
function mergeObjects(...objects) {
|
|
56
69
|
return deepmergeAll([Object.create(null), ...objects], {
|
|
57
70
|
arrayMerge: combineMerge,
|
|
58
71
|
isMergeableObject: isPlainObjectOrArray,
|
|
59
72
|
});
|
|
60
73
|
}
|
|
74
|
+
|
|
61
75
|
/**
|
|
62
76
|
* Determine whether a value exists in a list.
|
|
63
77
|
*
|
package/src/lib/utils.js
CHANGED
|
@@ -48,7 +48,6 @@ export function isStringable(value) {
|
|
|
48
48
|
* @access private
|
|
49
49
|
*/
|
|
50
50
|
export function resolveMiddlewareHooks(hookName, path, hooks = []) {
|
|
51
|
-
/* eslint-disable-next-line max-len */
|
|
52
51
|
const pathMatch = (h) =>
|
|
53
52
|
h.path === undefined ||
|
|
54
53
|
(h.path instanceof RegExp && h.path.test(path)) ||
|
|
@@ -119,10 +118,15 @@ export function stripWhitespace(value, options) {
|
|
|
119
118
|
throw new TypeError("nested must be a string");
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
// This approach avoids using `/s+$/` regex, which triggers the
|
|
122
|
+
// `sonarjs/slow-regex` eslint rule
|
|
123
|
+
let newValue = value.replace(/^\s+/, opts.leading);
|
|
124
|
+
if (newValue.match(/\s$/)) {
|
|
125
|
+
newValue = `${newValue.trimEnd()}${opts.trailing}`;
|
|
126
|
+
}
|
|
127
|
+
newValue = newValue.replace(/\s+/g, opts.nested);
|
|
128
|
+
|
|
129
|
+
return newValue;
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
/* ------------------------------------------------ validation / sanitisation */
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable class-methods-use-this */
|
|
2
1
|
import { DateTime } from "luxon";
|
|
3
2
|
import lodash from "lodash";
|
|
4
3
|
import ValidationError from "../ValidationError.js";
|
|
@@ -102,7 +101,7 @@ export default class DateObject extends ValidatorFactory {
|
|
|
102
101
|
if (test.flags.every((v) => v === true)) {
|
|
103
102
|
formats = [...formats, ...test.formats];
|
|
104
103
|
}
|
|
105
|
-
}
|
|
104
|
+
}
|
|
106
105
|
|
|
107
106
|
if (typeof value === "object") {
|
|
108
107
|
formats.find((format) => {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable class-methods-use-this */
|
|
2
1
|
import validatorPkg from "validator";
|
|
3
2
|
import ValidationError from "../ValidationError.js";
|
|
4
3
|
import ValidatorFactory from "../ValidatorFactory.js";
|
|
@@ -34,7 +33,7 @@ export default class Email extends ValidatorFactory {
|
|
|
34
33
|
let isValid;
|
|
35
34
|
try {
|
|
36
35
|
isValid = isEmail(value);
|
|
37
|
-
} catch
|
|
36
|
+
} catch {
|
|
38
37
|
isValid = false;
|
|
39
38
|
}
|
|
40
39
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable class-methods-use-this */
|
|
2
1
|
import lodash from "lodash";
|
|
3
2
|
import ValidationError from "../ValidationError.js";
|
|
4
3
|
import ValidatorFactory from "../ValidatorFactory.js";
|
|
@@ -83,7 +82,6 @@ export default class PostalAddressObject extends ValidatorFactory {
|
|
|
83
82
|
...this.config,
|
|
84
83
|
};
|
|
85
84
|
|
|
86
|
-
/* eslint-disable-next-line require-jsdoc */
|
|
87
85
|
const objectifyError = (err) =>
|
|
88
86
|
typeof err === "string"
|
|
89
87
|
? {
|
|
@@ -105,12 +103,14 @@ export default class PostalAddressObject extends ValidatorFactory {
|
|
|
105
103
|
const errorMsgs = [];
|
|
106
104
|
|
|
107
105
|
if (typeof value === "object") {
|
|
108
|
-
const reAddr = /^[
|
|
109
|
-
const reAddrLine1 =
|
|
106
|
+
const reAddr = /^[a-z0-9\-,.&#()/\\:;'" ]{2,}$/i;
|
|
107
|
+
const reAddrLine1 = /^(\d+|[a-z0-9\-,.&#()/\\:;'" ]{2,})$/i;
|
|
110
108
|
// UK Postcode regex taken from the dwp java pc checker
|
|
111
109
|
// https://github.com/dwp/postcode-format-validation
|
|
110
|
+
/* eslint-disable sonarjs/regex-complexity */
|
|
112
111
|
const rePostcode =
|
|
113
|
-
/^(?![QVX])[A-Z]((?![IJZ])[A-Z]
|
|
112
|
+
/^(?![QVX])[A-Z]((?![IJZ])[A-Z]\d((\d?)|([ABEHMNPRVWXY]?))|(\d(\d?|[ABCDEFGHJKPSTUW]?))) ?\d((?![CIKMOV])[A-Z]){2}$|^(BFPO) ?\d{1,4}$/i;
|
|
113
|
+
/* eslint-enable sonarjs/regex-complexity */
|
|
114
114
|
|
|
115
115
|
// [required, regex, strlenmax, error message]
|
|
116
116
|
const attributes = {
|