@dwp/govuk-casa 9.0.0 → 9.1.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/README.md +9 -9
- package/dist/assets/css/casa.css +1 -1
- package/dist/assets/css/casa.css.map +1 -1
- package/dist/casa.d.ts +122 -95
- package/dist/casa.js +119 -86
- package/dist/casa.js.map +1 -1
- package/dist/lib/CasaTemplateLoader.d.ts +4 -4
- package/dist/lib/CasaTemplateLoader.js +16 -16
- package/dist/lib/CasaTemplateLoader.js.map +1 -1
- package/dist/lib/JourneyContext.d.ts +38 -40
- package/dist/lib/JourneyContext.js +81 -75
- package/dist/lib/JourneyContext.js.map +1 -1
- package/dist/lib/MutableRouter.d.ts +40 -41
- package/dist/lib/MutableRouter.js +64 -71
- package/dist/lib/MutableRouter.js.map +1 -1
- package/dist/lib/Plan.d.ts +29 -26
- package/dist/lib/Plan.js +85 -71
- package/dist/lib/Plan.js.map +1 -1
- package/dist/lib/ValidationError.d.ts +16 -15
- package/dist/lib/ValidationError.js +21 -20
- package/dist/lib/ValidationError.js.map +1 -1
- package/dist/lib/ValidatorFactory.d.ts +15 -13
- package/dist/lib/ValidatorFactory.js +14 -12
- package/dist/lib/ValidatorFactory.js.map +1 -1
- package/dist/lib/configuration-ingestor.d.ts +37 -40
- package/dist/lib/configuration-ingestor.js +93 -93
- package/dist/lib/configuration-ingestor.js.map +1 -1
- package/dist/lib/configure.d.ts +6 -6
- package/dist/lib/configure.js +14 -12
- package/dist/lib/configure.js.map +1 -1
- package/dist/lib/constants.d.ts +1 -3
- package/dist/lib/constants.js +9 -11
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/context-id-generators.d.ts +3 -5
- package/dist/lib/context-id-generators.js +7 -6
- package/dist/lib/context-id-generators.js.map +1 -1
- package/dist/lib/end-session.d.ts +4 -4
- package/dist/lib/end-session.js +5 -5
- package/dist/lib/field.d.ts +20 -18
- package/dist/lib/field.js +35 -48
- package/dist/lib/field.js.map +1 -1
- package/dist/lib/index.d.ts +13 -13
- package/dist/lib/logger.d.ts +7 -6
- package/dist/lib/logger.js +7 -7
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/mount.d.ts +5 -5
- package/dist/lib/mount.js +11 -10
- package/dist/lib/mount.js.map +1 -1
- package/dist/lib/nunjucks-filters.d.ts +10 -12
- package/dist/lib/nunjucks-filters.js +35 -35
- package/dist/lib/nunjucks-filters.js.map +1 -1
- package/dist/lib/nunjucks.d.ts +7 -5
- package/dist/lib/nunjucks.js +10 -8
- package/dist/lib/nunjucks.js.map +1 -1
- package/dist/lib/utils.d.ts +19 -19
- package/dist/lib/utils.js +62 -55
- package/dist/lib/utils.js.map +1 -1
- package/dist/lib/validators/dateObject.d.ts +29 -22
- package/dist/lib/validators/dateObject.js +58 -49
- package/dist/lib/validators/dateObject.js.map +1 -1
- package/dist/lib/validators/email.d.ts +4 -4
- package/dist/lib/validators/email.js +4 -4
- package/dist/lib/validators/inArray.d.ts +4 -4
- package/dist/lib/validators/inArray.js +7 -8
- package/dist/lib/validators/inArray.js.map +1 -1
- package/dist/lib/validators/index.d.ts +10 -10
- package/dist/lib/validators/index.js +1 -3
- package/dist/lib/validators/index.js.map +1 -1
- package/dist/lib/validators/nino.d.ts +9 -8
- package/dist/lib/validators/nino.js +14 -10
- package/dist/lib/validators/nino.js.map +1 -1
- package/dist/lib/validators/postalAddressObject.d.ts +37 -24
- package/dist/lib/validators/postalAddressObject.js +65 -46
- package/dist/lib/validators/postalAddressObject.js.map +1 -1
- package/dist/lib/validators/range.d.ts +12 -8
- package/dist/lib/validators/range.js +11 -9
- package/dist/lib/validators/range.js.map +1 -1
- package/dist/lib/validators/regex.d.ts +4 -4
- package/dist/lib/validators/regex.js +5 -5
- package/dist/lib/validators/required.d.ts +6 -6
- package/dist/lib/validators/required.js +9 -11
- package/dist/lib/validators/required.js.map +1 -1
- package/dist/lib/validators/strlen.d.ts +12 -8
- package/dist/lib/validators/strlen.js +13 -11
- package/dist/lib/validators/strlen.js.map +1 -1
- package/dist/lib/validators/wordCount.d.ts +12 -8
- package/dist/lib/validators/wordCount.js +15 -11
- package/dist/lib/validators/wordCount.js.map +1 -1
- package/dist/lib/waypoint-url.d.ts +16 -13
- package/dist/lib/waypoint-url.js +39 -36
- package/dist/lib/waypoint-url.js.map +1 -1
- package/dist/middleware/body-parser.d.ts +1 -1
- package/dist/middleware/body-parser.js +6 -6
- package/dist/middleware/body-parser.js.map +1 -1
- package/dist/middleware/data.d.ts +1 -1
- package/dist/middleware/data.js +8 -7
- package/dist/middleware/data.js.map +1 -1
- package/dist/middleware/gather-fields.d.ts +2 -2
- package/dist/middleware/gather-fields.js +6 -4
- package/dist/middleware/gather-fields.js.map +1 -1
- package/dist/middleware/i18n.js +13 -15
- package/dist/middleware/i18n.js.map +1 -1
- package/dist/middleware/post.js +30 -18
- package/dist/middleware/post.js.map +1 -1
- package/dist/middleware/pre.d.ts +2 -2
- package/dist/middleware/pre.js +46 -27
- package/dist/middleware/pre.js.map +1 -1
- package/dist/middleware/progress-journey.d.ts +1 -1
- package/dist/middleware/progress-journey.js +5 -5
- package/dist/middleware/progress-journey.js.map +1 -1
- package/dist/middleware/sanitise-fields.d.ts +1 -1
- package/dist/middleware/sanitise-fields.js +13 -11
- package/dist/middleware/sanitise-fields.js.map +1 -1
- package/dist/middleware/serve-first-waypoint.d.ts +3 -3
- package/dist/middleware/serve-first-waypoint.js +8 -6
- package/dist/middleware/serve-first-waypoint.js.map +1 -1
- package/dist/middleware/session.js +14 -11
- package/dist/middleware/session.js.map +1 -1
- package/dist/middleware/skip-waypoint.d.ts +1 -1
- package/dist/middleware/skip-waypoint.js +3 -3
- package/dist/middleware/skip-waypoint.js.map +1 -1
- package/dist/middleware/steer-journey.d.ts +1 -1
- package/dist/middleware/steer-journey.js +16 -14
- package/dist/middleware/steer-journey.js.map +1 -1
- package/dist/middleware/strip-proxy-path.d.ts +1 -1
- package/dist/middleware/strip-proxy-path.js +3 -3
- package/dist/middleware/strip-proxy-path.js.map +1 -1
- package/dist/middleware/validate-fields.d.ts +1 -1
- package/dist/middleware/validate-fields.js +2 -5
- package/dist/middleware/validate-fields.js.map +1 -1
- package/dist/routes/ancillary.d.ts +3 -3
- package/dist/routes/ancillary.js +4 -4
- package/dist/routes/ancillary.js.map +1 -1
- package/dist/routes/journey.d.ts +2 -2
- package/dist/routes/journey.js +91 -39
- package/dist/routes/journey.js.map +1 -1
- package/dist/routes/static.d.ts +7 -5
- package/dist/routes/static.js +20 -19
- package/dist/routes/static.js.map +1 -1
- package/package.json +19 -18
- package/src/casa.js +133 -100
- package/src/lib/CasaTemplateLoader.js +24 -19
- package/src/lib/JourneyContext.js +138 -107
- package/src/lib/MutableRouter.js +72 -74
- package/src/lib/Plan.js +145 -97
- package/src/lib/ValidationError.js +25 -21
- package/src/lib/ValidatorFactory.js +17 -13
- package/src/lib/configuration-ingestor.js +147 -110
- package/src/lib/configure.js +34 -32
- package/src/lib/constants.js +9 -11
- package/src/lib/context-id-generators.js +40 -43
- package/src/lib/end-session.js +6 -6
- package/src/lib/field.js +69 -58
- package/src/lib/index.js +12 -12
- package/src/lib/logger.js +9 -9
- package/src/lib/mount.js +70 -74
- package/src/lib/nunjucks-filters.js +56 -59
- package/src/lib/nunjucks.js +23 -18
- package/src/lib/utils.js +78 -57
- package/src/lib/validators/dateObject.js +71 -60
- package/src/lib/validators/email.js +8 -8
- package/src/lib/validators/inArray.js +10 -11
- package/src/lib/validators/index.js +12 -14
- package/src/lib/validators/nino.js +29 -15
- package/src/lib/validators/postalAddressObject.js +87 -63
- package/src/lib/validators/range.js +14 -12
- package/src/lib/validators/regex.js +8 -8
- package/src/lib/validators/required.js +16 -16
- package/src/lib/validators/strlen.js +16 -14
- package/src/lib/validators/wordCount.js +22 -14
- package/src/lib/waypoint-url.js +64 -46
- package/src/middleware/body-parser.js +10 -10
- package/src/middleware/csrf.js +1 -1
- package/src/middleware/data.js +28 -24
- package/src/middleware/gather-fields.js +10 -9
- package/src/middleware/i18n.js +35 -37
- package/src/middleware/post.js +41 -21
- package/src/middleware/pre.js +62 -41
- package/src/middleware/progress-journey.js +32 -18
- package/src/middleware/sanitise-fields.js +43 -20
- package/src/middleware/serve-first-waypoint.js +14 -12
- package/src/middleware/session.js +74 -61
- package/src/middleware/skip-waypoint.js +7 -9
- package/src/middleware/steer-journey.js +40 -28
- package/src/middleware/strip-proxy-path.js +8 -7
- package/src/middleware/validate-fields.js +5 -12
- package/src/routes/ancillary.js +5 -7
- package/src/routes/journey.js +159 -85
- package/src/routes/static.js +62 -29
- package/views/casa/components/character-count/README.md +2 -2
- package/views/casa/components/checkboxes/README.md +6 -6
- package/views/casa/components/date-input/README.md +7 -7
- package/views/casa/components/input/README.md +2 -2
- package/views/casa/components/journey-form/README.md +33 -14
- package/views/casa/components/postal-address-object/README.md +4 -4
- package/views/casa/components/radios/README.md +6 -6
- package/views/casa/components/select/README.md +6 -6
- package/views/casa/components/textarea/README.md +2 -2
- package/views/casa/layouts/main.njk +2 -1
package/src/lib/waypoint-url.js
CHANGED
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @typedef {import("./index").JourneyContext} JourneyContext
|
|
2
3
|
* @access private
|
|
3
|
-
* @typedef {import('./index').JourneyContext} JourneyContext
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/** @access private */
|
|
7
|
-
const reUrlProtocolExtract = /^url:\/\/(.+)$/i
|
|
7
|
+
const reUrlProtocolExtract = /^url:\/\/(.+)$/i;
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Sanitise a waypoint string.
|
|
11
11
|
*
|
|
12
|
-
* @access private
|
|
13
12
|
* @param {string} w Waypoint
|
|
14
13
|
* @returns {string} Sanitised waypoint
|
|
14
|
+
* @access private
|
|
15
15
|
*/
|
|
16
|
-
const sanitiseWaypoint = (w) =>
|
|
16
|
+
const sanitiseWaypoint = (w) =>
|
|
17
|
+
w.replace(/[^/a-z0-9_-]/gi, "").replace(/\/+/g, "/");
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
|
-
* Sanitise a waypoint string, with allowed URL parameters:
|
|
20
|
-
*
|
|
20
|
+
* Sanitise a waypoint string, with allowed URL parameters: contextid =
|
|
21
|
+
* JourneyContext ID
|
|
21
22
|
*
|
|
22
|
-
* @access private
|
|
23
23
|
* @param {string} w Waypoint and potential URL parameters
|
|
24
24
|
* @returns {string} Sanitised waypoint
|
|
25
|
+
* @access private
|
|
25
26
|
*/
|
|
26
27
|
const sanitiseWaypointWithAllowedParams = (w) => {
|
|
27
28
|
// Extract URL params
|
|
28
|
-
const parts = w.split(
|
|
29
|
+
const parts = w.split("?");
|
|
29
30
|
if (parts.length !== 2) {
|
|
30
31
|
return sanitiseWaypoint(w);
|
|
31
32
|
}
|
|
@@ -34,63 +35,77 @@ const sanitiseWaypointWithAllowedParams = (w) => {
|
|
|
34
35
|
|
|
35
36
|
// Strip all but those parameters allowed
|
|
36
37
|
const validatedUrlSearchParams = new URLSearchParams();
|
|
37
|
-
for (const pk of [
|
|
38
|
+
for (const pk of ["contextid"]) {
|
|
38
39
|
if (urlSearchParams.has(pk)) {
|
|
39
40
|
validatedUrlSearchParams.set(pk, urlSearchParams.get(pk));
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
return `${sanitiseWaypoint(waypoint)}?${validatedUrlSearchParams.toString()}`.replace(
|
|
44
|
-
|
|
44
|
+
return `${sanitiseWaypoint(waypoint)}?${validatedUrlSearchParams.toString()}`.replace(
|
|
45
|
+
/\?$/,
|
|
46
|
+
"",
|
|
47
|
+
);
|
|
48
|
+
};
|
|
45
49
|
|
|
46
50
|
/**
|
|
47
51
|
* Generate a URL pointing at a particular waypoint.
|
|
48
52
|
*
|
|
49
|
-
* @example
|
|
50
|
-
* // generates: /path/details?edit&editorigin=%2Fsomewhere%2Felse
|
|
51
|
-
* waypointUrl({
|
|
52
|
-
* mountUrl: '/path/',
|
|
53
|
-
* waypoint: 'details',
|
|
54
|
-
* edit: true,
|
|
55
|
-
* editOrigin: '/somewhere/else'
|
|
56
|
-
* })
|
|
57
53
|
* @memberof module:@dwp/govuk-casa
|
|
54
|
+
* @example
|
|
55
|
+
* // generates: /path/details?edit&editorigin=%2Fsomewhere%2Felse
|
|
56
|
+
* waypointUrl({
|
|
57
|
+
* mountUrl: "/path/",
|
|
58
|
+
* waypoint: "details",
|
|
59
|
+
* edit: true,
|
|
60
|
+
* editOrigin: "/somewhere/else",
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
58
63
|
* @param {object} obj Options
|
|
59
|
-
* @param {string} [obj.waypoint=""] Waypoint
|
|
60
|
-
* @param {string} [obj.mountUrl="/"] Mount URL
|
|
64
|
+
* @param {string} [obj.waypoint=""] Waypoint. Default is `""`
|
|
65
|
+
* @param {string} [obj.mountUrl="/"] Mount URL. Default is `"/"`
|
|
61
66
|
* @param {JourneyContext} [obj.journeyContext] JourneyContext
|
|
62
|
-
* @param {boolean} [obj.edit=false] Turn edit mode on or off
|
|
67
|
+
* @param {boolean} [obj.edit=false] Turn edit mode on or off. Default is
|
|
68
|
+
* `false`
|
|
63
69
|
* @param {string} [obj.editOrigin] Edit mode original URL
|
|
64
70
|
* @param {boolean} [obj.skipTo] Skip to this waypoint from the current one
|
|
65
|
-
* @param {string} [obj.routeName=next] Plan route name; next | prev
|
|
71
|
+
* @param {string} [obj.routeName=next] Plan route name; next | prev. Default is
|
|
72
|
+
* `next`
|
|
66
73
|
* @returns {string} URL
|
|
67
74
|
*/
|
|
68
|
-
export default function waypointUrl(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
export default function waypointUrl(
|
|
76
|
+
{
|
|
77
|
+
waypoint = "",
|
|
78
|
+
mountUrl = "/",
|
|
79
|
+
journeyContext,
|
|
80
|
+
edit = false,
|
|
81
|
+
editOrigin,
|
|
82
|
+
skipTo,
|
|
83
|
+
routeName = "next",
|
|
84
|
+
} = Object.create(null),
|
|
85
|
+
) {
|
|
86
|
+
const url = new URL("https://placeholder.test");
|
|
78
87
|
|
|
79
88
|
// Handle url:// protocol
|
|
80
89
|
// - This will generate a link to the root handler "_" for the given mount path
|
|
81
|
-
if (String(waypoint).substr(0, 7) ===
|
|
90
|
+
if (String(waypoint).substr(0, 7) === "url:///") {
|
|
82
91
|
const m = waypoint.match(reUrlProtocolExtract);
|
|
83
92
|
|
|
84
|
-
const u = new URL(
|
|
93
|
+
const u = new URL(
|
|
94
|
+
sanitiseWaypointWithAllowedParams(m[1]),
|
|
95
|
+
"https://placeholder.test/",
|
|
96
|
+
);
|
|
85
97
|
url.pathname = `${sanitiseWaypoint(u.pathname)}/_/`;
|
|
86
98
|
|
|
87
|
-
url.searchParams.set(
|
|
88
|
-
url.searchParams.set(
|
|
99
|
+
url.searchParams.set("refmount", `url://${mountUrl}`);
|
|
100
|
+
url.searchParams.set("route", routeName);
|
|
89
101
|
for (const [uk, uv] of u.searchParams.entries()) {
|
|
90
102
|
url.searchParams.append(uk, uv);
|
|
91
103
|
}
|
|
92
104
|
} else {
|
|
93
|
-
const u = new URL(
|
|
105
|
+
const u = new URL(
|
|
106
|
+
sanitiseWaypointWithAllowedParams(`${mountUrl}${waypoint}`),
|
|
107
|
+
"https://placeholder.test/",
|
|
108
|
+
);
|
|
94
109
|
url.pathname = u.pathname;
|
|
95
110
|
url.search = u.search;
|
|
96
111
|
}
|
|
@@ -100,26 +115,29 @@ export default function waypointUrl({
|
|
|
100
115
|
// added if the context ID already appears in the url path, i.e. to avoid
|
|
101
116
|
// `/path/1234-abcd/waypoint?contextid=1234-abcd` scenarios
|
|
102
117
|
if (
|
|
103
|
-
journeyContext
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
118
|
+
journeyContext &&
|
|
119
|
+
!journeyContext.isDefault() &&
|
|
120
|
+
journeyContext.identity.id &&
|
|
121
|
+
!mountUrl.includes(`/${journeyContext.identity.id}/`)
|
|
107
122
|
) {
|
|
108
|
-
url.searchParams.set(
|
|
123
|
+
url.searchParams.set("contextid", journeyContext.identity.id);
|
|
109
124
|
}
|
|
110
125
|
|
|
111
126
|
// Attach edit mode flag
|
|
112
127
|
if (edit === true) {
|
|
113
|
-
url.searchParams.set(
|
|
128
|
+
url.searchParams.set("edit", "true");
|
|
114
129
|
}
|
|
115
130
|
|
|
116
131
|
if (edit && editOrigin) {
|
|
117
|
-
url.searchParams.set(
|
|
132
|
+
url.searchParams.set(
|
|
133
|
+
"editorigin",
|
|
134
|
+
sanitiseWaypointWithAllowedParams(editOrigin),
|
|
135
|
+
);
|
|
118
136
|
}
|
|
119
137
|
|
|
120
138
|
// Skipto
|
|
121
139
|
if (skipTo) {
|
|
122
|
-
url.searchParams.set(
|
|
140
|
+
url.searchParams.set("skipto", sanitiseWaypointWithAllowedParams(skipTo));
|
|
123
141
|
}
|
|
124
142
|
|
|
125
143
|
return `${sanitiseWaypoint(url.pathname)}${url.search}`;
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import { urlencoded as expressBodyParser } from
|
|
1
|
+
import { urlencoded as expressBodyParser } from "express";
|
|
2
2
|
|
|
3
3
|
const rProto = /__proto__/i;
|
|
4
4
|
const rPrototype = /prototype[='"[\]]/i;
|
|
5
5
|
const rConstructor = /constructor[='"[\]]/i;
|
|
6
6
|
|
|
7
7
|
export function verifyBody(req, res, buf, encoding) {
|
|
8
|
-
const body = decodeURI(buf.toString(encoding)).replace(
|
|
8
|
+
const body = decodeURI(buf.toString(encoding)).replace(
|
|
9
|
+
/[\s\u200B-\u200D\uFEFF]/g,
|
|
10
|
+
"",
|
|
11
|
+
);
|
|
9
12
|
if (rProto.test(body)) {
|
|
10
|
-
throw new Error(
|
|
13
|
+
throw new Error("Request body verification failed (__proto__)");
|
|
11
14
|
}
|
|
12
15
|
if (rPrototype.test(body)) {
|
|
13
|
-
throw new Error(
|
|
16
|
+
throw new Error("Request body verification failed (prototype)");
|
|
14
17
|
}
|
|
15
18
|
if (rConstructor.test(body)) {
|
|
16
|
-
throw new Error(
|
|
19
|
+
throw new Error("Request body verification failed (constructor)");
|
|
17
20
|
}
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
export default function bodyParserMiddleware({
|
|
21
|
-
formMaxParams,
|
|
22
|
-
formMaxBytes,
|
|
23
|
-
}) {
|
|
23
|
+
export default function bodyParserMiddleware({ formMaxParams, formMaxBytes }) {
|
|
24
24
|
return [
|
|
25
25
|
expressBodyParser({
|
|
26
26
|
extended: true,
|
|
27
|
-
type:
|
|
27
|
+
type: "application/x-www-form-urlencoded",
|
|
28
28
|
inflate: true,
|
|
29
29
|
parameterLimit: formMaxParams,
|
|
30
30
|
limit: formMaxBytes,
|
package/src/middleware/csrf.js
CHANGED
package/src/middleware/data.js
CHANGED
|
@@ -1,28 +1,24 @@
|
|
|
1
1
|
// Decorates the request with some contextual data about the user's journey
|
|
2
2
|
// through the application. This is used by downstream middleware and templates.
|
|
3
3
|
|
|
4
|
-
import lodash from
|
|
5
|
-
import JourneyContext from
|
|
6
|
-
import { validateUrlPath } from
|
|
7
|
-
import waypointUrl from
|
|
4
|
+
import lodash from "lodash";
|
|
5
|
+
import JourneyContext from "../lib/JourneyContext.js";
|
|
6
|
+
import { validateUrlPath } from "../lib/utils.js";
|
|
7
|
+
import waypointUrl from "../lib/waypoint-url.js";
|
|
8
8
|
|
|
9
9
|
const { has } = lodash;
|
|
10
10
|
|
|
11
11
|
const editOrigin = (req) => {
|
|
12
|
-
if (has(req.query,
|
|
12
|
+
if (has(req.query, "editorigin")) {
|
|
13
13
|
return waypointUrl({ waypoint: req.query.editorigin });
|
|
14
14
|
}
|
|
15
|
-
if (has(req?.body,
|
|
15
|
+
if (has(req?.body, "editorigin")) {
|
|
16
16
|
return waypointUrl({ waypoint: req.body.editorigin });
|
|
17
17
|
}
|
|
18
|
-
return
|
|
19
|
-
}
|
|
18
|
+
return "";
|
|
19
|
+
};
|
|
20
20
|
|
|
21
|
-
export default function dataMiddleware({
|
|
22
|
-
plan,
|
|
23
|
-
events,
|
|
24
|
-
contextIdGenerator,
|
|
25
|
-
}) {
|
|
21
|
+
export default function dataMiddleware({ plan, events, contextIdGenerator }) {
|
|
26
22
|
return [
|
|
27
23
|
(req, res, next) => {
|
|
28
24
|
/* ------------------------------------------------ Request decorations */
|
|
@@ -36,10 +32,15 @@ export default function dataMiddleware({
|
|
|
36
32
|
|
|
37
33
|
// Current journey context, loaded from session, specified by
|
|
38
34
|
// `contextid` request parameter
|
|
39
|
-
journeyContext:
|
|
35
|
+
journeyContext:
|
|
36
|
+
JourneyContext.extractContextFromRequest(req).addEventListeners(
|
|
37
|
+
events,
|
|
38
|
+
),
|
|
40
39
|
|
|
41
40
|
// Edit mode
|
|
42
|
-
editMode:
|
|
41
|
+
editMode:
|
|
42
|
+
(has(req?.query, "edit") && has(req?.query, "editorigin")) ||
|
|
43
|
+
(has(req?.body, "edit") && has(req?.body, "editorigin")),
|
|
43
44
|
editOrigin: editOrigin(req),
|
|
44
45
|
};
|
|
45
46
|
|
|
@@ -56,7 +57,7 @@ export default function dataMiddleware({
|
|
|
56
57
|
/* ------------------------------------------------- Template variables */
|
|
57
58
|
|
|
58
59
|
// Capture mount URL that will be used in generating all browser URLs
|
|
59
|
-
const mountUrl = validateUrlPath(`${req.baseUrl}/`.replace(/\/+/g,
|
|
60
|
+
const mountUrl = validateUrlPath(`${req.baseUrl}/`.replace(/\/+/g, "/"));
|
|
60
61
|
|
|
61
62
|
// If this CASA app is mounted on a parameterised route, then all of its
|
|
62
63
|
// static assets (served by `staticRouter`) will, by default, be served
|
|
@@ -77,7 +78,9 @@ export default function dataMiddleware({
|
|
|
77
78
|
// Router, the `baseUrl` is different in each case, so we cannot rely
|
|
78
79
|
// on it to be consistent. Hence the need for this property, which will
|
|
79
80
|
// always be the non-parameterised version of the baseUrl.
|
|
80
|
-
const staticMountUrl = validateUrlPath(
|
|
81
|
+
const staticMountUrl = validateUrlPath(
|
|
82
|
+
`${req.unparameterisedBaseUrl}/`.replace(/\/+/g, "/"),
|
|
83
|
+
);
|
|
81
84
|
|
|
82
85
|
// CASA and userland templates
|
|
83
86
|
res.locals.casa = {
|
|
@@ -99,13 +102,14 @@ export default function dataMiddleware({
|
|
|
99
102
|
// the template author does not have to be concerned about the current
|
|
100
103
|
// "state" when generating URLs, but still has the ability to override
|
|
101
104
|
// these curried defaults if needs be.
|
|
102
|
-
res.locals.waypointUrl = (args) =>
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
res.locals.waypointUrl = (args) =>
|
|
106
|
+
waypointUrl({
|
|
107
|
+
mountUrl,
|
|
108
|
+
journeyContext: req.casa.journeyContext,
|
|
109
|
+
edit: req.casa.editMode,
|
|
110
|
+
editOrigin: req.casa.editOrigin,
|
|
111
|
+
...args,
|
|
112
|
+
});
|
|
109
113
|
|
|
110
114
|
next();
|
|
111
115
|
},
|
|
@@ -3,29 +3,27 @@
|
|
|
3
3
|
// - Update the user's journey context with the new data
|
|
4
4
|
// - Remove validation date of JourneyContext so it can re-evaluted
|
|
5
5
|
|
|
6
|
-
import JourneyContext from
|
|
7
|
-
import { REQUEST_PHASE_GATHER } from
|
|
6
|
+
import JourneyContext from "../lib/JourneyContext.js";
|
|
7
|
+
import { REQUEST_PHASE_GATHER } from "../lib/constants.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
+
* @typedef {import("../lib/field").PageField} PageField
|
|
10
11
|
* @access private
|
|
11
|
-
* @typedef {import('../lib/field').PageField} PageField
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Gather the field data from `req.body` into the current JourneyContext
|
|
16
|
+
*
|
|
16
17
|
* - Store in the current session
|
|
17
18
|
* - Update the user's journey context with the new data
|
|
18
19
|
* - Remove validation date of JourneyContext so it can re-evaluted
|
|
19
20
|
*
|
|
20
21
|
* @param {object} obj Options
|
|
21
22
|
* @param {string} obj.waypoint Waypoint
|
|
22
|
-
* @param {PageField[]} [obj.fields=[]] Fields
|
|
23
|
+
* @param {PageField[]} [obj.fields=[]] Fields. Default is `[]`
|
|
23
24
|
* @returns {Array} Array of middleware
|
|
24
25
|
*/
|
|
25
|
-
export default ({
|
|
26
|
-
waypoint,
|
|
27
|
-
fields = [],
|
|
28
|
-
}) => [
|
|
26
|
+
export default ({ waypoint, fields = [] }) => [
|
|
29
27
|
(req, res, next) => {
|
|
30
28
|
// Store a copy of the journey context before modifying it. This is useful
|
|
31
29
|
// for any comparison work that may be done in subsequent middleware.
|
|
@@ -39,7 +37,10 @@ export default ({
|
|
|
39
37
|
/* eslint-disable security/detect-object-injection */
|
|
40
38
|
const persistentBody = Object.create(null);
|
|
41
39
|
for (let i = 0, l = fields.length; i < l; i++) {
|
|
42
|
-
if (
|
|
40
|
+
if (
|
|
41
|
+
fields[i].meta.persist &&
|
|
42
|
+
fields[i].getValue(req.body) !== undefined
|
|
43
|
+
) {
|
|
43
44
|
persistentBody[fields[i].name] = fields[i].getValue(req.body);
|
|
44
45
|
}
|
|
45
46
|
}
|
package/src/middleware/i18n.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import { createInstance } from
|
|
2
|
-
import { LanguageDetector, handle } from
|
|
3
|
-
import { resolve, basename } from
|
|
4
|
-
import { existsSync, readFileSync, readdirSync } from
|
|
5
|
-
import deepmerge from
|
|
6
|
-
import yaml from
|
|
7
|
-
import logger from
|
|
1
|
+
import { createInstance } from "i18next";
|
|
2
|
+
import { LanguageDetector, handle } from "i18next-http-middleware";
|
|
3
|
+
import { resolve, basename } from "path";
|
|
4
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
5
|
+
import deepmerge from "deepmerge";
|
|
6
|
+
import yaml from "js-yaml";
|
|
7
|
+
import logger from "../lib/logger.js";
|
|
8
8
|
|
|
9
|
-
const log = logger(
|
|
9
|
+
const log = logger("middleware:i18n");
|
|
10
10
|
|
|
11
11
|
const loadJson = (file) => {
|
|
12
12
|
// Strip out newlines (this is a legacy feature which we're keeping for
|
|
13
13
|
// backwards compatibility).
|
|
14
14
|
/* eslint-disable-next-line security/detect-non-literal-fs-filename */
|
|
15
|
-
const json = readFileSync(file,
|
|
16
|
-
return JSON.parse(json.replace(/[\r\n]/g,
|
|
17
|
-
}
|
|
15
|
+
const json = readFileSync(file, "utf8");
|
|
16
|
+
return JSON.parse(json.replace(/[\r\n]/g, ""));
|
|
17
|
+
};
|
|
18
18
|
|
|
19
19
|
/* eslint-disable-next-line security/detect-non-literal-fs-filename */
|
|
20
|
-
const loadYaml = (file) => yaml.load(readFileSync(file,
|
|
20
|
+
const loadYaml = (file) => yaml.load(readFileSync(file, "utf8"));
|
|
21
21
|
|
|
22
22
|
const extract = (file) => {
|
|
23
|
-
const ext = /.yaml$/i.test(file) ?
|
|
24
|
-
const data = ext ===
|
|
23
|
+
const ext = /.yaml$/i.test(file) ? ".yaml" : ".json";
|
|
24
|
+
const data = ext === ".yaml" ? loadYaml(file) : loadJson(file);
|
|
25
25
|
|
|
26
26
|
return {
|
|
27
27
|
ns: basename(file, ext),
|
|
28
28
|
data,
|
|
29
29
|
};
|
|
30
|
-
}
|
|
30
|
+
};
|
|
31
31
|
|
|
32
32
|
const loadResources = (languages, directories) => {
|
|
33
33
|
const store = Object.create(null);
|
|
@@ -45,7 +45,7 @@ const loadResources = (languages, directories) => {
|
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
log.info(
|
|
48
|
+
log.info("Loading %s language from %s ...", language, dir);
|
|
49
49
|
/* eslint-disable-next-line security/detect-non-literal-fs-filename */
|
|
50
50
|
readdirSync(dir).forEach((file) => {
|
|
51
51
|
const { ns, data } = extract(resolve(dir, file));
|
|
@@ -61,10 +61,10 @@ const loadResources = (languages, directories) => {
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
return store;
|
|
64
|
-
}
|
|
64
|
+
};
|
|
65
65
|
|
|
66
66
|
export default function i18nMiddleware({
|
|
67
|
-
languages = [
|
|
67
|
+
languages = ["en", "cy"],
|
|
68
68
|
directories = [],
|
|
69
69
|
}) {
|
|
70
70
|
// Load _all_ translations, from all directories into memory.
|
|
@@ -72,25 +72,23 @@ export default function i18nMiddleware({
|
|
|
72
72
|
|
|
73
73
|
// Configure i18next
|
|
74
74
|
const i18nInstance = createInstance();
|
|
75
|
-
i18nInstance
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
},
|
|
93
|
-
});
|
|
75
|
+
i18nInstance.use(LanguageDetector).init({
|
|
76
|
+
initImmediate: false, // because we need synchronous loading
|
|
77
|
+
supportedLngs: languages,
|
|
78
|
+
fallbackLng: false,
|
|
79
|
+
defaultNS: "common",
|
|
80
|
+
// debug: true,
|
|
81
|
+
|
|
82
|
+
// All translation resources
|
|
83
|
+
resources,
|
|
84
|
+
|
|
85
|
+
// LanguageDetector options
|
|
86
|
+
detection: {
|
|
87
|
+
lookupQuerystring: "lang",
|
|
88
|
+
lookupSession: "language",
|
|
89
|
+
order: ["querystring", "session"],
|
|
90
|
+
},
|
|
91
|
+
});
|
|
94
92
|
|
|
95
93
|
// 2 middleware: one to read/set the session language, and one to enhance the
|
|
96
94
|
// req/res objects with i18n features
|
package/src/middleware/post.js
CHANGED
|
@@ -1,61 +1,81 @@
|
|
|
1
1
|
// 2 middleware: one as a fallback 404 handler, one to handle thrown errors
|
|
2
|
-
import logger from
|
|
2
|
+
import logger from "../lib/logger.js";
|
|
3
3
|
|
|
4
|
-
const log = logger(
|
|
4
|
+
const log = logger("middleware:post");
|
|
5
5
|
|
|
6
6
|
export default function postMiddleware() {
|
|
7
7
|
return [
|
|
8
8
|
(req, res) => {
|
|
9
|
-
res.status(404).render(
|
|
9
|
+
res.status(404).render("casa/errors/404.njk");
|
|
10
10
|
},
|
|
11
11
|
/* eslint-disable-next-line no-unused-vars */
|
|
12
12
|
(err, req, res, next) => {
|
|
13
13
|
// In some cases, an error may have been thrown before the template assets
|
|
14
14
|
// have had a chance to initialise. So we use a hardcoded template in
|
|
15
15
|
// these cases to ensure the user sees an appropriate message.
|
|
16
|
-
let TEMPLATE =
|
|
16
|
+
let TEMPLATE = "casa/errors/500.njk";
|
|
17
17
|
if (!res.locals.t) {
|
|
18
|
-
res.locals.t = () =>
|
|
18
|
+
res.locals.t = () => "";
|
|
19
19
|
res.locals.casa = {
|
|
20
20
|
...res.locals?.casa,
|
|
21
21
|
mountUrl: `${req.baseUrl}/`,
|
|
22
22
|
};
|
|
23
|
-
TEMPLATE =
|
|
23
|
+
TEMPLATE = "casa/errors/static.njk";
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// CSRF token is invalid in some way
|
|
27
|
-
if (err?.code ===
|
|
28
|
-
log.info(
|
|
29
|
-
|
|
27
|
+
if (err?.code === "EBADCSRFTOKEN") {
|
|
28
|
+
log.info(
|
|
29
|
+
"CSRF validation has failed. This may be caused by the user submitting a stale form from a previous session [EBADCSRFTOKEN]",
|
|
30
|
+
);
|
|
31
|
+
return res
|
|
32
|
+
.status(403)
|
|
33
|
+
.render(TEMPLATE, { errorCode: "bad_csrf_token", error: err });
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
// Body parsing verification check failed
|
|
33
|
-
if (err?.type ===
|
|
34
|
-
log.info(
|
|
35
|
-
|
|
37
|
+
if (err?.type === "entity.verify.failed") {
|
|
38
|
+
log.info(
|
|
39
|
+
"Body parser verification has failed. This has been caused by the user submitting a payload containing invalid data [entity.verify.failed]",
|
|
40
|
+
);
|
|
41
|
+
return res
|
|
42
|
+
.status(403)
|
|
43
|
+
.render(TEMPLATE, { errorCode: "invalid_payload", error: err });
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
// Too many parameters submitted
|
|
39
|
-
if (err?.type ===
|
|
40
|
-
log.info(
|
|
41
|
-
|
|
47
|
+
if (err?.type === "parameters.too.many") {
|
|
48
|
+
log.info(
|
|
49
|
+
"The request contains more parameters than is currently allowed [parameters.too.many]",
|
|
50
|
+
);
|
|
51
|
+
return res.status(413).render(TEMPLATE, {
|
|
52
|
+
errorCode: "parameter_limit_exceeded",
|
|
53
|
+
error: err,
|
|
54
|
+
});
|
|
42
55
|
}
|
|
43
56
|
|
|
44
57
|
// Overall payload too large
|
|
45
|
-
if (err?.type ===
|
|
46
|
-
log.info(
|
|
47
|
-
|
|
58
|
+
if (err?.type === "entity.too.large") {
|
|
59
|
+
log.info(
|
|
60
|
+
`The request payload is too large. Received ${err.length}b with a maximum of ${err.limit}b [parameters.too.many]`,
|
|
61
|
+
);
|
|
62
|
+
return res
|
|
63
|
+
.status(413)
|
|
64
|
+
.render(TEMPLATE, { errorCode: "payload_size_exceeded", error: err });
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
// Unaccept request method
|
|
51
|
-
if (err?.code ===
|
|
68
|
+
if (err?.code === "unaccepted_request_method") {
|
|
52
69
|
log.info(err.message);
|
|
53
|
-
return res.status(400).render(TEMPLATE, {
|
|
70
|
+
return res.status(400).render(TEMPLATE, {
|
|
71
|
+
errorCode: "unaccepted_request_method",
|
|
72
|
+
error: err,
|
|
73
|
+
});
|
|
54
74
|
}
|
|
55
75
|
|
|
56
76
|
// Unknown error
|
|
57
77
|
log.error(`Unknown error: ${err.message}; stacktrace: ${err.stack}`);
|
|
58
78
|
return res.status(200).render(TEMPLATE, { error: err });
|
|
59
79
|
},
|
|
60
|
-
]
|
|
80
|
+
];
|
|
61
81
|
}
|