@dwp/govuk-casa 8.0.0-alpha2 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/dist/assets/css/casa-ie8.css +1 -1
- package/dist/assets/css/casa.css +1 -1
- package/dist/casa.d.ts +2 -1
- package/dist/casa.js +3 -1
- package/dist/lib/CasaTemplateLoader.d.ts +4 -2
- package/dist/lib/CasaTemplateLoader.js +26 -4
- package/dist/lib/JourneyContext.d.ts +38 -6
- package/dist/lib/JourneyContext.js +58 -17
- package/dist/lib/MutableRouter.js +6 -2
- package/dist/lib/Plan.d.ts +37 -4
- package/dist/lib/Plan.js +75 -11
- package/dist/lib/ValidationError.d.ts +6 -2
- package/dist/lib/ValidationError.js +7 -0
- package/dist/lib/ValidatorFactory.d.ts +72 -19
- package/dist/lib/ValidatorFactory.js +33 -20
- package/dist/lib/configuration-ingestor.d.ts +262 -0
- package/dist/lib/configuration-ingestor.js +464 -0
- package/dist/lib/configure.d.ts +26 -140
- package/dist/lib/configure.js +17 -45
- package/dist/lib/dirname.cjs +1 -1
- package/dist/lib/dirname.d.cts +2 -0
- package/dist/lib/end-session.d.ts +2 -1
- package/dist/lib/end-session.js +27 -7
- package/dist/lib/field.d.ts +39 -46
- package/dist/lib/field.js +75 -36
- package/dist/lib/index.d.ts +14 -0
- package/dist/lib/index.js +54 -0
- package/dist/lib/logger.d.ts +2 -1
- package/dist/lib/logger.js +3 -4
- package/dist/lib/nunjucks-filters.js +8 -0
- package/dist/lib/utils.d.ts +18 -2
- package/dist/lib/utils.js +56 -2
- package/dist/lib/validators/inArray.js +1 -1
- package/dist/lib/validators/index.js +0 -22
- package/dist/lib/validators/postalAddressObject.js +6 -2
- package/dist/lib/waypoint-url.d.ts +2 -1
- package/dist/lib/waypoint-url.js +3 -0
- package/dist/middleware/body-parser.d.ts +1 -0
- package/dist/middleware/body-parser.js +18 -9
- package/dist/middleware/data.d.ts +1 -2
- package/dist/middleware/data.js +9 -9
- package/dist/middleware/dirname.cjs +1 -1
- package/dist/middleware/dirname.d.cts +2 -0
- package/dist/middleware/gather-fields.d.ts +2 -1
- package/dist/middleware/gather-fields.js +6 -5
- package/dist/middleware/i18n.js +5 -1
- package/dist/middleware/post.js +6 -6
- package/dist/middleware/progress-journey.js +1 -1
- package/dist/middleware/sanitise-fields.js +9 -9
- package/dist/middleware/session.d.ts +2 -1
- package/dist/middleware/session.js +62 -55
- package/dist/middleware/skip-waypoint.js +2 -2
- package/dist/middleware/steer-journey.d.ts +2 -1
- package/dist/middleware/steer-journey.js +3 -0
- package/dist/middleware/validate-fields.js +7 -6
- package/dist/mjs/esm-wrapper.js +10 -0
- package/dist/routes/ancillary.d.ts +8 -1
- package/dist/routes/ancillary.js +7 -2
- package/dist/routes/dirname.cjs +1 -1
- package/dist/routes/dirname.d.cts +2 -0
- package/dist/routes/journey.js +14 -8
- package/dist/routes/static.js +4 -3
- package/package.json +42 -25
- package/views/casa/layouts/main.njk +2 -2
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateEvents = exports.validatePlugins = exports.validateGlobalHooks = exports.validatePlan = exports.validatePages = exports.validateFields = exports.validatePageHooks = exports.validateSessionCookieSameSite = exports.validateSessionCookiePath = exports.validateSessionStore = exports.validateSessionSecure = exports.validateSessionName = exports.validateSessionTtl = exports.validateSessionSecret = exports.validateViews = exports.validateSessionObject = exports.validateMountUrl = exports.validateI18nLocales = exports.validateI18nDirs = exports.validateI18nObject = void 0;
|
|
7
|
+
/* eslint-disable sonarjs/no-duplicate-string */
|
|
8
|
+
const field_js_1 = require("./field.js");
|
|
9
|
+
const Plan_js_1 = __importDefault(require("./Plan.js"));
|
|
10
|
+
const logger_js_1 = __importDefault(require("./logger.js"));
|
|
11
|
+
const utils_js_1 = require("./utils.js");
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {import('./index').ContextEvent} ContextEvent
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {object} SessionOptions
|
|
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
|
+
* Configure some middleware for use in creating a new CASA app.
|
|
49
|
+
*
|
|
50
|
+
* `mountUrl` is used to ensure the CSS content uses the correct reference to
|
|
51
|
+
* static assets in the `govuk-frontend` module.
|
|
52
|
+
*
|
|
53
|
+
* @typedef {object} ConfigurationOptions Configuration options
|
|
54
|
+
* @property {string} [mountUrl=/] URL path to root of CASA app
|
|
55
|
+
* @property {string[]} [views=[]] Template directories
|
|
56
|
+
* @property {SessionOptions} [session] Session configuration
|
|
57
|
+
* @property {Page[]} [pages=[]] Pages the represent waypoints
|
|
58
|
+
* @property {GlobalHook[]} [hooks=[]] Hooks to apply
|
|
59
|
+
* @property {object[]} [plugins=[]] Plugins
|
|
60
|
+
* @property {I18nOptions[]} [i18n] I18n configuration
|
|
61
|
+
* @property {Plan} plan CASA Plan
|
|
62
|
+
* @property {ContextEvent[]} [events=[]] Handlers for JourneyContext events
|
|
63
|
+
*/
|
|
64
|
+
const log = (0, logger_js_1.default)('lib:configuration-ingestor');
|
|
65
|
+
const echo = (a) => (a);
|
|
66
|
+
/**
|
|
67
|
+
* Validates and sanitises i18n obejct.
|
|
68
|
+
*
|
|
69
|
+
* @param {object} i18n Object to validate.
|
|
70
|
+
* @param {Function} cb Callback function that receives the validated value.
|
|
71
|
+
* @throws {TypeError} For invalid object.
|
|
72
|
+
* @returns {object} Sanitised i18n object.
|
|
73
|
+
*/
|
|
74
|
+
function validateI18nObject(i18n, cb = echo) {
|
|
75
|
+
if (Object.prototype.toString.call(i18n) !== '[object Object]') {
|
|
76
|
+
throw new TypeError('I18n must be an object');
|
|
77
|
+
}
|
|
78
|
+
return cb(i18n);
|
|
79
|
+
}
|
|
80
|
+
exports.validateI18nObject = validateI18nObject;
|
|
81
|
+
/**
|
|
82
|
+
* Validates and sanitises i18n directory.
|
|
83
|
+
*
|
|
84
|
+
* @param {Array} dirs Array of directories.
|
|
85
|
+
* @throws {SyntaxError} For invalid directories.
|
|
86
|
+
* @throws {TypeError} For invalid type.
|
|
87
|
+
* @returns {Array} Array of directories.
|
|
88
|
+
*/
|
|
89
|
+
function validateI18nDirs(dirs) {
|
|
90
|
+
if (typeof dirs === 'undefined') {
|
|
91
|
+
throw ReferenceError('I18n directories are missing (i18n.dirs)');
|
|
92
|
+
}
|
|
93
|
+
else if (!Array.isArray(dirs)) {
|
|
94
|
+
throw new TypeError('I18n directories must be an array (i18n.dirs)');
|
|
95
|
+
}
|
|
96
|
+
dirs.forEach((dir, i) => {
|
|
97
|
+
if (typeof dir !== 'string') {
|
|
98
|
+
throw new TypeError(`I18n directory must be a string, got ${typeof dir} (i18n.dirs[${i}])`);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
return dirs;
|
|
102
|
+
}
|
|
103
|
+
exports.validateI18nDirs = validateI18nDirs;
|
|
104
|
+
/**
|
|
105
|
+
* Validates and sanitises i18n locales.
|
|
106
|
+
*
|
|
107
|
+
* @param {Array} locales Array of locales.
|
|
108
|
+
* @throws {SyntaxError} For invalid locales.
|
|
109
|
+
* @throws {TypeError} For invalid type.
|
|
110
|
+
* @returns {Array} Array of locales.
|
|
111
|
+
*/
|
|
112
|
+
function validateI18nLocales(locales) {
|
|
113
|
+
if (typeof locales === 'undefined') {
|
|
114
|
+
throw ReferenceError('I18n locales are missing (i18n.locales)');
|
|
115
|
+
}
|
|
116
|
+
else if (!Array.isArray(locales)) {
|
|
117
|
+
throw new TypeError('I18n locales must be an array (i18n.locales)');
|
|
118
|
+
}
|
|
119
|
+
locales.forEach((locale, i) => {
|
|
120
|
+
if (typeof locale !== 'string') {
|
|
121
|
+
throw new TypeError(`I18n locale must be a string, got ${typeof locale} (i18n.locales[${i}])`);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
return locales;
|
|
125
|
+
}
|
|
126
|
+
exports.validateI18nLocales = validateI18nLocales;
|
|
127
|
+
/**
|
|
128
|
+
* Validates and sanitises mount url.
|
|
129
|
+
*
|
|
130
|
+
* @param {string} mountUrl URL from which Express app will be served.
|
|
131
|
+
* @param {string} name Name of the URL type (Mount URL, or Proxy Mount URL).
|
|
132
|
+
* @throws {SyntaxError} For invalid URL.
|
|
133
|
+
* @returns {string} Sanitised URL.
|
|
134
|
+
*/
|
|
135
|
+
function validateMountUrl(mountUrl, name = 'Mount URL') {
|
|
136
|
+
if (typeof mountUrl === 'undefined') {
|
|
137
|
+
return '/';
|
|
138
|
+
}
|
|
139
|
+
if (!mountUrl.match(/\/$/)) {
|
|
140
|
+
throw new SyntaxError(`${name} must include a trailing slash (/)`);
|
|
141
|
+
}
|
|
142
|
+
return mountUrl;
|
|
143
|
+
}
|
|
144
|
+
exports.validateMountUrl = validateMountUrl;
|
|
145
|
+
/**
|
|
146
|
+
* Validates and sanitises sessions object.
|
|
147
|
+
*
|
|
148
|
+
* @param {object} session Object to validate.
|
|
149
|
+
* @param {Function} cb Callback function that receives the validated value.
|
|
150
|
+
* @throws {TypeError} For invalid object.
|
|
151
|
+
* @returns {object} Sanitised sessions object.
|
|
152
|
+
*/
|
|
153
|
+
function validateSessionObject(session, cb = echo) {
|
|
154
|
+
if (session === undefined) {
|
|
155
|
+
return cb(session);
|
|
156
|
+
}
|
|
157
|
+
if (typeof session !== 'object') {
|
|
158
|
+
throw new TypeError('Session config has not been specified');
|
|
159
|
+
}
|
|
160
|
+
return cb(session);
|
|
161
|
+
}
|
|
162
|
+
exports.validateSessionObject = validateSessionObject;
|
|
163
|
+
/**
|
|
164
|
+
* Validates and sanitises view directory.
|
|
165
|
+
*
|
|
166
|
+
* @param {Array} dirs Array of directories.
|
|
167
|
+
* @throws {SyntaxError} For invalid directories.
|
|
168
|
+
* @throws {TypeError} For invalid type.
|
|
169
|
+
* @returns {Array} Array of directories.
|
|
170
|
+
*/
|
|
171
|
+
function validateViews(dirs) {
|
|
172
|
+
if (typeof dirs === 'undefined') {
|
|
173
|
+
throw ReferenceError('View directories are missing (views)');
|
|
174
|
+
}
|
|
175
|
+
else if (!Array.isArray(dirs)) {
|
|
176
|
+
throw new TypeError('View directories must be an array (views)');
|
|
177
|
+
}
|
|
178
|
+
dirs.forEach((dir, i) => {
|
|
179
|
+
if (typeof dir !== 'string') {
|
|
180
|
+
throw new TypeError(`View directory must be a string, got ${typeof dir} (views[${i}])`);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
return dirs;
|
|
184
|
+
}
|
|
185
|
+
exports.validateViews = validateViews;
|
|
186
|
+
/**
|
|
187
|
+
* Validates and sanitises sessions secret.
|
|
188
|
+
*
|
|
189
|
+
* @param {string} secret Session secret.
|
|
190
|
+
* @throws {ReferenceError} For missing value type.
|
|
191
|
+
* @throws {TypeError} For invalid value.
|
|
192
|
+
* @returns {string} Secret.
|
|
193
|
+
*/
|
|
194
|
+
function validateSessionSecret(secret) {
|
|
195
|
+
if (typeof secret === 'undefined') {
|
|
196
|
+
throw ReferenceError('Session secret is missing (session.secret)');
|
|
197
|
+
}
|
|
198
|
+
else if (typeof secret !== 'string') {
|
|
199
|
+
throw new TypeError('Session secret must be a string (session.secret)');
|
|
200
|
+
}
|
|
201
|
+
return secret;
|
|
202
|
+
}
|
|
203
|
+
exports.validateSessionSecret = validateSessionSecret;
|
|
204
|
+
/**
|
|
205
|
+
* Validates and sanitises sessions ttl.
|
|
206
|
+
*
|
|
207
|
+
* @param {number} ttl Session ttl (seconds).
|
|
208
|
+
* @throws {ReferenceError} For missing value type.
|
|
209
|
+
* @throws {TypeError} For invalid value.
|
|
210
|
+
* @returns {number} Ttl.
|
|
211
|
+
*/
|
|
212
|
+
function validateSessionTtl(ttl) {
|
|
213
|
+
if (typeof ttl === 'undefined') {
|
|
214
|
+
throw ReferenceError('Session ttl is missing (session.ttl)');
|
|
215
|
+
}
|
|
216
|
+
else if (typeof ttl !== 'number') {
|
|
217
|
+
throw new TypeError('Session ttl must be an integer (session.ttl)');
|
|
218
|
+
}
|
|
219
|
+
return ttl;
|
|
220
|
+
}
|
|
221
|
+
exports.validateSessionTtl = validateSessionTtl;
|
|
222
|
+
/**
|
|
223
|
+
* Validates and sanitises sessions name.
|
|
224
|
+
*
|
|
225
|
+
* @param {string} name Session name.
|
|
226
|
+
* @throws {ReferenceError} For missing value type.
|
|
227
|
+
* @throws {TypeError} For invalid value.
|
|
228
|
+
* @returns {string} Name.
|
|
229
|
+
*/
|
|
230
|
+
function validateSessionName(name) {
|
|
231
|
+
if (typeof name === 'undefined') {
|
|
232
|
+
throw ReferenceError('Session name is missing (session.name)');
|
|
233
|
+
}
|
|
234
|
+
else if (typeof name !== 'string') {
|
|
235
|
+
throw new TypeError('Session name must be a string (session.name)');
|
|
236
|
+
}
|
|
237
|
+
return name;
|
|
238
|
+
}
|
|
239
|
+
exports.validateSessionName = validateSessionName;
|
|
240
|
+
/**
|
|
241
|
+
* Validates and sanitises sessions secure flag.
|
|
242
|
+
*
|
|
243
|
+
* @param {boolean} secure Session secure flag.
|
|
244
|
+
* @throws {ReferenceError} For missing value type.
|
|
245
|
+
* @throws {TypeError} For invalid value.
|
|
246
|
+
* @returns {string} Name.
|
|
247
|
+
*/
|
|
248
|
+
function validateSessionSecure(secure) {
|
|
249
|
+
if (typeof secure === 'undefined') {
|
|
250
|
+
throw ReferenceError('Session secure flag is missing (session.secure)');
|
|
251
|
+
}
|
|
252
|
+
else if (typeof secure !== 'boolean') {
|
|
253
|
+
throw new TypeError('Session secure flag must be boolean (session.secure)');
|
|
254
|
+
}
|
|
255
|
+
return secure;
|
|
256
|
+
}
|
|
257
|
+
exports.validateSessionSecure = validateSessionSecure;
|
|
258
|
+
/**
|
|
259
|
+
* Validates and sanitises sessions store.
|
|
260
|
+
*
|
|
261
|
+
* @param {Function} store Session store.
|
|
262
|
+
* @returns {Function} Store.
|
|
263
|
+
*/
|
|
264
|
+
function validateSessionStore(store) {
|
|
265
|
+
if (typeof store === 'undefined') {
|
|
266
|
+
log.warn('Using MemoryStore session storage, which is not suitable for production');
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
return store;
|
|
270
|
+
}
|
|
271
|
+
exports.validateSessionStore = validateSessionStore;
|
|
272
|
+
/**
|
|
273
|
+
* Validates and sanitises sessions cookie url path.
|
|
274
|
+
*
|
|
275
|
+
* @param {string} cookiePath Session cookie url path.
|
|
276
|
+
* @param {string} defaultPath Default path if none specified.
|
|
277
|
+
* @returns {string} Cookie path.
|
|
278
|
+
*/
|
|
279
|
+
function validateSessionCookiePath(cookiePath, defaultPath = '/') {
|
|
280
|
+
if (typeof cookiePath === 'undefined') {
|
|
281
|
+
return defaultPath;
|
|
282
|
+
}
|
|
283
|
+
return cookiePath;
|
|
284
|
+
}
|
|
285
|
+
exports.validateSessionCookiePath = validateSessionCookiePath;
|
|
286
|
+
/**
|
|
287
|
+
* Validates and sanitises sessions cookie "sameSite" flag. One of:
|
|
288
|
+
* true (Strict)
|
|
289
|
+
* false (will not set the flag at all)
|
|
290
|
+
* Strict
|
|
291
|
+
* Lax
|
|
292
|
+
* None
|
|
293
|
+
*
|
|
294
|
+
* @param {any} cookieSameSite Session cookie "sameSite" flag
|
|
295
|
+
* @param {any} defaultFlag Default path if none specified
|
|
296
|
+
* @returns {boolean} cookie path
|
|
297
|
+
* @throws {TypeError} When invalid arguments are provided
|
|
298
|
+
*/
|
|
299
|
+
function validateSessionCookieSameSite(cookieSameSite, defaultFlag) {
|
|
300
|
+
const validValues = [true, false, 'Strict', 'Lax', 'None'];
|
|
301
|
+
if (defaultFlag === undefined) {
|
|
302
|
+
throw new TypeError('validateSessionCookieSameSite() requires an explicit default flag');
|
|
303
|
+
}
|
|
304
|
+
else if (!validValues.includes(defaultFlag)) {
|
|
305
|
+
throw new TypeError('validateSessionCookieSameSite() default flag must be set to one of true, false, Strict, Lax or None (session.cookieSameSite)');
|
|
306
|
+
}
|
|
307
|
+
const value = cookieSameSite !== undefined ? cookieSameSite : defaultFlag;
|
|
308
|
+
if (!validValues.includes(value)) {
|
|
309
|
+
throw new TypeError('SameSite flag must be set to one of true, false, Strict, Lax or None (session.cookieSameSite)');
|
|
310
|
+
}
|
|
311
|
+
return value;
|
|
312
|
+
}
|
|
313
|
+
exports.validateSessionCookieSameSite = validateSessionCookieSameSite;
|
|
314
|
+
const validatePageHook = (hook, index) => {
|
|
315
|
+
try {
|
|
316
|
+
(0, utils_js_1.validateHookName)(hook.hook);
|
|
317
|
+
if (typeof hook.middleware !== 'function') {
|
|
318
|
+
throw new TypeError('Hook middleware must be a function');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
err.message = `Page hook at index ${index} is invalid: ${err.message}`;
|
|
323
|
+
throw err;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
function validatePageHooks(hooks) {
|
|
327
|
+
if (!Array.isArray(hooks)) {
|
|
328
|
+
throw new TypeError('Hooks must be an array');
|
|
329
|
+
}
|
|
330
|
+
hooks.forEach((hook, index) => validatePageHook(hook, index));
|
|
331
|
+
return hooks;
|
|
332
|
+
}
|
|
333
|
+
exports.validatePageHooks = validatePageHooks;
|
|
334
|
+
const validateField = (field, index) => {
|
|
335
|
+
try {
|
|
336
|
+
if (!(field instanceof field_js_1.PageField)) {
|
|
337
|
+
throw new TypeError('Page field must be an instance of PageField (created via the "field()" function)');
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
err.message = `Page field at index ${index} is invalid: ${err.message}`;
|
|
342
|
+
throw err;
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
function validateFields(fields) {
|
|
346
|
+
if (!Array.isArray(fields)) {
|
|
347
|
+
throw new TypeError('Page fields must be an array (page[].fields)');
|
|
348
|
+
}
|
|
349
|
+
fields.forEach((hook, index) => validateField(hook, index));
|
|
350
|
+
return fields;
|
|
351
|
+
}
|
|
352
|
+
exports.validateFields = validateFields;
|
|
353
|
+
const validatePage = (page, index) => {
|
|
354
|
+
try {
|
|
355
|
+
(0, utils_js_1.validateWaypoint)(page.waypoint);
|
|
356
|
+
(0, utils_js_1.validateView)(page.view);
|
|
357
|
+
if (page.fields !== undefined) {
|
|
358
|
+
validateFields(page.fields);
|
|
359
|
+
}
|
|
360
|
+
if (page.hooks !== undefined) {
|
|
361
|
+
validatePageHooks(page.hooks);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
err.message = `Page at index ${index} is invalid: ${err.message}`;
|
|
366
|
+
throw err;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
function validatePages(pages) {
|
|
370
|
+
if (!Array.isArray(pages)) {
|
|
371
|
+
throw new TypeError('Pages must be an array (pages)');
|
|
372
|
+
}
|
|
373
|
+
pages.forEach((page, index) => validatePage(page, index));
|
|
374
|
+
return pages;
|
|
375
|
+
}
|
|
376
|
+
exports.validatePages = validatePages;
|
|
377
|
+
function validatePlan(plan) {
|
|
378
|
+
if (plan === undefined) {
|
|
379
|
+
return plan;
|
|
380
|
+
}
|
|
381
|
+
if (!(plan instanceof Plan_js_1.default)) {
|
|
382
|
+
throw new TypeError('Plan must be an instance the Plan class (plan)');
|
|
383
|
+
}
|
|
384
|
+
return plan;
|
|
385
|
+
}
|
|
386
|
+
exports.validatePlan = validatePlan;
|
|
387
|
+
const validateGlobalHook = (hook, index) => {
|
|
388
|
+
try {
|
|
389
|
+
(0, utils_js_1.validateHookName)(hook.hook);
|
|
390
|
+
if (typeof hook.middleware !== 'function') {
|
|
391
|
+
throw new TypeError('Hook middleware must be a function');
|
|
392
|
+
}
|
|
393
|
+
if (hook.path !== undefined) {
|
|
394
|
+
(0, utils_js_1.validateHookPath)(hook.path);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch (err) {
|
|
398
|
+
err.message = `Global hook at index ${index} is invalid: ${err.message}`;
|
|
399
|
+
throw err;
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
function validateGlobalHooks(hooks) {
|
|
403
|
+
if (hooks === undefined) {
|
|
404
|
+
return [];
|
|
405
|
+
}
|
|
406
|
+
if (!Array.isArray(hooks)) {
|
|
407
|
+
throw new TypeError('Hooks must be an array');
|
|
408
|
+
}
|
|
409
|
+
hooks.forEach((hook, index) => validateGlobalHook(hook, index));
|
|
410
|
+
return hooks;
|
|
411
|
+
}
|
|
412
|
+
exports.validateGlobalHooks = validateGlobalHooks;
|
|
413
|
+
function validatePlugins(plugins) {
|
|
414
|
+
return plugins;
|
|
415
|
+
}
|
|
416
|
+
exports.validatePlugins = validatePlugins;
|
|
417
|
+
function validateEvents(events) {
|
|
418
|
+
return events;
|
|
419
|
+
}
|
|
420
|
+
exports.validateEvents = validateEvents;
|
|
421
|
+
/**
|
|
422
|
+
* Ingest, validate, sanitise and manipulate configuration parameters.
|
|
423
|
+
*
|
|
424
|
+
* @param {ConfigurationOptions} config Config to ingest.
|
|
425
|
+
* @throws {Error|SyntaxError|TypeError} For invalid config values.
|
|
426
|
+
* @returns {object} Immutable config object.
|
|
427
|
+
*/
|
|
428
|
+
function ingest(config = {}) {
|
|
429
|
+
const parsed = {
|
|
430
|
+
// I18n configuration
|
|
431
|
+
i18n: validateI18nObject(config.i18n, (i18n) => ({
|
|
432
|
+
dirs: validateI18nDirs(i18n.dirs),
|
|
433
|
+
locales: validateI18nLocales(i18n.locales),
|
|
434
|
+
})),
|
|
435
|
+
// Public URL from which the app will be served
|
|
436
|
+
mountUrl: validateMountUrl(config.mountUrl),
|
|
437
|
+
// Session
|
|
438
|
+
session: validateSessionObject(config.session, (session) => ({
|
|
439
|
+
name: validateSessionName(session.name),
|
|
440
|
+
secret: validateSessionSecret(session.secret),
|
|
441
|
+
secure: validateSessionSecure(session.secure),
|
|
442
|
+
ttl: validateSessionTtl(session.ttl),
|
|
443
|
+
store: validateSessionStore(session.store),
|
|
444
|
+
cookiePath: validateSessionCookiePath(session.cookiePath, '/'),
|
|
445
|
+
cookieSameSite: validateSessionCookieSameSite(session.cookieSameSite, 'Strict'),
|
|
446
|
+
})),
|
|
447
|
+
// Views configuration
|
|
448
|
+
views: validateViews(config.views),
|
|
449
|
+
// Pages
|
|
450
|
+
pages: validatePages(config.pages),
|
|
451
|
+
// Plan
|
|
452
|
+
plan: validatePlan(config.plan),
|
|
453
|
+
// Hooks
|
|
454
|
+
hooks: validateGlobalHooks(config.hooks),
|
|
455
|
+
// Plugins
|
|
456
|
+
plugins: validatePlugins(config.plugins),
|
|
457
|
+
// Events
|
|
458
|
+
events: validateEvents(config.events),
|
|
459
|
+
};
|
|
460
|
+
// Freeze to modifications
|
|
461
|
+
Object.freeze(parsed);
|
|
462
|
+
return parsed;
|
|
463
|
+
}
|
|
464
|
+
exports.default = ingest;
|
package/dist/lib/configure.d.ts
CHANGED
|
@@ -1,30 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @typedef {object} SessionOptions
|
|
4
|
-
* @property {string} [name=casasession] Session name
|
|
5
|
-
* @property {string} [secret=secret] Encryption secret
|
|
6
|
-
* @property {number} [ttl=3600] Session ttl (seconds)
|
|
7
|
-
* @property {boolean} [secure=false] Whether to use secure session cookies
|
|
8
|
-
* @property {boolean|string} [cookieSameSite=true] SameSite (true = Strict)
|
|
9
|
-
* @property {object} [store] Session store (default MemoryStore)
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* @typedef {object} GlobalHook Hook configuration
|
|
13
|
-
* @property {string} hook Hook name in format `<router>.<hook>`
|
|
14
|
-
* @property {Function} middleware Middleware function to insert at the hook point
|
|
15
|
-
* @property {string|RegExp} [path=undefined] Only run if route path matches this string/regexp
|
|
2
|
+
* @typedef {import('express').RequestHandler} ExpressRequestHandler
|
|
16
3
|
*/
|
|
17
4
|
/**
|
|
18
|
-
* @typedef {
|
|
19
|
-
* @property {string} hook Hook name (without a scope prefix)
|
|
20
|
-
* @property {Function} middleware Middleware function to insert at the hook point
|
|
5
|
+
* @typedef {import('./index').MutableRouter} MutableRouter
|
|
21
6
|
*/
|
|
22
7
|
/**
|
|
23
|
-
* @typedef {
|
|
24
|
-
* @property {string} waypoint The waypoint with which this page is associated
|
|
25
|
-
* @property {string} view Template path
|
|
26
|
-
* @property {PageHook[]} [hooks=[]] Page-specific hooks (optional, default [])
|
|
27
|
-
* @property {PageField[]} [fields=[]] Fields to be managed on this page (optional, default [])
|
|
8
|
+
* @typedef {import('./configuration-ingestor').ConfigurationOptions} ConfigurationOptions
|
|
28
9
|
*/
|
|
29
10
|
/**
|
|
30
11
|
* @typedef {object} ConfigureResult Result of a call to configure() function
|
|
@@ -32,13 +13,13 @@
|
|
|
32
13
|
* @property {MutableRouter} staticRouter Router handling all static assets
|
|
33
14
|
* @property {MutableRouter} ancillaryRouter Router handling ancillary routes
|
|
34
15
|
* @property {MutableRouter} journeyRouter Router handling all waypoint requests
|
|
35
|
-
* @property {
|
|
36
|
-
* @property {
|
|
37
|
-
* @property {
|
|
38
|
-
* @property {
|
|
39
|
-
* @property {
|
|
40
|
-
* @property {
|
|
41
|
-
* @property {
|
|
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
|
|
42
23
|
* @property {Function} mount Function used to mount all CASA artifacts onto an ExpressJS app
|
|
43
24
|
*/
|
|
44
25
|
/**
|
|
@@ -47,108 +28,13 @@
|
|
|
47
28
|
* `mountUrl` is used to ensure the CSS content uses the correct reference to
|
|
48
29
|
* static assets in the `govuk-frontend` module.
|
|
49
30
|
*
|
|
50
|
-
* @param {
|
|
51
|
-
* @param {string} [config.mountUrl=/] URL path to root of CASA app
|
|
52
|
-
* @param {string} [config.serviceName=common.serviceName] Service name (i18n key)
|
|
53
|
-
* @param {string[]} [config.views=[]] Template directories
|
|
54
|
-
* @param {SessionOptions} [config.session] Session configuration
|
|
55
|
-
* @param {Page[]} [config.pages=[]] Pages the represent waypoints
|
|
56
|
-
* @param {GlobalHook[]} [config.hooks=[]] Hooks to apply
|
|
57
|
-
* @param {object[]} [config.plugins=[]] Plugins
|
|
58
|
-
* @param {I18nOptions[]} [config.i18n] I18n configuration
|
|
59
|
-
* @param {Plan} config.plan CASA Plan
|
|
60
|
-
* @param {ContextEvent[]} [config.events=[]] Handlers for JourneyContext events
|
|
31
|
+
* @param {ConfigurationOptions} config Configuration options
|
|
61
32
|
* @returns {ConfigureResult} Result
|
|
62
33
|
*/
|
|
63
|
-
export default function configure(config?:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
session?: SessionOptions | undefined;
|
|
68
|
-
pages?: Page[] | undefined;
|
|
69
|
-
hooks?: GlobalHook[] | undefined;
|
|
70
|
-
plugins?: object[] | undefined;
|
|
71
|
-
i18n?: any[] | undefined;
|
|
72
|
-
plan: any;
|
|
73
|
-
events?: any[] | undefined;
|
|
74
|
-
}): ConfigureResult;
|
|
75
|
-
export type SessionOptions = {
|
|
76
|
-
/**
|
|
77
|
-
* Session name
|
|
78
|
-
*/
|
|
79
|
-
name?: string | undefined;
|
|
80
|
-
/**
|
|
81
|
-
* Encryption secret
|
|
82
|
-
*/
|
|
83
|
-
secret?: string | undefined;
|
|
84
|
-
/**
|
|
85
|
-
* Session ttl (seconds)
|
|
86
|
-
*/
|
|
87
|
-
ttl?: number | undefined;
|
|
88
|
-
/**
|
|
89
|
-
* Whether to use secure session cookies
|
|
90
|
-
*/
|
|
91
|
-
secure?: boolean | undefined;
|
|
92
|
-
/**
|
|
93
|
-
* SameSite (true = Strict)
|
|
94
|
-
*/
|
|
95
|
-
cookieSameSite?: string | boolean | undefined;
|
|
96
|
-
/**
|
|
97
|
-
* Session store (default MemoryStore)
|
|
98
|
-
*/
|
|
99
|
-
store?: object | undefined;
|
|
100
|
-
};
|
|
101
|
-
/**
|
|
102
|
-
* Hook configuration
|
|
103
|
-
*/
|
|
104
|
-
export type GlobalHook = {
|
|
105
|
-
/**
|
|
106
|
-
* Hook name in format `<router>.<hook>`
|
|
107
|
-
*/
|
|
108
|
-
hook: string;
|
|
109
|
-
/**
|
|
110
|
-
* Middleware function to insert at the hook point
|
|
111
|
-
*/
|
|
112
|
-
middleware: Function;
|
|
113
|
-
/**
|
|
114
|
-
* Only run if route path matches this string/regexp
|
|
115
|
-
*/
|
|
116
|
-
path?: string | RegExp | undefined;
|
|
117
|
-
};
|
|
118
|
-
/**
|
|
119
|
-
* (extends GlobalHook)
|
|
120
|
-
*/
|
|
121
|
-
export type PageHook = {
|
|
122
|
-
/**
|
|
123
|
-
* Hook name (without a scope prefix)
|
|
124
|
-
*/
|
|
125
|
-
hook: string;
|
|
126
|
-
/**
|
|
127
|
-
* Middleware function to insert at the hook point
|
|
128
|
-
*/
|
|
129
|
-
middleware: Function;
|
|
130
|
-
};
|
|
131
|
-
/**
|
|
132
|
-
* Page configuration. A Page is the interactive representation of a waypoint
|
|
133
|
-
*/
|
|
134
|
-
export type Page = {
|
|
135
|
-
/**
|
|
136
|
-
* The waypoint with which this page is associated
|
|
137
|
-
*/
|
|
138
|
-
waypoint: string;
|
|
139
|
-
/**
|
|
140
|
-
* Template path
|
|
141
|
-
*/
|
|
142
|
-
view: string;
|
|
143
|
-
/**
|
|
144
|
-
* Page-specific hooks (optional, default [])
|
|
145
|
-
*/
|
|
146
|
-
hooks?: PageHook[] | undefined;
|
|
147
|
-
/**
|
|
148
|
-
* Fields to be managed on this page (optional, default [])
|
|
149
|
-
*/
|
|
150
|
-
fields?: any[] | undefined;
|
|
151
|
-
};
|
|
34
|
+
export default function configure(config?: ConfigurationOptions): ConfigureResult;
|
|
35
|
+
export type ExpressRequestHandler = import('express').RequestHandler;
|
|
36
|
+
export type MutableRouter = import('./index').MutableRouter;
|
|
37
|
+
export type ConfigurationOptions = import('./configuration-ingestor').ConfigurationOptions;
|
|
152
38
|
/**
|
|
153
39
|
* Result of a call to configure() function
|
|
154
40
|
*/
|
|
@@ -156,47 +42,47 @@ export type ConfigureResult = {
|
|
|
156
42
|
/**
|
|
157
43
|
* Nunjucks environment
|
|
158
44
|
*/
|
|
159
|
-
nunjucksEnv:
|
|
45
|
+
nunjucksEnv: nunjucks.Environment;
|
|
160
46
|
/**
|
|
161
47
|
* Router handling all static assets
|
|
162
48
|
*/
|
|
163
|
-
staticRouter:
|
|
49
|
+
staticRouter: MutableRouter;
|
|
164
50
|
/**
|
|
165
51
|
* Router handling ancillary routes
|
|
166
52
|
*/
|
|
167
|
-
ancillaryRouter:
|
|
53
|
+
ancillaryRouter: MutableRouter;
|
|
168
54
|
/**
|
|
169
55
|
* Router handling all waypoint requests
|
|
170
56
|
*/
|
|
171
|
-
journeyRouter:
|
|
57
|
+
journeyRouter: MutableRouter;
|
|
172
58
|
/**
|
|
173
59
|
* Middleware mounted before anything else
|
|
174
60
|
*/
|
|
175
|
-
preMiddleware:
|
|
61
|
+
preMiddleware: ExpressRequestHandler[];
|
|
176
62
|
/**
|
|
177
63
|
* Middleware mounted after everything else
|
|
178
64
|
*/
|
|
179
|
-
postMiddleware:
|
|
65
|
+
postMiddleware: ExpressRequestHandler[];
|
|
180
66
|
/**
|
|
181
67
|
* CSRF get/set middleware (useful for forms)
|
|
182
68
|
*/
|
|
183
|
-
csrfMiddleware:
|
|
69
|
+
csrfMiddleware: ExpressRequestHandler[];
|
|
184
70
|
/**
|
|
185
71
|
* Session middleware
|
|
186
72
|
*/
|
|
187
|
-
sessionMiddleware:
|
|
73
|
+
sessionMiddleware: ExpressRequestHandler;
|
|
188
74
|
/**
|
|
189
75
|
* Cookie-parsing middleware
|
|
190
76
|
*/
|
|
191
|
-
cookieParserMiddleware:
|
|
77
|
+
cookieParserMiddleware: ExpressRequestHandler[];
|
|
192
78
|
/**
|
|
193
79
|
* I18n preparation middleware
|
|
194
80
|
*/
|
|
195
|
-
i18nMiddleware:
|
|
81
|
+
i18nMiddleware: ExpressRequestHandler[];
|
|
196
82
|
/**
|
|
197
83
|
* Body parsing middleware
|
|
198
84
|
*/
|
|
199
|
-
bodyParserMiddleware:
|
|
85
|
+
bodyParserMiddleware: ExpressRequestHandler;
|
|
200
86
|
/**
|
|
201
87
|
* Function used to mount all CASA artifacts onto an ExpressJS app
|
|
202
88
|
*/
|