@dwp/govuk-casa 8.7.12 → 8.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/casa.js +2 -1
- package/dist/casa.js.map +1 -0
- package/dist/lib/CasaTemplateLoader.js +1 -0
- package/dist/lib/CasaTemplateLoader.js.map +1 -0
- package/dist/lib/JourneyContext.d.ts +1 -1
- package/dist/lib/JourneyContext.js +2 -1
- package/dist/lib/JourneyContext.js.map +1 -0
- package/dist/lib/MutableRouter.js +1 -0
- package/dist/lib/MutableRouter.js.map +1 -0
- package/dist/lib/Plan.d.ts +2 -1
- package/dist/lib/Plan.js +4 -3
- package/dist/lib/Plan.js.map +1 -0
- package/dist/lib/ValidationError.js +1 -0
- package/dist/lib/ValidationError.js.map +1 -0
- package/dist/lib/ValidatorFactory.d.ts +2 -2
- package/dist/lib/ValidatorFactory.js +3 -2
- package/dist/lib/ValidatorFactory.js.map +1 -0
- package/dist/lib/configuration-ingestor.js +1 -0
- package/dist/lib/configuration-ingestor.js.map +1 -0
- package/dist/lib/configure.js +2 -1
- package/dist/lib/configure.js.map +1 -0
- package/dist/lib/end-session.js +1 -0
- package/dist/lib/end-session.js.map +1 -0
- package/dist/lib/field.js +1 -0
- package/dist/lib/field.js.map +1 -0
- package/dist/lib/index.js +1 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/logger.js +1 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/mount.js +3 -2
- package/dist/lib/mount.js.map +1 -0
- package/dist/lib/nunjucks-filters.js +1 -0
- package/dist/lib/nunjucks-filters.js.map +1 -0
- package/dist/lib/nunjucks.js +1 -0
- package/dist/lib/nunjucks.js.map +1 -0
- package/dist/lib/utils.d.ts +45 -27
- package/dist/lib/utils.js +105 -67
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/validators/dateObject.js +4 -3
- package/dist/lib/validators/dateObject.js.map +1 -0
- package/dist/lib/validators/email.js +1 -0
- package/dist/lib/validators/email.js.map +1 -0
- package/dist/lib/validators/inArray.js +1 -0
- package/dist/lib/validators/inArray.js.map +1 -0
- package/dist/lib/validators/index.js +1 -0
- package/dist/lib/validators/index.js.map +1 -0
- package/dist/lib/validators/nino.js +1 -0
- package/dist/lib/validators/nino.js.map +1 -0
- package/dist/lib/validators/postalAddressObject.d.ts +2 -2
- package/dist/lib/validators/postalAddressObject.js +2 -1
- package/dist/lib/validators/postalAddressObject.js.map +1 -0
- package/dist/lib/validators/regex.js +1 -0
- package/dist/lib/validators/regex.js.map +1 -0
- package/dist/lib/validators/required.js +1 -0
- package/dist/lib/validators/required.js.map +1 -0
- package/dist/lib/validators/strlen.js +1 -0
- package/dist/lib/validators/strlen.js.map +1 -0
- package/dist/lib/validators/wordCount.js +1 -0
- package/dist/lib/validators/wordCount.js.map +1 -0
- package/dist/lib/waypoint-url.js +1 -0
- package/dist/lib/waypoint-url.js.map +1 -0
- package/dist/middleware/body-parser.js +1 -0
- package/dist/middleware/body-parser.js.map +1 -0
- package/dist/middleware/csrf.js +1 -0
- package/dist/middleware/csrf.js.map +1 -0
- package/dist/middleware/data.js +1 -0
- package/dist/middleware/data.js.map +1 -0
- package/dist/middleware/gather-fields.js +1 -0
- package/dist/middleware/gather-fields.js.map +1 -0
- package/dist/middleware/i18n.js +1 -0
- package/dist/middleware/i18n.js.map +1 -0
- package/dist/middleware/post.js +1 -0
- package/dist/middleware/post.js.map +1 -0
- package/dist/middleware/pre.js +1 -0
- package/dist/middleware/pre.js.map +1 -0
- package/dist/middleware/progress-journey.js +1 -0
- package/dist/middleware/progress-journey.js.map +1 -0
- package/dist/middleware/sanitise-fields.js +1 -0
- package/dist/middleware/sanitise-fields.js.map +1 -0
- package/dist/middleware/serve-first-waypoint.js +1 -0
- package/dist/middleware/serve-first-waypoint.js.map +1 -0
- package/dist/middleware/session.js +1 -0
- package/dist/middleware/session.js.map +1 -0
- package/dist/middleware/skip-waypoint.js +1 -0
- package/dist/middleware/skip-waypoint.js.map +1 -0
- package/dist/middleware/steer-journey.js +1 -0
- package/dist/middleware/steer-journey.js.map +1 -0
- package/dist/middleware/strip-proxy-path.js +1 -0
- package/dist/middleware/strip-proxy-path.js.map +1 -0
- package/dist/middleware/validate-fields.js +1 -0
- package/dist/middleware/validate-fields.js.map +1 -0
- package/dist/mjs/esm-wrapper.js +10 -15
- package/dist/routes/ancillary.js +1 -0
- package/dist/routes/ancillary.js.map +1 -0
- package/dist/routes/journey.js +1 -0
- package/dist/routes/journey.js.map +1 -0
- package/dist/routes/static.js +1 -0
- package/dist/routes/static.js.map +1 -0
- package/locales/cy/error.json +1 -1
- package/locales/en/error.json +1 -1
- package/package.json +16 -15
- package/src/casa.js +320 -0
- package/src/lib/CasaTemplateLoader.js +104 -0
- package/src/lib/JourneyContext.js +783 -0
- package/src/lib/MutableRouter.js +310 -0
- package/src/lib/Plan.js +624 -0
- package/src/lib/ValidationError.js +163 -0
- package/src/lib/ValidatorFactory.js +105 -0
- package/src/lib/configuration-ingestor.js +457 -0
- package/src/lib/configure.js +202 -0
- package/src/lib/dirname.cjs +1 -0
- package/src/lib/end-session.js +45 -0
- package/src/lib/field.js +456 -0
- package/src/lib/index.js +33 -0
- package/src/lib/logger.js +16 -0
- package/src/lib/mount.js +127 -0
- package/src/lib/nunjucks-filters.js +150 -0
- package/src/lib/nunjucks.js +53 -0
- package/src/lib/utils.js +232 -0
- package/src/lib/validators/dateObject.js +169 -0
- package/src/lib/validators/email.js +55 -0
- package/src/lib/validators/inArray.js +81 -0
- package/src/lib/validators/index.js +24 -0
- package/src/lib/validators/nino.js +57 -0
- package/src/lib/validators/postalAddressObject.js +162 -0
- package/src/lib/validators/regex.js +48 -0
- package/src/lib/validators/required.js +74 -0
- package/src/lib/validators/strlen.js +66 -0
- package/src/lib/validators/wordCount.js +70 -0
- package/src/lib/waypoint-url.js +93 -0
- package/src/middleware/body-parser.js +31 -0
- package/src/middleware/csrf.js +29 -0
- package/src/middleware/data.js +105 -0
- package/src/middleware/dirname.cjs +1 -0
- package/src/middleware/gather-fields.js +51 -0
- package/src/middleware/i18n.js +106 -0
- package/src/middleware/post.js +61 -0
- package/src/middleware/pre.js +91 -0
- package/src/middleware/progress-journey.js +92 -0
- package/src/middleware/sanitise-fields.js +58 -0
- package/src/middleware/serve-first-waypoint.js +28 -0
- package/src/middleware/session.js +129 -0
- package/src/middleware/skip-waypoint.js +46 -0
- package/src/middleware/steer-journey.js +78 -0
- package/src/middleware/strip-proxy-path.js +56 -0
- package/src/middleware/validate-fields.js +84 -0
- package/src/routes/ancillary.js +29 -0
- package/src/routes/dirname.cjs +1 -0
- package/src/routes/journey.js +212 -0
- package/src/routes/static.js +77 -0
- package/views/casa/components/character-count/README.md +10 -0
- package/views/casa/components/character-count/template.njk +6 -2
- package/views/casa/components/checkboxes/README.md +43 -34
- package/views/casa/components/checkboxes/template.njk +8 -7
- package/views/casa/components/date-input/README.md +11 -1
- package/views/casa/components/date-input/template.njk +6 -4
- package/views/casa/components/input/README.md +9 -0
- package/views/casa/components/input/template.njk +6 -2
- package/views/casa/components/postal-address-object/README.md +10 -0
- package/views/casa/components/postal-address-object/template.njk +20 -5
- package/views/casa/components/radios/README.md +49 -24
- package/views/casa/components/radios/template.njk +6 -3
- package/views/casa/components/select/README.md +65 -0
- package/views/casa/components/select/macro.njk +3 -0
- package/views/casa/components/select/template.njk +49 -0
- package/views/casa/components/textarea/README.md +9 -0
- package/views/casa/components/textarea/template.njk +6 -2
- package/views/casa/layouts/journey.njk +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this */
|
|
2
|
+
/**
|
|
3
|
+
* Test if a value is present in an array.
|
|
4
|
+
*
|
|
5
|
+
* Config options:
|
|
6
|
+
* Array source = Array of values to test against
|
|
7
|
+
*
|
|
8
|
+
* If the value itself is an array, all values within that array must be present
|
|
9
|
+
* in the `source` array in order to pass validation.
|
|
10
|
+
*/
|
|
11
|
+
import ValidationError from '../ValidationError.js';
|
|
12
|
+
import ValidatorFactory from '../ValidatorFactory.js';
|
|
13
|
+
import { stringifyInput, isStringable } from '../utils.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @access private
|
|
17
|
+
* @typedef {import('../../casa').ErrorMessageConfig} ErrorMessageConfig
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} ArrayConfigOptions
|
|
22
|
+
* @property {ErrorMessageConfig} errorMsg Error message config
|
|
23
|
+
* @property {string[]} source Array of values to test against
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Test if a value is present in an array.
|
|
28
|
+
*
|
|
29
|
+
* If the value itself is an array, all values within that array must be present
|
|
30
|
+
* in the `source` array in order to pass validation.
|
|
31
|
+
*
|
|
32
|
+
* See {@link ArrayConfigOptions} for `make()` options.
|
|
33
|
+
*
|
|
34
|
+
* @memberof Validators
|
|
35
|
+
* @augments ValidatorFactory
|
|
36
|
+
*/
|
|
37
|
+
export default class InArray extends ValidatorFactory {
|
|
38
|
+
/** @property {string} name Validator name ("inArray") */
|
|
39
|
+
name = 'inArray';
|
|
40
|
+
|
|
41
|
+
validate(value, dataContext = {}) {
|
|
42
|
+
let valid = false;
|
|
43
|
+
const source = this.config.source || [];
|
|
44
|
+
const errorMsg = this.config.errorMsg || {
|
|
45
|
+
inline: 'validation:rule.inArray.inline',
|
|
46
|
+
summary: 'validation:rule.inArray.summary',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (value !== null && typeof value !== 'undefined') {
|
|
50
|
+
const search = Array.isArray(value) ? value : [value];
|
|
51
|
+
for (let i = 0, l = search.length; i < l; i += 1) {
|
|
52
|
+
if (source.indexOf(search[parseInt(i, 10)]) > -1) {
|
|
53
|
+
valid = true;
|
|
54
|
+
} else {
|
|
55
|
+
valid = false;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return valid ? [] : [ValidationError.make({ errorMsg, dataContext })];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
sanitise(value) {
|
|
65
|
+
const coerce = (val) => (stringifyInput(val, undefined));
|
|
66
|
+
|
|
67
|
+
// Basic stringable
|
|
68
|
+
if (isStringable(value)) {
|
|
69
|
+
return stringifyInput(value);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Coerce all elements to Strings.
|
|
73
|
+
// This only supports one dimensional array, with stringable element.
|
|
74
|
+
if (Array.isArray(value)) {
|
|
75
|
+
return value.map(coerce);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Unsupported value
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import dateObject from './dateObject.js';
|
|
2
|
+
import email from './email.js';
|
|
3
|
+
import inArray from './inArray.js';
|
|
4
|
+
import nino from './nino.js';
|
|
5
|
+
import postalAddressObject from './postalAddressObject.js';
|
|
6
|
+
import regex from './regex.js';
|
|
7
|
+
import required from './required.js';
|
|
8
|
+
import strlen from './strlen.js';
|
|
9
|
+
import wordCount from './wordCount.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @namespace Validators
|
|
13
|
+
*/
|
|
14
|
+
export default {
|
|
15
|
+
dateObject,
|
|
16
|
+
email,
|
|
17
|
+
inArray,
|
|
18
|
+
nino,
|
|
19
|
+
postalAddressObject,
|
|
20
|
+
regex,
|
|
21
|
+
required,
|
|
22
|
+
strlen,
|
|
23
|
+
wordCount,
|
|
24
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this */
|
|
2
|
+
import ValidationError from '../ValidationError.js';
|
|
3
|
+
import ValidatorFactory from '../ValidatorFactory.js';
|
|
4
|
+
import { stringifyInput } from '../utils.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @access private
|
|
8
|
+
* @typedef {import('../../casa').ErrorMessageConfig} ErrorMessageConfig
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {object} NinoConfigOptions
|
|
13
|
+
* @property {ErrorMessageConfig} errorMsg Error message config
|
|
14
|
+
* @property {boolean} allowWhitespace Will permit input values that contain spaces.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* UK National Insurance number.
|
|
19
|
+
*
|
|
20
|
+
* Ref:
|
|
21
|
+
* https://en.wikipedia.org/wiki/National_Insurance_number#Format
|
|
22
|
+
* https://design-system.service.gov.uk/patterns/national-insurance-numbers/
|
|
23
|
+
*
|
|
24
|
+
* See {@link NinoConfigOptions} for `make()` options.
|
|
25
|
+
*
|
|
26
|
+
* @memberof Validators
|
|
27
|
+
* @augments ValidatorFactory
|
|
28
|
+
*/
|
|
29
|
+
export default class Nino extends ValidatorFactory {
|
|
30
|
+
name = 'nino';
|
|
31
|
+
|
|
32
|
+
validate(value, dataContext = {}) {
|
|
33
|
+
const {
|
|
34
|
+
allowWhitespace,
|
|
35
|
+
errorMsg = {
|
|
36
|
+
inline: 'validation:rule.nino.inline',
|
|
37
|
+
summary: 'validation:rule.nino.summary',
|
|
38
|
+
},
|
|
39
|
+
} = this.config;
|
|
40
|
+
|
|
41
|
+
if (typeof allowWhitespace !== 'undefined' && typeof allowWhitespace !== 'boolean') {
|
|
42
|
+
throw new TypeError(`NINO validation rule option "allowWhitespace" must been a boolean. received ${typeof allowWhitespace}`);
|
|
43
|
+
}
|
|
44
|
+
const valid = typeof value === 'string'
|
|
45
|
+
&& value.replace((typeof allowWhitespace !== 'undefined' && allowWhitespace) ? /\u0020/g : '', '')
|
|
46
|
+
.match(/^(?!BG|GB|NK|KN|TN|NT|ZZ)[ABCEGHJ-PRSTW-Z][ABCEGHJ-NPRSTW-Z]\d{6}[A-D]$/i);
|
|
47
|
+
|
|
48
|
+
return valid ? [] : [ValidationError.make({ errorMsg, dataContext })];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
sanitise(value) {
|
|
52
|
+
if (value !== undefined) {
|
|
53
|
+
return stringifyInput(value);
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this */
|
|
2
|
+
import lodash from 'lodash';
|
|
3
|
+
import ValidationError from '../ValidationError.js';
|
|
4
|
+
import ValidatorFactory from '../ValidatorFactory.js';
|
|
5
|
+
import { stringifyInput } from '../utils.js';
|
|
6
|
+
|
|
7
|
+
const { isPlainObject } = lodash; // CommonjS
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @access private
|
|
11
|
+
* @typedef {import('../../casa').ErrorMessageConfig} ErrorMessageConfig
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {object} PostalAddressObjectConfigOptions
|
|
16
|
+
* @property {ErrorMessageConfig} [errorMsg] General error message for the entire address block
|
|
17
|
+
* @property {string|object} [errorMsgAddress1] Error message for address1 part
|
|
18
|
+
* @property {string|object} [errorMsgAddress2] Error message for address2 part
|
|
19
|
+
* @property {string|object} [errorMsgAddress3] Error message for address3 part
|
|
20
|
+
* @property {string|object} [errorMsgAddress4] Error message for address4 part
|
|
21
|
+
* @property {string|object} [errorMsgPostcode] Error message for postcode part
|
|
22
|
+
* @property {number} [strlenmax] Max. String length for each of the inputs address[1-4]
|
|
23
|
+
* @property {string[]} [requiredFields] Field parts required (others become optional). One of
|
|
24
|
+
* 'address1'|'address2'|'address3'|'address4'|'postcode'
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Works hand in hand with the core CASA `postalAddressObject` form
|
|
29
|
+
* macro.
|
|
30
|
+
*
|
|
31
|
+
* The errors sent back from this validator are specific to each subfield. For
|
|
32
|
+
* example, if the field name being tested is "address", any errors related to
|
|
33
|
+
* the "postcode" component would be associated with "address[postcode]".
|
|
34
|
+
*
|
|
35
|
+
* See {@link PostalAddressObjectConfigOptions} for `make()` options.
|
|
36
|
+
*
|
|
37
|
+
* @memberof Validators
|
|
38
|
+
* @augments ValidatorFactory
|
|
39
|
+
*/
|
|
40
|
+
export default class PostalAddressObject extends ValidatorFactory {
|
|
41
|
+
name = 'postalAddressObject';
|
|
42
|
+
|
|
43
|
+
validate(value, dataContext = {}) {
|
|
44
|
+
const cfg = {
|
|
45
|
+
requiredFields: ['address1', 'address3', 'postcode'],
|
|
46
|
+
strlenmax: undefined,
|
|
47
|
+
errorMsgAddress1: {
|
|
48
|
+
inline: 'validation:rule.postalAddressObject.address1.inline',
|
|
49
|
+
summary: 'validation:rule.postalAddressObject.address1.summary',
|
|
50
|
+
focusSuffix: '[address1]',
|
|
51
|
+
},
|
|
52
|
+
errorMsgAddress2: {
|
|
53
|
+
inline: 'validation:rule.postalAddressObject.address2.inline',
|
|
54
|
+
summary: 'validation:rule.postalAddressObject.address2.summary',
|
|
55
|
+
focusSuffix: '[address2]',
|
|
56
|
+
},
|
|
57
|
+
errorMsgAddress3: {
|
|
58
|
+
inline: 'validation:rule.postalAddressObject.address3.inline',
|
|
59
|
+
summary: 'validation:rule.postalAddressObject.address3.summary',
|
|
60
|
+
focusSuffix: '[address3]',
|
|
61
|
+
},
|
|
62
|
+
errorMsgAddress4: {
|
|
63
|
+
inline: 'validation:rule.postalAddressObject.address4.inline',
|
|
64
|
+
summary: 'validation:rule.postalAddressObject.address4.summary',
|
|
65
|
+
focusSuffix: '[address4]',
|
|
66
|
+
},
|
|
67
|
+
errorMsgPostcode: {
|
|
68
|
+
inline: 'validation:rule.postalAddressObject.postcode.inline',
|
|
69
|
+
summary: 'validation:rule.postalAddressObject.postcode.summary',
|
|
70
|
+
focusSuffix: '[postcode]',
|
|
71
|
+
},
|
|
72
|
+
errorMsg: {
|
|
73
|
+
inline: 'validation:rule.postalAddressObject.group.inline',
|
|
74
|
+
summary: 'validation:rule.postalAddressObject.group.summary',
|
|
75
|
+
focusSuffix: '[address1]',
|
|
76
|
+
},
|
|
77
|
+
...this.config,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/* eslint-disable-next-line require-jsdoc */
|
|
81
|
+
const objectifyError = (err) => (typeof err === 'string' ? {
|
|
82
|
+
inline: err,
|
|
83
|
+
summary: err,
|
|
84
|
+
} : err);
|
|
85
|
+
|
|
86
|
+
// Work out required/optional parts based on config
|
|
87
|
+
const reqF = Object.create(null);
|
|
88
|
+
const reqC = cfg.requiredFields;
|
|
89
|
+
['address1', 'address2', 'address3', 'address4', 'postcode'].forEach((k) => {
|
|
90
|
+
// ESLint disabled as `k` is a known value from a constant list
|
|
91
|
+
/* eslint-disable-next-line security/detect-object-injection */
|
|
92
|
+
reqF[k] = reqC.indexOf(k) > -1;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
let valid = true;
|
|
96
|
+
const errorMsgs = [];
|
|
97
|
+
|
|
98
|
+
if (typeof value === 'object') {
|
|
99
|
+
const reAddr = /^[^\s]+[a-z0-9\-,.&#()/\\:;'" ]+$/i;
|
|
100
|
+
const reAddrLine1 = /^\d+|[^\s]+[a-z0-9\-,.&#()/\\:;'" ]+$/i;
|
|
101
|
+
// UK Postcode regex taken from the dwp java pc checker
|
|
102
|
+
// https://github.com/dwp/postcode-format-validation
|
|
103
|
+
const rePostcode = /^(?![QVX])[A-Z]((?![IJZ])[A-Z][0-9](([0-9]?)|([ABEHMNPRVWXY]?))|([0-9]([0-9]?|[ABCDEFGHJKPSTUW]?))) ?[0-9]((?![CIKMOV])[A-Z]){2}$|^(BFPO)[ ]?[0-9]{1,4}$/i;
|
|
104
|
+
|
|
105
|
+
// [required, regex, strlenmax, error message]
|
|
106
|
+
const attributes = {
|
|
107
|
+
address1: [reqF.address1, reAddrLine1, cfg.strlenmax, cfg.errorMsgAddress1],
|
|
108
|
+
address2: [reqF.address2, reAddr, cfg.strlenmax, cfg.errorMsgAddress2],
|
|
109
|
+
address3: [reqF.address3, reAddr, cfg.strlenmax, cfg.errorMsgAddress3],
|
|
110
|
+
address4: [reqF.address4, reAddr, cfg.strlenmax, cfg.errorMsgAddress4],
|
|
111
|
+
postcode: [reqF.postcode, rePostcode, null, cfg.errorMsgPostcode],
|
|
112
|
+
};
|
|
113
|
+
// ESLint disabled as `k` is a known value from the constant list above
|
|
114
|
+
/* eslint-disable security/detect-object-injection */
|
|
115
|
+
Object.keys(attributes).forEach((k) => {
|
|
116
|
+
const attr = attributes[k];
|
|
117
|
+
const hasProperty = Object.prototype.hasOwnProperty.call(value, k);
|
|
118
|
+
const hasContent = hasProperty && value[k].length > 0;
|
|
119
|
+
|
|
120
|
+
const condMissingOrRegexMismatch = (attr[0] || hasContent)
|
|
121
|
+
&& (!hasProperty || !value[k].match(attr[1]));
|
|
122
|
+
const condExceedStrlen = attr[2] > 0 && hasContent
|
|
123
|
+
&& String(value[k]).length > attr[2];
|
|
124
|
+
|
|
125
|
+
if (condMissingOrRegexMismatch || condExceedStrlen) {
|
|
126
|
+
valid = false;
|
|
127
|
+
errorMsgs.push(Object.assign(Object.create(null), objectifyError(attr[3]), {
|
|
128
|
+
fieldKeySuffix: `[${k}]`,
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
/* eslint-enable security/detect-object-injection */
|
|
133
|
+
} else {
|
|
134
|
+
valid = false;
|
|
135
|
+
errorMsgs.push(cfg.errorMsg);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Build ValidationErrorGroup
|
|
139
|
+
const errorGroup = errorMsgs.map((err) => (
|
|
140
|
+
ValidationError.make({ errorMsg: err, dataContext })));
|
|
141
|
+
|
|
142
|
+
return valid ? [] : [...errorGroup];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
sanitise(value) {
|
|
146
|
+
// Only objects are supported
|
|
147
|
+
if (!isPlainObject(value)) {
|
|
148
|
+
return Object.create(null);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Prune unrecognised attributes, and coerce to Strings
|
|
152
|
+
const validKeys = ['address1', 'address2', 'address3', 'address4', 'postcode'];
|
|
153
|
+
const pruned = Object.fromEntries(
|
|
154
|
+
Object.entries(value).filter(
|
|
155
|
+
([k]) => (validKeys.includes(k)),
|
|
156
|
+
).map(
|
|
157
|
+
([k, v]) => ([k, stringifyInput(v)]),
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
return Object.assign(Object.create(null), pruned);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this */
|
|
2
|
+
import ValidatorFactory from '../ValidatorFactory.js';
|
|
3
|
+
import ValidationError from '../ValidationError.js';
|
|
4
|
+
import { stringifyInput } from '../utils.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @access private
|
|
8
|
+
* @typedef {import('../../casa').ErrorMessageConfig} ErrorMessageConfig
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {object} RegexConfigOptions
|
|
13
|
+
* @property {ErrorMessageConfig} errorMsg Error message config
|
|
14
|
+
* @property {RegExp} pattern Regular expression to test against
|
|
15
|
+
* @property {boolean} invert Return error on positive regex match
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Match a string pattern.
|
|
20
|
+
*
|
|
21
|
+
* See {@link RegexConfigOptions} for `make()` options.
|
|
22
|
+
*
|
|
23
|
+
* @memberof Validators
|
|
24
|
+
* @augments ValidatorFactory
|
|
25
|
+
*/
|
|
26
|
+
export default class Regex extends ValidatorFactory {
|
|
27
|
+
name = 'regex';
|
|
28
|
+
|
|
29
|
+
validate(value = '', dataContext = {}) {
|
|
30
|
+
const invert = this.config.invert || false;
|
|
31
|
+
const match = value.match(this.config.pattern || /.*/);
|
|
32
|
+
const valid = invert ? !match : match;
|
|
33
|
+
|
|
34
|
+
const errorMsg = this.config.errorMsg || {
|
|
35
|
+
inline: 'validation:rule.regex.inline',
|
|
36
|
+
summary: 'validation:rule.regex.summary',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return valid ? [] : [ValidationError.make({ errorMsg, dataContext })];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
sanitise(value) {
|
|
43
|
+
if (value !== undefined) {
|
|
44
|
+
return stringifyInput(value);
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this */
|
|
2
|
+
import lodash from 'lodash';
|
|
3
|
+
import { isEmpty, isStringable, stringifyInput } from '../utils.js';
|
|
4
|
+
import ValidatorFactory from '../ValidatorFactory.js';
|
|
5
|
+
import ValidationError from '../ValidationError.js';
|
|
6
|
+
|
|
7
|
+
const { isPlainObject } = lodash; // CommonJS
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @access private
|
|
11
|
+
* @typedef {import('../../casa').ErrorMessageConfig} ErrorMessageConfig
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {object} RequiredConfigOptions
|
|
16
|
+
* @property {ErrorMessageConfig} errorMsg Error message config
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Test if value is present.
|
|
21
|
+
*
|
|
22
|
+
* Value is required. The following values will fail this rule:
|
|
23
|
+
* (all values that satisfy `isEmpty()`) plus '\s'
|
|
24
|
+
*
|
|
25
|
+
* See {@link RequiredConfigOptions} for `make()` options.
|
|
26
|
+
*
|
|
27
|
+
* @memberof Validators
|
|
28
|
+
* @augments ValidatorFactory
|
|
29
|
+
*/
|
|
30
|
+
export default class Required extends ValidatorFactory {
|
|
31
|
+
name = 'required';
|
|
32
|
+
|
|
33
|
+
validate(value, dataContext = {}) {
|
|
34
|
+
const {
|
|
35
|
+
errorMsg = {
|
|
36
|
+
inline: 'validation:rule.required.inline',
|
|
37
|
+
summary: 'validation:rule.required.summary',
|
|
38
|
+
},
|
|
39
|
+
} = this.config;
|
|
40
|
+
|
|
41
|
+
if (!isEmpty(value)) {
|
|
42
|
+
return []
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return [
|
|
46
|
+
ValidationError.make({ errorMsg, dataContext }),
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
sanitise(value) {
|
|
51
|
+
const coerce = (val) => {
|
|
52
|
+
const s = stringifyInput(val, undefined);
|
|
53
|
+
return s === undefined ? undefined : s.replace(/^\s+$/, '');
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (isStringable(value)) {
|
|
57
|
+
return coerce(value);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Coerce all elements to Strings.
|
|
61
|
+
// This only supports one dimensional array, with stringable element.
|
|
62
|
+
if (Array.isArray(value)) {
|
|
63
|
+
return value.map(coerce);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Coerce all elements to Strings.
|
|
67
|
+
// This only supports a one dimensional object, with stringable elements.
|
|
68
|
+
if (isPlainObject(value)) {
|
|
69
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => ([k, coerce(v)])));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this */
|
|
2
|
+
import ValidatorFactory from '../ValidatorFactory.js';
|
|
3
|
+
import ValidationError from '../ValidationError.js';
|
|
4
|
+
import { stringifyInput } from '../utils.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @access private
|
|
8
|
+
* @typedef {import('../../casa').ErrorMessageConfig} ErrorMessageConfig
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {object} StrlenConfigOptions
|
|
13
|
+
* @property {ErrorMessageConfig} errorMsgMax Error message to use on max length failure
|
|
14
|
+
* @property {ErrorMessageConfig} errorMsgMin Error message to use on min length failure
|
|
15
|
+
* @property {number} max Maximum string length allowed
|
|
16
|
+
* @property {number} min Minimum string length required
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Test the length of a string.
|
|
21
|
+
*
|
|
22
|
+
* See {@link StrlenConfigOptions} for `make()` options.
|
|
23
|
+
*
|
|
24
|
+
* @memberof Validators
|
|
25
|
+
* @augments ValidatorFactory
|
|
26
|
+
*/
|
|
27
|
+
export default class Strlen extends ValidatorFactory {
|
|
28
|
+
name = 'strlen';
|
|
29
|
+
|
|
30
|
+
validate(inputValue = '', dataContext = {}) {
|
|
31
|
+
const {
|
|
32
|
+
errorMsgMax = {
|
|
33
|
+
inline: 'validation:rule.strlen.max.inline',
|
|
34
|
+
summary: 'validation:rule.strlen.max.summary',
|
|
35
|
+
},
|
|
36
|
+
errorMsgMin = {
|
|
37
|
+
inline: 'validation:rule.strlen.min.inline',
|
|
38
|
+
summary: 'validation:rule.strlen.min.summary',
|
|
39
|
+
},
|
|
40
|
+
min,
|
|
41
|
+
max,
|
|
42
|
+
} = this.config;
|
|
43
|
+
|
|
44
|
+
let errorMsg;
|
|
45
|
+
let valid = true;
|
|
46
|
+
|
|
47
|
+
if (typeof max !== 'undefined' && inputValue.length > max) {
|
|
48
|
+
valid = false;
|
|
49
|
+
errorMsg = errorMsgMax;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (typeof min !== 'undefined' && inputValue.length < min) {
|
|
53
|
+
valid = false;
|
|
54
|
+
errorMsg = errorMsgMin;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return valid ? [] : [ValidationError.make({ errorMsg, dataContext })];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
sanitise(value) {
|
|
61
|
+
if (value !== undefined) {
|
|
62
|
+
return stringifyInput(value);
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this */
|
|
2
|
+
import ValidatorFactory from '../ValidatorFactory.js';
|
|
3
|
+
import ValidationError from '../ValidationError.js';
|
|
4
|
+
import { stringifyInput } from '../utils.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @access private
|
|
8
|
+
* @typedef {import('../../casa').ErrorMessageConfig} ErrorMessageConfig
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {object} WordcountConfigOptions
|
|
13
|
+
* @property {ErrorMessageConfig} errorMsgMax Error message to use on max length failure
|
|
14
|
+
* @property {ErrorMessageConfig} errorMsgMin Error message to use on min length failure
|
|
15
|
+
* @property {number} max Maximum string length allowed
|
|
16
|
+
* @property {number} min Minimum string length required
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Test the number of words in a string.
|
|
21
|
+
*
|
|
22
|
+
* See {@link WordcountConfigOptions} for `make()` options.
|
|
23
|
+
*
|
|
24
|
+
* @memberof Validators
|
|
25
|
+
* @augments ValidatorFactory
|
|
26
|
+
*/
|
|
27
|
+
export default class WordCount extends ValidatorFactory {
|
|
28
|
+
name = 'wordCount';
|
|
29
|
+
|
|
30
|
+
count(input) {
|
|
31
|
+
return (input.match(/\S+/g) || []).length;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
validate(inputValue = '', dataContext = {}) {
|
|
35
|
+
const {
|
|
36
|
+
errorMsgMax = {
|
|
37
|
+
inline: 'validation:rule.wordCount.max.inline',
|
|
38
|
+
summary: 'validation:rule.wordCount.max.summary',
|
|
39
|
+
},
|
|
40
|
+
errorMsgMin = {
|
|
41
|
+
inline: 'validation:rule.wordCount.min.inline',
|
|
42
|
+
summary: 'validation:rule.wordCount.min.summary',
|
|
43
|
+
},
|
|
44
|
+
min,
|
|
45
|
+
max,
|
|
46
|
+
} = this.config;
|
|
47
|
+
|
|
48
|
+
let errorMsg;
|
|
49
|
+
let valid = true;
|
|
50
|
+
|
|
51
|
+
if (typeof max !== 'undefined' && (inputValue.match(/\S+/g) || []).length > max) {
|
|
52
|
+
valid = false;
|
|
53
|
+
errorMsg = errorMsgMax;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof min !== 'undefined' && (inputValue.match(/\S+/g) || []).length < min) {
|
|
57
|
+
valid = false;
|
|
58
|
+
errorMsg = errorMsgMin;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return valid ? [] : [ValidationError.make({ errorMsg, dataContext })];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
sanitise(value) {
|
|
65
|
+
if (value !== undefined) {
|
|
66
|
+
return stringifyInput(value);
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @access private
|
|
3
|
+
* @typedef {import('./index').JourneyContext} JourneyContext
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @access private */
|
|
7
|
+
const reUrlProtocolExtract = /^url:\/\/(.+)$/i
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Sanitise a waypoint string.
|
|
11
|
+
*
|
|
12
|
+
* @access private
|
|
13
|
+
* @param {string} w Waypoint
|
|
14
|
+
* @returns {string} Sanitised waypoint
|
|
15
|
+
*/
|
|
16
|
+
const sanitiseWaypoint = (w) => w.replace(/[^/a-z0-9_-]/ig, '').replace(/\/+/g, '/');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generate a URL pointing at a particular waypoint.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // generates: /path/details?edit&editorigin=%2Fsomewhere%2Felse
|
|
23
|
+
* waypointUrl({
|
|
24
|
+
* mountUrl: '/path/',
|
|
25
|
+
* waypoint: 'details',
|
|
26
|
+
* edit: true,
|
|
27
|
+
* editOrigin: '/somewhere/else'
|
|
28
|
+
* })
|
|
29
|
+
* @memberof module:@dwp/govuk-casa
|
|
30
|
+
* @param {object} obj Options
|
|
31
|
+
* @param {string} [obj.waypoint=""] Waypoint
|
|
32
|
+
* @param {string} [obj.mountUrl="/"] Mount URL
|
|
33
|
+
* @param {JourneyContext} [obj.journeyContext] JourneyContext
|
|
34
|
+
* @param {boolean} [obj.edit=false] Turn edit mode on or off
|
|
35
|
+
* @param {string} [obj.editOrigin] Edit mode original URL
|
|
36
|
+
* @param {boolean} [obj.skipTo] Skip to this waypoint from the current one
|
|
37
|
+
* @param {string} [obj.routeName=next] Plan route name; next | prev
|
|
38
|
+
* @returns {string} URL
|
|
39
|
+
*/
|
|
40
|
+
export default function waypointUrl({
|
|
41
|
+
waypoint = '',
|
|
42
|
+
mountUrl = '/',
|
|
43
|
+
journeyContext,
|
|
44
|
+
edit = false,
|
|
45
|
+
editOrigin,
|
|
46
|
+
skipTo,
|
|
47
|
+
routeName = 'next',
|
|
48
|
+
} = Object.create(null)) {
|
|
49
|
+
const url = new URL('https://placeholder.test');
|
|
50
|
+
|
|
51
|
+
// Handle url:// protocol
|
|
52
|
+
// - This will generate a link to the root handler "_" for the given mount path
|
|
53
|
+
if (String(waypoint).substr(0, 7) === 'url:///') {
|
|
54
|
+
const m = waypoint.match(reUrlProtocolExtract);
|
|
55
|
+
|
|
56
|
+
const u = new URL(m[1], 'https://placeholder.test/');
|
|
57
|
+
url.pathname = `${sanitiseWaypoint(u.pathname)}/_/`;
|
|
58
|
+
|
|
59
|
+
url.searchParams.append('refmount', `url://${mountUrl}`);
|
|
60
|
+
url.searchParams.append('route', routeName);
|
|
61
|
+
} else {
|
|
62
|
+
url.pathname = `${mountUrl}${waypoint}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Attach context ID as query parameter for non-default contexts.
|
|
66
|
+
// To avoid messy URLs with duplicated content, this parameter will _not_ be
|
|
67
|
+
// added if the context ID already appears in the url path, i.e. to avoid
|
|
68
|
+
// `/path/1234-abcd/waypoint?contextid=1234-abcd` scenarios
|
|
69
|
+
if (
|
|
70
|
+
journeyContext
|
|
71
|
+
&& !journeyContext.isDefault()
|
|
72
|
+
&& journeyContext.identity.id
|
|
73
|
+
&& !mountUrl.includes(journeyContext.identity.id)
|
|
74
|
+
) {
|
|
75
|
+
url.searchParams.append('contextid', journeyContext.identity.id);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Attach edit mode flag
|
|
79
|
+
if (edit === true) {
|
|
80
|
+
url.searchParams.append('edit', 'true');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (edit && editOrigin) {
|
|
84
|
+
url.searchParams.append('editorigin', sanitiseWaypoint(editOrigin));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Skipto
|
|
88
|
+
if (skipTo) {
|
|
89
|
+
url.searchParams.append('skipto', sanitiseWaypoint(skipTo));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return `${sanitiseWaypoint(url.pathname)}${url.search}`;
|
|
93
|
+
}
|