@dwp/govuk-casa 8.1.0 → 8.2.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/CHANGELOG.md +11 -0
- package/README.md +2 -0
- package/dist/assets/css/casa-ie8.css +1 -1
- package/dist/assets/css/casa.css +1 -1
- package/dist/casa.d.ts +210 -0
- package/dist/casa.js +102 -0
- package/dist/lib/JourneyContext.d.ts +15 -27
- package/dist/lib/JourneyContext.js +25 -15
- package/dist/lib/configuration-ingestor.d.ts +13 -140
- package/dist/lib/configuration-ingestor.js +22 -88
- package/dist/lib/configure.d.ts +6 -77
- package/dist/lib/configure.js +80 -38
- package/dist/lib/nunjucks.d.ts +1 -6
- package/dist/lib/nunjucks.js +1 -3
- package/dist/lib/utils.d.ts +13 -10
- package/dist/lib/utils.js +40 -8
- package/dist/lib/waypoint-url.js +8 -2
- package/dist/middleware/body-parser.js +1 -1
- package/dist/middleware/data.d.ts +1 -2
- package/dist/middleware/data.js +12 -2
- package/dist/middleware/post.d.ts +1 -3
- package/dist/middleware/post.js +2 -2
- package/dist/middleware/pre.d.ts +1 -1
- package/dist/middleware/pre.js +5 -4
- package/dist/middleware/progress-journey.d.ts +1 -2
- package/dist/middleware/progress-journey.js +2 -2
- package/dist/middleware/session.d.ts +1 -2
- package/dist/middleware/session.js +7 -5
- package/dist/middleware/skip-waypoint.d.ts +1 -2
- package/dist/middleware/skip-waypoint.js +2 -2
- package/dist/middleware/steer-journey.d.ts +1 -2
- package/dist/middleware/steer-journey.js +2 -2
- package/dist/middleware/validate-fields.d.ts +1 -2
- package/dist/middleware/validate-fields.js +2 -1
- package/dist/routes/journey.d.ts +1 -2
- package/dist/routes/journey.js +8 -8
- package/dist/routes/static.d.ts +1 -6
- package/dist/routes/static.js +6 -6
- package/package.json +25 -23
- package/views/casa/components/journey-form/README.md +3 -0
- package/views/casa/components/journey-form/template.njk +1 -1
- package/views/casa/partials/scripts.njk +1 -1
- package/views/casa/partials/styles.njk +2 -2
|
@@ -10,61 +10,10 @@ const Plan_js_1 = __importDefault(require("./Plan.js"));
|
|
|
10
10
|
const logger_js_1 = __importDefault(require("./logger.js"));
|
|
11
11
|
const utils_js_1 = require("./utils.js");
|
|
12
12
|
/**
|
|
13
|
-
* @typedef {import('
|
|
13
|
+
* @typedef {import('../casa').ConfigurationOptions} ConfigurationOptions
|
|
14
14
|
*/
|
|
15
15
|
/**
|
|
16
|
-
* @typedef {
|
|
17
|
-
* @property {string} [name=casasession] Session name
|
|
18
|
-
* @property {string} [secret=secret] Encryption secret
|
|
19
|
-
* @property {number} [ttl=3600] Session ttl (seconds)
|
|
20
|
-
* @property {boolean} [secure=false] Whether to use secure session cookies
|
|
21
|
-
* @property {boolean|string} [cookieSameSite=true] SameSite (true = Strict)
|
|
22
|
-
* @property {object} [store] Session store (default MemoryStore)
|
|
23
|
-
*/
|
|
24
|
-
/**
|
|
25
|
-
* @typedef {object} I18nOptions
|
|
26
|
-
* @property {string[]} dirs Directories to search for locale dictionaries
|
|
27
|
-
* @property {string[]} [locales=['en', 'cy']] Supported locales
|
|
28
|
-
*/
|
|
29
|
-
/**
|
|
30
|
-
* @typedef {object} GlobalHook Hook configuration
|
|
31
|
-
* @property {string} hook Hook name in format `<router>.<hook>`
|
|
32
|
-
* @property {Function} middleware Middleware function to insert at the hook point
|
|
33
|
-
* @property {string|RegExp} [path=undefined] Only run if route path matches this string/regexp
|
|
34
|
-
*/
|
|
35
|
-
/**
|
|
36
|
-
* @typedef {object} PageHook (extends GlobalHook)
|
|
37
|
-
* @property {string} hook Hook name (without a scope prefix)
|
|
38
|
-
* @property {Function} middleware Middleware function to insert at the hook point
|
|
39
|
-
*/
|
|
40
|
-
/**
|
|
41
|
-
* @typedef {object} Page Page configuration. A Page is the interactive representation of a waypoint
|
|
42
|
-
* @property {string} waypoint The waypoint with which this page is associated
|
|
43
|
-
* @property {string} view Template path
|
|
44
|
-
* @property {PageHook[]} [hooks=[]] Page-specific hooks (optional, default [])
|
|
45
|
-
* @property {PageField[]} [fields=[]] Fields to be managed on this page (optional, default [])
|
|
46
|
-
*/
|
|
47
|
-
/**
|
|
48
|
-
* @callback HelmetConfigurator
|
|
49
|
-
* @param {object} config A default Helmet configuration provided by CASA
|
|
50
|
-
* @returns {object} The modified configuration object
|
|
51
|
-
*/
|
|
52
|
-
/**
|
|
53
|
-
* Configure some middleware for use in creating a new CASA app.
|
|
54
|
-
*
|
|
55
|
-
* `mountUrl` is used to ensure the CSS content uses the correct reference to
|
|
56
|
-
* static assets in the `govuk-frontend` module.
|
|
57
|
-
*
|
|
58
|
-
* @typedef {object} ConfigurationOptions Configuration options
|
|
59
|
-
* @property {string} [mountUrl=/] URL path to root of CASA app
|
|
60
|
-
* @property {string[]} [views=[]] Template directories
|
|
61
|
-
* @property {SessionOptions} [session] Session configuration
|
|
62
|
-
* @property {Page[]} [pages=[]] Pages the represent waypoints
|
|
63
|
-
* @property {GlobalHook[]} [hooks=[]] Hooks to apply
|
|
64
|
-
* @property {object[]} [plugins=[]] Plugins
|
|
65
|
-
* @property {I18nOptions[]} [i18n] I18n configuration
|
|
66
|
-
* @property {Plan} plan CASA Plan
|
|
67
|
-
* @property {ContextEvent[]} [events=[]] Handlers for JourneyContext events
|
|
16
|
+
* @typedef {import('../casa').HelmetConfigurator} HelmetConfigurator
|
|
68
17
|
*/
|
|
69
18
|
const log = (0, logger_js_1.default)('lib:configuration-ingestor');
|
|
70
19
|
const echo = (a) => (a);
|
|
@@ -76,7 +25,7 @@ const echo = (a) => (a);
|
|
|
76
25
|
* @throws {TypeError} For invalid object.
|
|
77
26
|
* @returns {object} Sanitised i18n object.
|
|
78
27
|
*/
|
|
79
|
-
function validateI18nObject(i18n, cb = echo) {
|
|
28
|
+
function validateI18nObject(i18n = Object.create(null), cb = echo) {
|
|
80
29
|
if (Object.prototype.toString.call(i18n) !== '[object Object]') {
|
|
81
30
|
throw new TypeError('I18n must be an object');
|
|
82
31
|
}
|
|
@@ -91,11 +40,8 @@ exports.validateI18nObject = validateI18nObject;
|
|
|
91
40
|
* @throws {TypeError} For invalid type.
|
|
92
41
|
* @returns {Array} Array of directories.
|
|
93
42
|
*/
|
|
94
|
-
function validateI18nDirs(dirs) {
|
|
95
|
-
if (
|
|
96
|
-
throw ReferenceError('I18n directories are missing (i18n.dirs)');
|
|
97
|
-
}
|
|
98
|
-
else if (!Array.isArray(dirs)) {
|
|
43
|
+
function validateI18nDirs(dirs = []) {
|
|
44
|
+
if (!Array.isArray(dirs)) {
|
|
99
45
|
throw new TypeError('I18n directories must be an array (i18n.dirs)');
|
|
100
46
|
}
|
|
101
47
|
dirs.forEach((dir, i) => {
|
|
@@ -114,11 +60,8 @@ exports.validateI18nDirs = validateI18nDirs;
|
|
|
114
60
|
* @throws {TypeError} For invalid type.
|
|
115
61
|
* @returns {Array} Array of locales.
|
|
116
62
|
*/
|
|
117
|
-
function validateI18nLocales(locales) {
|
|
118
|
-
if (
|
|
119
|
-
throw ReferenceError('I18n locales are missing (i18n.locales)');
|
|
120
|
-
}
|
|
121
|
-
else if (!Array.isArray(locales)) {
|
|
63
|
+
function validateI18nLocales(locales = ['en', 'cy']) {
|
|
64
|
+
if (!Array.isArray(locales)) {
|
|
122
65
|
throw new TypeError('I18n locales must be an array (i18n.locales)');
|
|
123
66
|
}
|
|
124
67
|
locales.forEach((locale, i) => {
|
|
@@ -155,7 +98,7 @@ exports.validateMountUrl = validateMountUrl;
|
|
|
155
98
|
* @throws {TypeError} For invalid object.
|
|
156
99
|
* @returns {object} Sanitised sessions object.
|
|
157
100
|
*/
|
|
158
|
-
function validateSessionObject(session, cb = echo) {
|
|
101
|
+
function validateSessionObject(session = Object.create(null), cb = echo) {
|
|
159
102
|
if (session === undefined) {
|
|
160
103
|
return cb(session);
|
|
161
104
|
}
|
|
@@ -173,11 +116,8 @@ exports.validateSessionObject = validateSessionObject;
|
|
|
173
116
|
* @throws {TypeError} For invalid type.
|
|
174
117
|
* @returns {Array} Array of directories.
|
|
175
118
|
*/
|
|
176
|
-
function validateViews(dirs) {
|
|
177
|
-
if (
|
|
178
|
-
throw ReferenceError('View directories are missing (views)');
|
|
179
|
-
}
|
|
180
|
-
else if (!Array.isArray(dirs)) {
|
|
119
|
+
function validateViews(dirs = []) {
|
|
120
|
+
if (!Array.isArray(dirs)) {
|
|
181
121
|
throw new TypeError('View directories must be an array (views)');
|
|
182
122
|
}
|
|
183
123
|
dirs.forEach((dir, i) => {
|
|
@@ -214,11 +154,8 @@ exports.validateSessionSecret = validateSessionSecret;
|
|
|
214
154
|
* @throws {TypeError} For invalid value.
|
|
215
155
|
* @returns {number} Ttl.
|
|
216
156
|
*/
|
|
217
|
-
function validateSessionTtl(ttl) {
|
|
218
|
-
if (typeof ttl
|
|
219
|
-
throw ReferenceError('Session ttl is missing (session.ttl)');
|
|
220
|
-
}
|
|
221
|
-
else if (typeof ttl !== 'number') {
|
|
157
|
+
function validateSessionTtl(ttl = 3600) {
|
|
158
|
+
if (typeof ttl !== 'number') {
|
|
222
159
|
throw new TypeError('Session ttl must be an integer (session.ttl)');
|
|
223
160
|
}
|
|
224
161
|
return ttl;
|
|
@@ -227,16 +164,13 @@ exports.validateSessionTtl = validateSessionTtl;
|
|
|
227
164
|
/**
|
|
228
165
|
* Validates and sanitises sessions name.
|
|
229
166
|
*
|
|
230
|
-
* @param {string} name Session name.
|
|
167
|
+
* @param {string} [name=casa-session] Session name.
|
|
231
168
|
* @throws {ReferenceError} For missing value type.
|
|
232
169
|
* @throws {TypeError} For invalid value.
|
|
233
170
|
* @returns {string} Name.
|
|
234
171
|
*/
|
|
235
|
-
function validateSessionName(name) {
|
|
236
|
-
if (typeof name
|
|
237
|
-
throw ReferenceError('Session name is missing (session.name)');
|
|
238
|
-
}
|
|
239
|
-
else if (typeof name !== 'string') {
|
|
172
|
+
function validateSessionName(name = 'casa-session') {
|
|
173
|
+
if (typeof name !== 'string') {
|
|
240
174
|
throw new TypeError('Session name must be a string (session.name)');
|
|
241
175
|
}
|
|
242
176
|
return name;
|
|
@@ -245,16 +179,13 @@ exports.validateSessionName = validateSessionName;
|
|
|
245
179
|
/**
|
|
246
180
|
* Validates and sanitises sessions secure flag.
|
|
247
181
|
*
|
|
248
|
-
* @param {boolean} secure Session secure flag.
|
|
182
|
+
* @param {boolean} [secure=false] Session secure flag.
|
|
249
183
|
* @throws {ReferenceError} For missing value type.
|
|
250
184
|
* @throws {TypeError} For invalid value.
|
|
251
185
|
* @returns {string} Name.
|
|
252
186
|
*/
|
|
253
|
-
function validateSessionSecure(secure) {
|
|
254
|
-
if (typeof secure
|
|
255
|
-
throw ReferenceError('Session secure flag is missing (session.secure)');
|
|
256
|
-
}
|
|
257
|
-
else if (typeof secure !== 'boolean') {
|
|
187
|
+
function validateSessionSecure(secure = false) {
|
|
188
|
+
if (typeof secure !== 'boolean') {
|
|
258
189
|
throw new TypeError('Session secure flag must be boolean (session.secure)');
|
|
259
190
|
}
|
|
260
191
|
return secure;
|
|
@@ -371,7 +302,7 @@ const validatePage = (page, index) => {
|
|
|
371
302
|
throw err;
|
|
372
303
|
}
|
|
373
304
|
};
|
|
374
|
-
function validatePages(pages) {
|
|
305
|
+
function validatePages(pages = []) {
|
|
375
306
|
if (!Array.isArray(pages)) {
|
|
376
307
|
throw new TypeError('Pages must be an array (pages)');
|
|
377
308
|
}
|
|
@@ -451,6 +382,9 @@ function ingest(config = {}) {
|
|
|
451
382
|
dirs: validateI18nDirs(i18n.dirs),
|
|
452
383
|
locales: validateI18nLocales(i18n.locales),
|
|
453
384
|
})),
|
|
385
|
+
// !!! DEPRECATION NOTICE !!!
|
|
386
|
+
// This will be removed in next major version
|
|
387
|
+
//
|
|
454
388
|
// Public URL from which the app will be served
|
|
455
389
|
mountUrl: validateMountUrl(config.mountUrl),
|
|
456
390
|
// Session
|
package/dist/lib/configure.d.ts
CHANGED
|
@@ -1,90 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import('
|
|
2
|
+
* @typedef {import('../casa').ConfigurationOptions} ConfigurationOptions
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
* @typedef {import('
|
|
5
|
+
* @typedef {import('../casa').ConfigurationOptions} ConfigureResult
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
|
-
* @typedef {import('
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {object} ConfigureResult Result of a call to configure() function
|
|
12
|
-
* @property {nunjucks.Environment} nunjucksEnv Nunjucks environment
|
|
13
|
-
* @property {MutableRouter} staticRouter Router handling all static assets
|
|
14
|
-
* @property {MutableRouter} ancillaryRouter Router handling ancillary routes
|
|
15
|
-
* @property {MutableRouter} journeyRouter Router handling all waypoint requests
|
|
16
|
-
* @property {ExpressRequestHandler[]} preMiddleware Middleware mounted before anything else
|
|
17
|
-
* @property {ExpressRequestHandler[]} postMiddleware Middleware mounted after everything else
|
|
18
|
-
* @property {ExpressRequestHandler[]} csrfMiddleware CSRF get/set middleware (useful for forms)
|
|
19
|
-
* @property {ExpressRequestHandler} sessionMiddleware Session middleware
|
|
20
|
-
* @property {ExpressRequestHandler[]} cookieParserMiddleware Cookie-parsing middleware
|
|
21
|
-
* @property {ExpressRequestHandler[]} i18nMiddleware I18n preparation middleware
|
|
22
|
-
* @property {ExpressRequestHandler} bodyParserMiddleware Body parsing middleware
|
|
23
|
-
* @property {Function} mount Function used to mount all CASA artifacts onto an ExpressJS app
|
|
8
|
+
* @typedef {import('../casa').Mounter} Mounter
|
|
24
9
|
*/
|
|
25
10
|
/**
|
|
26
11
|
* Configure some middleware for use in creating a new CASA app.
|
|
27
12
|
*
|
|
28
|
-
* `mountUrl` is used to ensure the CSS content uses the correct reference to
|
|
29
|
-
* static assets in the `govuk-frontend` module.
|
|
30
|
-
*
|
|
31
13
|
* @param {ConfigurationOptions} config Configuration options
|
|
32
14
|
* @returns {ConfigureResult} Result
|
|
33
15
|
*/
|
|
34
16
|
export default function configure(config?: ConfigurationOptions): ConfigureResult;
|
|
35
|
-
export type
|
|
36
|
-
export type
|
|
37
|
-
export type
|
|
38
|
-
/**
|
|
39
|
-
* Result of a call to configure() function
|
|
40
|
-
*/
|
|
41
|
-
export type ConfigureResult = {
|
|
42
|
-
/**
|
|
43
|
-
* Nunjucks environment
|
|
44
|
-
*/
|
|
45
|
-
nunjucksEnv: nunjucks.Environment;
|
|
46
|
-
/**
|
|
47
|
-
* Router handling all static assets
|
|
48
|
-
*/
|
|
49
|
-
staticRouter: MutableRouter;
|
|
50
|
-
/**
|
|
51
|
-
* Router handling ancillary routes
|
|
52
|
-
*/
|
|
53
|
-
ancillaryRouter: MutableRouter;
|
|
54
|
-
/**
|
|
55
|
-
* Router handling all waypoint requests
|
|
56
|
-
*/
|
|
57
|
-
journeyRouter: MutableRouter;
|
|
58
|
-
/**
|
|
59
|
-
* Middleware mounted before anything else
|
|
60
|
-
*/
|
|
61
|
-
preMiddleware: ExpressRequestHandler[];
|
|
62
|
-
/**
|
|
63
|
-
* Middleware mounted after everything else
|
|
64
|
-
*/
|
|
65
|
-
postMiddleware: ExpressRequestHandler[];
|
|
66
|
-
/**
|
|
67
|
-
* CSRF get/set middleware (useful for forms)
|
|
68
|
-
*/
|
|
69
|
-
csrfMiddleware: ExpressRequestHandler[];
|
|
70
|
-
/**
|
|
71
|
-
* Session middleware
|
|
72
|
-
*/
|
|
73
|
-
sessionMiddleware: ExpressRequestHandler;
|
|
74
|
-
/**
|
|
75
|
-
* Cookie-parsing middleware
|
|
76
|
-
*/
|
|
77
|
-
cookieParserMiddleware: ExpressRequestHandler[];
|
|
78
|
-
/**
|
|
79
|
-
* I18n preparation middleware
|
|
80
|
-
*/
|
|
81
|
-
i18nMiddleware: ExpressRequestHandler[];
|
|
82
|
-
/**
|
|
83
|
-
* Body parsing middleware
|
|
84
|
-
*/
|
|
85
|
-
bodyParserMiddleware: ExpressRequestHandler;
|
|
86
|
-
/**
|
|
87
|
-
* Function used to mount all CASA artifacts onto an ExpressJS app
|
|
88
|
-
*/
|
|
89
|
-
mount: Function;
|
|
90
|
-
};
|
|
17
|
+
export type ConfigurationOptions = import('../casa').ConfigurationOptions;
|
|
18
|
+
export type ConfigureResult = import('../casa').ConfigurationOptions;
|
|
19
|
+
export type Mounter = import('../casa').Mounter;
|
package/dist/lib/configure.js
CHANGED
|
@@ -3,11 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const express_1 = require("express");
|
|
6
7
|
const express_session_1 = require("express-session");
|
|
7
8
|
const path_1 = require("path");
|
|
8
9
|
const module_1 = require("module");
|
|
9
10
|
const cookie_parser_1 = __importDefault(require("cookie-parser"));
|
|
11
|
+
const path_to_regexp_1 = require("path-to-regexp");
|
|
10
12
|
const dirname_cjs_1 = __importDefault(require("./dirname.cjs"));
|
|
13
|
+
const utils_js_1 = require("./utils.js");
|
|
11
14
|
const configuration_ingestor_js_1 = __importDefault(require("./configuration-ingestor.js"));
|
|
12
15
|
const nunjucks_js_1 = __importDefault(require("./nunjucks.js"));
|
|
13
16
|
const static_js_1 = __importDefault(require("../routes/static.js"));
|
|
@@ -20,36 +23,20 @@ const i18n_js_1 = __importDefault(require("../middleware/i18n.js"));
|
|
|
20
23
|
const data_js_1 = __importDefault(require("../middleware/data.js"));
|
|
21
24
|
const body_parser_js_1 = __importDefault(require("../middleware/body-parser.js"));
|
|
22
25
|
const csrf_js_1 = __importDefault(require("../middleware/csrf.js"));
|
|
26
|
+
const logger_js_1 = __importDefault(require("./logger.js"));
|
|
27
|
+
const log = (0, logger_js_1.default)('lib:configure');
|
|
23
28
|
/**
|
|
24
|
-
* @typedef {import('
|
|
29
|
+
* @typedef {import('../casa').ConfigurationOptions} ConfigurationOptions
|
|
25
30
|
*/
|
|
26
31
|
/**
|
|
27
|
-
* @typedef {import('
|
|
32
|
+
* @typedef {import('../casa').ConfigurationOptions} ConfigureResult
|
|
28
33
|
*/
|
|
29
34
|
/**
|
|
30
|
-
* @typedef {import('
|
|
31
|
-
*/
|
|
32
|
-
/**
|
|
33
|
-
* @typedef {object} ConfigureResult Result of a call to configure() function
|
|
34
|
-
* @property {nunjucks.Environment} nunjucksEnv Nunjucks environment
|
|
35
|
-
* @property {MutableRouter} staticRouter Router handling all static assets
|
|
36
|
-
* @property {MutableRouter} ancillaryRouter Router handling ancillary routes
|
|
37
|
-
* @property {MutableRouter} journeyRouter Router handling all waypoint requests
|
|
38
|
-
* @property {ExpressRequestHandler[]} preMiddleware Middleware mounted before anything else
|
|
39
|
-
* @property {ExpressRequestHandler[]} postMiddleware Middleware mounted after everything else
|
|
40
|
-
* @property {ExpressRequestHandler[]} csrfMiddleware CSRF get/set middleware (useful for forms)
|
|
41
|
-
* @property {ExpressRequestHandler} sessionMiddleware Session middleware
|
|
42
|
-
* @property {ExpressRequestHandler[]} cookieParserMiddleware Cookie-parsing middleware
|
|
43
|
-
* @property {ExpressRequestHandler[]} i18nMiddleware I18n preparation middleware
|
|
44
|
-
* @property {ExpressRequestHandler} bodyParserMiddleware Body parsing middleware
|
|
45
|
-
* @property {Function} mount Function used to mount all CASA artifacts onto an ExpressJS app
|
|
35
|
+
* @typedef {import('../casa').Mounter} Mounter
|
|
46
36
|
*/
|
|
47
37
|
/**
|
|
48
38
|
* Configure some middleware for use in creating a new CASA app.
|
|
49
39
|
*
|
|
50
|
-
* `mountUrl` is used to ensure the CSS content uses the correct reference to
|
|
51
|
-
* static assets in the `govuk-frontend` module.
|
|
52
|
-
*
|
|
53
40
|
* @param {ConfigurationOptions} config Configuration options
|
|
54
41
|
* @returns {ConfigureResult} Result
|
|
55
42
|
*/
|
|
@@ -82,7 +69,6 @@ function configure(config = {}) {
|
|
|
82
69
|
// Prepare a Nunjucks environment for rendering all templates.
|
|
83
70
|
// Resolve priority: userland templates > CASA templates > GOVUK templates > Plugin templates
|
|
84
71
|
const nunjucksEnv = (0, nunjucks_js_1.default)({
|
|
85
|
-
mountUrl,
|
|
86
72
|
views: [
|
|
87
73
|
...views,
|
|
88
74
|
(0, path_1.resolve)(dirname_cjs_1.default, '../../views'),
|
|
@@ -93,7 +79,7 @@ function configure(config = {}) {
|
|
|
93
79
|
// These _must_ be added to the ExpressJS application at the start and end
|
|
94
80
|
// of all other middleware respectively.
|
|
95
81
|
const preMiddleware = (0, pre_js_1.default)({ helmetConfigurator });
|
|
96
|
-
const postMiddleware = (0, post_js_1.default)(
|
|
82
|
+
const postMiddleware = (0, post_js_1.default)();
|
|
97
83
|
// Prepare common middleware mounted prior to the ancillaryRouter
|
|
98
84
|
const cookieParserMiddleware = (0, cookie_parser_1.default)(session.secret);
|
|
99
85
|
const sessionMiddleware = (0, session_js_1.default)({
|
|
@@ -104,7 +90,6 @@ function configure(config = {}) {
|
|
|
104
90
|
ttl: session.ttl,
|
|
105
91
|
cookieSameSite: session.cookieSameSite,
|
|
106
92
|
cookiePath: session.cookiePath,
|
|
107
|
-
mountUrl,
|
|
108
93
|
store: (_b = session.store) !== null && _b !== void 0 ? _b : new express_session_1.MemoryStore(),
|
|
109
94
|
});
|
|
110
95
|
const i18nMiddleware = (0, i18n_js_1.default)({
|
|
@@ -117,7 +102,6 @@ function configure(config = {}) {
|
|
|
117
102
|
});
|
|
118
103
|
const dataMiddleware = (0, data_js_1.default)({
|
|
119
104
|
plan,
|
|
120
|
-
mountUrl,
|
|
121
105
|
events,
|
|
122
106
|
});
|
|
123
107
|
// Prepare form middleware and its constiuent parts
|
|
@@ -125,9 +109,7 @@ function configure(config = {}) {
|
|
|
125
109
|
const bodyParserMiddleware = (0, body_parser_js_1.default)();
|
|
126
110
|
const csrfMiddleware = (0, csrf_js_1.default)();
|
|
127
111
|
// Setup router to serve up bundled static assets
|
|
128
|
-
const staticRouter = (0, static_js_1.default)(
|
|
129
|
-
mountUrl,
|
|
130
|
-
});
|
|
112
|
+
const staticRouter = (0, static_js_1.default)();
|
|
131
113
|
// Setup ancillary router default stand-alone pages.
|
|
132
114
|
const ancillaryRouter = (0, ancillary_js_1.default)({
|
|
133
115
|
sessionTtl: session.ttl,
|
|
@@ -138,25 +120,85 @@ function configure(config = {}) {
|
|
|
138
120
|
pages,
|
|
139
121
|
plan,
|
|
140
122
|
csrfMiddleware,
|
|
141
|
-
mountUrl,
|
|
142
123
|
});
|
|
143
124
|
// Mount function
|
|
144
125
|
// This will mount all of these routes and middleware in the correct order on
|
|
145
126
|
// the given ExpressJS app.
|
|
146
127
|
// Once this is called, you will not be able to modify any of the routers as
|
|
147
128
|
// they will be "sealed".
|
|
148
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Mounting function.
|
|
131
|
+
*
|
|
132
|
+
* @type {Mounter} mount
|
|
133
|
+
*/
|
|
134
|
+
const mount = (app, { route = '/' } = {}) => {
|
|
149
135
|
nunjucksEnv.express(app);
|
|
150
136
|
app.set('view engine', 'njk');
|
|
137
|
+
// !!! DEPRECATION NOTICE !!!
|
|
138
|
+
// This provides a non-breaking pathway to replacing `mountUrl` with
|
|
139
|
+
// `req.baseUrl` in all internal route handlers/middleware for services
|
|
140
|
+
// that use a proxy path in their mount point.
|
|
141
|
+
//
|
|
142
|
+
// In some cases, the URL on which `app` instance is mounted might include a
|
|
143
|
+
// proxy path so that it can handle incoming requests that have had a path
|
|
144
|
+
// prepended to it by an intermediary, such as nginx. This would be common
|
|
145
|
+
// in a hosting environment that serves several separate applications.
|
|
146
|
+
//
|
|
147
|
+
// This bit of middleware removes that proxy path segment from the request
|
|
148
|
+
// so that all subsequent middleware behave as if it was never present.
|
|
149
|
+
//
|
|
150
|
+
// e.g. Where the proxy path is `my-proxy`, then a request to
|
|
151
|
+
// `/my-proxy/app` will be seen as `/app` in all subsequent middleware, and
|
|
152
|
+
// all URLs generated for the browser will use `/app`.
|
|
153
|
+
//
|
|
154
|
+
// Using `config.mountUrl` rather than `mountUrl` here to test whether the
|
|
155
|
+
// consumer explicitly set a `mountUrl`, in which case we're dealing with
|
|
156
|
+
// backwards-compatibility mode.
|
|
157
|
+
if (config.mountUrl) {
|
|
158
|
+
log.warn('[DEPRECATION WARNING] Using configuration attribute, mountUrl. This will be removed in an upcoming major version');
|
|
159
|
+
app.use((req, res, next) => {
|
|
160
|
+
req.baseUrl = mountUrl.replace(/\/$/, '');
|
|
161
|
+
next();
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
// Attach a handler to redirect requests for `/` to the first waypoint in
|
|
165
|
+
// the plan
|
|
166
|
+
if (plan) {
|
|
167
|
+
const re = (0, path_to_regexp_1.pathToRegexp)(`${route}`.replace(/\/+/g, '/'));
|
|
168
|
+
app.use(re, (req, res) => {
|
|
169
|
+
const reqUrl = new URL(req.url, 'https://placeholder.test/');
|
|
170
|
+
const reqPath = (0, utils_js_1.validateUrlPath)(`${req.baseUrl}${reqUrl.pathname}${plan.getWaypoints()[0]}`);
|
|
171
|
+
let reqParams = reqUrl.searchParams.toString();
|
|
172
|
+
reqParams = reqParams ? `?${reqParams}` : '';
|
|
173
|
+
res.redirect(302, `${reqPath}${reqParams}`);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
// Service static assets from the `app` rather than the `router`. The router
|
|
177
|
+
// may contain paramaterised path segments which would mean serving static
|
|
178
|
+
// assets over a dynamic URL each time, thus causing lots of cache misses on
|
|
179
|
+
// the browser.
|
|
180
|
+
const sealedStaticRouter = staticRouter.seal();
|
|
151
181
|
app.use(preMiddleware);
|
|
152
|
-
app.use(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
app
|
|
182
|
+
app.use(sealedStaticRouter);
|
|
183
|
+
const router = (0, express_1.Router)({
|
|
184
|
+
// Required so that any parameters in the URL are propagated to middleware
|
|
185
|
+
mergeParams: true,
|
|
186
|
+
});
|
|
187
|
+
router.use(preMiddleware);
|
|
188
|
+
// !!! DEPRECATE in v9 !!! For performance reasons, static assets will
|
|
189
|
+
// always be handled via the `app` middleware rather than `router`.
|
|
190
|
+
// Anywhere `mountUrl` is used in templates to service static assets must be
|
|
191
|
+
// changed to use `staticMountUrl`.
|
|
192
|
+
// TASK: remove this line below
|
|
193
|
+
router.use(sealedStaticRouter);
|
|
194
|
+
router.use(sessionMiddleware);
|
|
195
|
+
router.use(i18nMiddleware);
|
|
196
|
+
router.use(bodyParserMiddleware);
|
|
197
|
+
router.use(dataMiddleware);
|
|
198
|
+
router.use(ancillaryRouter.seal());
|
|
199
|
+
router.use(journeyRouter.seal());
|
|
200
|
+
router.use(postMiddleware);
|
|
201
|
+
app.use(route, router);
|
|
160
202
|
return app;
|
|
161
203
|
};
|
|
162
204
|
// Prepare configuration result
|
package/dist/lib/nunjucks.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {object} NunjucksOptions
|
|
3
|
-
* @property {string} [mountUrl=/] Mount URL (optional, default /)
|
|
4
3
|
* @property {string[]} [views=[]] Template file directories (optional, default [])
|
|
5
4
|
*/
|
|
6
5
|
/**
|
|
@@ -9,12 +8,8 @@
|
|
|
9
8
|
* @param {NunjucksOptions} options Nunjucks options
|
|
10
9
|
* @returns {Environment} Nunjucks Environment instance
|
|
11
10
|
*/
|
|
12
|
-
export default function nunjucksConfig({
|
|
11
|
+
export default function nunjucksConfig({ views, }: NunjucksOptions): Environment;
|
|
13
12
|
export type NunjucksOptions = {
|
|
14
|
-
/**
|
|
15
|
-
* )
|
|
16
|
-
*/
|
|
17
|
-
mountUrl?: string | undefined;
|
|
18
13
|
/**
|
|
19
14
|
* Template file directories (optional, default [])
|
|
20
15
|
*/
|
package/dist/lib/nunjucks.js
CHANGED
|
@@ -11,7 +11,6 @@ const CasaTemplateLoader_js_1 = __importDefault(require("./CasaTemplateLoader.js
|
|
|
11
11
|
const nunjucks_filters_js_1 = require("./nunjucks-filters.js");
|
|
12
12
|
/**
|
|
13
13
|
* @typedef {object} NunjucksOptions
|
|
14
|
-
* @property {string} [mountUrl=/] Mount URL (optional, default /)
|
|
15
14
|
* @property {string[]} [views=[]] Template file directories (optional, default [])
|
|
16
15
|
*/
|
|
17
16
|
/**
|
|
@@ -20,7 +19,7 @@ const nunjucks_filters_js_1 = require("./nunjucks-filters.js");
|
|
|
20
19
|
* @param {NunjucksOptions} options Nunjucks options
|
|
21
20
|
* @returns {Environment} Nunjucks Environment instance
|
|
22
21
|
*/
|
|
23
|
-
function nunjucksConfig({
|
|
22
|
+
function nunjucksConfig({ views = [], }) {
|
|
24
23
|
// Prepare a single Nunjucks environment for all responses to use. Note that
|
|
25
24
|
// we cannot prepare response-specific global functions/filters if we use a
|
|
26
25
|
// single environment, but the performance gains of doing so are significant.
|
|
@@ -38,7 +37,6 @@ function nunjucksConfig({ mountUrl = '/', views = [], }) {
|
|
|
38
37
|
env.modifyBlock = loader.modifyBlock.bind(loader);
|
|
39
38
|
// Globals
|
|
40
39
|
// These can't be modified once set. But they can be overridden by res.locals.
|
|
41
|
-
env.addGlobal('assetPath', `${mountUrl}govuk/assets`); // Required by govuk-frontend layout template
|
|
42
40
|
env.addGlobal('casaVersion', JSON.parse((0, fs_1.readFileSync)((0, path_1.resolve)(dirname_cjs_1.default, '../../package.json'))).version);
|
|
43
41
|
env.addGlobal('mergeObjects', nunjucks_filters_js_1.mergeObjects);
|
|
44
42
|
env.addGlobal('includes', nunjucks_filters_js_1.includes);
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import('
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* @typedef {import('./configuration-ingestor').PageHook} PageHook
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* @typedef {GlobalHook | PageHook} Hook
|
|
2
|
+
* @typedef {import('../casa').GlobalHook | import('../casa').PageHook} Hook
|
|
9
3
|
*/
|
|
10
4
|
/**
|
|
11
5
|
* Test is a value can be stringifed (numbers or strings)
|
|
@@ -40,6 +34,7 @@ export function isEmpty(val: any): boolean;
|
|
|
40
34
|
*/
|
|
41
35
|
export function resolveMiddlewareHooks(hookName: string, path: string, hooks?: Hook[]): Function[];
|
|
42
36
|
export function validateWaypoint(waypoint: any): void;
|
|
37
|
+
export function validateUrlPath(path: any): string;
|
|
43
38
|
export function validateView(view: any): void;
|
|
44
39
|
export function validateHookName(hookName: any): void;
|
|
45
40
|
export function validateHookPath(path: any): void;
|
|
@@ -51,6 +46,14 @@ export function validateHookPath(path: any): void;
|
|
|
51
46
|
* @throws {Error} if proposed key is an invalid keyword
|
|
52
47
|
*/
|
|
53
48
|
export function notProto(key: string): string;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Remove any path segments from the URL that are present in the `mountpath`,
|
|
51
|
+
* but not in the `baseUrl`. Those segments are considered to be part of an
|
|
52
|
+
* internal proxying arrangement, and should not be used by CASA.
|
|
53
|
+
*
|
|
54
|
+
* @param {import('express').Request} req Express request
|
|
55
|
+
* @throws {Error} When multiple mountpaths are present
|
|
56
|
+
* @returns {string} URL path with any proxy prefixes removed
|
|
57
|
+
*/
|
|
58
|
+
export function stripProxyFromUrlPath(req: import('express').Request): string;
|
|
59
|
+
export type Hook = import('../casa').GlobalHook | import('../casa').PageHook;
|
package/dist/lib/utils.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* @typedef {import('
|
|
3
|
+
* @typedef {import('../casa').GlobalHook | import('../casa').PageHook} Hook
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.notProto = exports.validateHookPath = exports.validateHookName = exports.validateView = exports.validateWaypoint = exports.resolveMiddlewareHooks = exports.isEmpty = exports.stringifyInput = exports.isStringable = void 0;
|
|
7
|
-
/**
|
|
8
|
-
* @typedef {import('./configuration-ingestor').PageHook} PageHook
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {GlobalHook | PageHook} Hook
|
|
12
|
-
*/
|
|
6
|
+
exports.stripProxyFromUrlPath = exports.notProto = exports.validateHookPath = exports.validateHookName = exports.validateView = exports.validateUrlPath = exports.validateWaypoint = exports.resolveMiddlewareHooks = exports.isEmpty = exports.stringifyInput = exports.isStringable = void 0;
|
|
13
7
|
/**
|
|
14
8
|
* Test is a value can be stringifed (numbers or strings)
|
|
15
9
|
*
|
|
@@ -81,6 +75,19 @@ function validateWaypoint(waypoint) {
|
|
|
81
75
|
}
|
|
82
76
|
}
|
|
83
77
|
exports.validateWaypoint = validateWaypoint;
|
|
78
|
+
function validateUrlPath(path) {
|
|
79
|
+
if (typeof path !== 'string') {
|
|
80
|
+
throw new TypeError('URL path must be a string');
|
|
81
|
+
}
|
|
82
|
+
if (path.match(/[^/a-z0-9_-]/)) {
|
|
83
|
+
throw new SyntaxError('URL path must contain only a-z, 0-9, -, _ and / characters');
|
|
84
|
+
}
|
|
85
|
+
if (path.match(/\/{2,}/)) {
|
|
86
|
+
throw new SyntaxError('URL path must not contain consecutive /');
|
|
87
|
+
}
|
|
88
|
+
return path;
|
|
89
|
+
}
|
|
90
|
+
exports.validateUrlPath = validateUrlPath;
|
|
84
91
|
function validateView(view) {
|
|
85
92
|
if (typeof view !== 'string') {
|
|
86
93
|
throw new TypeError('View must be a string');
|
|
@@ -125,3 +132,28 @@ function notProto(key) {
|
|
|
125
132
|
return key;
|
|
126
133
|
}
|
|
127
134
|
exports.notProto = notProto;
|
|
135
|
+
/**
|
|
136
|
+
* Remove any path segments from the URL that are present in the `mountpath`,
|
|
137
|
+
* but not in the `baseUrl`. Those segments are considered to be part of an
|
|
138
|
+
* internal proxying arrangement, and should not be used by CASA.
|
|
139
|
+
*
|
|
140
|
+
* @param {import('express').Request} req Express request
|
|
141
|
+
* @throws {Error} When multiple mountpaths are present
|
|
142
|
+
* @returns {string} URL path with any proxy prefixes removed
|
|
143
|
+
*/
|
|
144
|
+
function stripProxyFromUrlPath(req) {
|
|
145
|
+
if (typeof req.app.mountpath !== 'string') {
|
|
146
|
+
throw new Error('CASA does not currently support multiple mountpaths');
|
|
147
|
+
}
|
|
148
|
+
let stripped = '/';
|
|
149
|
+
const mountPathParts = req.app.mountpath.replace(/^\/+/, '').replace(/\/+$/, '').split('/');
|
|
150
|
+
const baseUrlParts = req.baseUrl.replace(/^\/+/, '').replace(/\/+$/, '').split('/');
|
|
151
|
+
for (let i = 0, l = mountPathParts.length; i < l; i++) {
|
|
152
|
+
/* eslint-disable-next-line security/detect-object-injection */
|
|
153
|
+
if (baseUrlParts.length && mountPathParts[i] === baseUrlParts[0]) {
|
|
154
|
+
stripped = `${stripped}${baseUrlParts.shift()}/`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return stripped;
|
|
158
|
+
}
|
|
159
|
+
exports.stripProxyFromUrlPath = stripProxyFromUrlPath;
|
package/dist/lib/waypoint-url.js
CHANGED
|
@@ -32,8 +32,14 @@ function waypointUrl({ waypoint = '', mountUrl = '/', journeyContext, edit = fal
|
|
|
32
32
|
else {
|
|
33
33
|
url.pathname = `${mountUrl}${waypoint}`;
|
|
34
34
|
}
|
|
35
|
-
// Attach context
|
|
36
|
-
|
|
35
|
+
// Attach context ID as query parameter for non-default contexts.
|
|
36
|
+
// To avoid messy URLs with duplicated content, this parameter will _not_ be
|
|
37
|
+
// added if the context ID already appears in the url path, i.e. to avoid
|
|
38
|
+
// `/path/1234-abcd/waypoint?contextid=1234-abcd` scenarios
|
|
39
|
+
if (journeyContext
|
|
40
|
+
&& !journeyContext.isDefault()
|
|
41
|
+
&& journeyContext.identity.id
|
|
42
|
+
&& !mountUrl.includes(journeyContext.identity.id)) {
|
|
37
43
|
url.searchParams.append('contextid', journeyContext.identity.id);
|
|
38
44
|
}
|
|
39
45
|
// Attach edit mode flag
|
|
@@ -6,7 +6,7 @@ const rProto = /__proto__/i;
|
|
|
6
6
|
const rPrototype = /prototype[='"[\]]/i;
|
|
7
7
|
const rConstructor = /constructor[='"[\]]/i;
|
|
8
8
|
function verifyBody(req, res, buf, encoding) {
|
|
9
|
-
const body = decodeURI(buf.toString(encoding));
|
|
9
|
+
const body = decodeURI(buf.toString(encoding)).replace(/[\s\u200B-\u200D\uFEFF]/g, '');
|
|
10
10
|
if (rProto.test(body)) {
|
|
11
11
|
throw new Error('Request body verification failed (__proto__)');
|
|
12
12
|
}
|