@dwp/govuk-casa 8.1.0 → 8.2.2
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 +25 -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 +214 -0
- package/dist/casa.js +103 -0
- package/dist/lib/JourneyContext.d.ts +15 -27
- package/dist/lib/JourneyContext.js +25 -15
- package/dist/lib/configuration-ingestor.d.ts +16 -144
- package/dist/lib/configuration-ingestor.js +25 -95
- package/dist/lib/configure.d.ts +6 -77
- package/dist/lib/configure.js +58 -39
- 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/strip-proxy-path.d.ts +4 -0
- package/dist/middleware/strip-proxy-path.js +52 -0
- 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
package/dist/lib/configure.js
CHANGED
|
@@ -3,16 +3,20 @@ 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"));
|
|
14
17
|
const ancillary_js_1 = __importDefault(require("../routes/ancillary.js"));
|
|
15
18
|
const journey_js_1 = __importDefault(require("../routes/journey.js"));
|
|
19
|
+
const strip_proxy_path_js_1 = __importDefault(require("../middleware/strip-proxy-path.js"));
|
|
16
20
|
const pre_js_1 = __importDefault(require("../middleware/pre.js"));
|
|
17
21
|
const post_js_1 = __importDefault(require("../middleware/post.js"));
|
|
18
22
|
const session_js_1 = __importDefault(require("../middleware/session.js"));
|
|
@@ -21,35 +25,17 @@ const data_js_1 = __importDefault(require("../middleware/data.js"));
|
|
|
21
25
|
const body_parser_js_1 = __importDefault(require("../middleware/body-parser.js"));
|
|
22
26
|
const csrf_js_1 = __importDefault(require("../middleware/csrf.js"));
|
|
23
27
|
/**
|
|
24
|
-
* @typedef {import('
|
|
28
|
+
* @typedef {import('../casa').ConfigurationOptions} ConfigurationOptions
|
|
25
29
|
*/
|
|
26
30
|
/**
|
|
27
|
-
* @typedef {import('
|
|
31
|
+
* @typedef {import('../casa').ConfigurationOptions} ConfigureResult
|
|
28
32
|
*/
|
|
29
33
|
/**
|
|
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
|
|
34
|
+
* @typedef {import('../casa').Mounter} Mounter
|
|
46
35
|
*/
|
|
47
36
|
/**
|
|
48
37
|
* Configure some middleware for use in creating a new CASA app.
|
|
49
38
|
*
|
|
50
|
-
* `mountUrl` is used to ensure the CSS content uses the correct reference to
|
|
51
|
-
* static assets in the `govuk-frontend` module.
|
|
52
|
-
*
|
|
53
39
|
* @param {ConfigurationOptions} config Configuration options
|
|
54
40
|
* @returns {ConfigureResult} Result
|
|
55
41
|
*/
|
|
@@ -61,7 +47,7 @@ function configure(config = {}) {
|
|
|
61
47
|
plugin.configure(config);
|
|
62
48
|
});
|
|
63
49
|
// Extract config
|
|
64
|
-
const { mountUrl
|
|
50
|
+
const { mountUrl, views = [], session = {
|
|
65
51
|
secret: 'secret',
|
|
66
52
|
name: 'casasession',
|
|
67
53
|
secure: false,
|
|
@@ -82,7 +68,6 @@ function configure(config = {}) {
|
|
|
82
68
|
// Prepare a Nunjucks environment for rendering all templates.
|
|
83
69
|
// Resolve priority: userland templates > CASA templates > GOVUK templates > Plugin templates
|
|
84
70
|
const nunjucksEnv = (0, nunjucks_js_1.default)({
|
|
85
|
-
mountUrl,
|
|
86
71
|
views: [
|
|
87
72
|
...views,
|
|
88
73
|
(0, path_1.resolve)(dirname_cjs_1.default, '../../views'),
|
|
@@ -93,7 +78,7 @@ function configure(config = {}) {
|
|
|
93
78
|
// These _must_ be added to the ExpressJS application at the start and end
|
|
94
79
|
// of all other middleware respectively.
|
|
95
80
|
const preMiddleware = (0, pre_js_1.default)({ helmetConfigurator });
|
|
96
|
-
const postMiddleware = (0, post_js_1.default)(
|
|
81
|
+
const postMiddleware = (0, post_js_1.default)();
|
|
97
82
|
// Prepare common middleware mounted prior to the ancillaryRouter
|
|
98
83
|
const cookieParserMiddleware = (0, cookie_parser_1.default)(session.secret);
|
|
99
84
|
const sessionMiddleware = (0, session_js_1.default)({
|
|
@@ -104,7 +89,6 @@ function configure(config = {}) {
|
|
|
104
89
|
ttl: session.ttl,
|
|
105
90
|
cookieSameSite: session.cookieSameSite,
|
|
106
91
|
cookiePath: session.cookiePath,
|
|
107
|
-
mountUrl,
|
|
108
92
|
store: (_b = session.store) !== null && _b !== void 0 ? _b : new express_session_1.MemoryStore(),
|
|
109
93
|
});
|
|
110
94
|
const i18nMiddleware = (0, i18n_js_1.default)({
|
|
@@ -117,7 +101,6 @@ function configure(config = {}) {
|
|
|
117
101
|
});
|
|
118
102
|
const dataMiddleware = (0, data_js_1.default)({
|
|
119
103
|
plan,
|
|
120
|
-
mountUrl,
|
|
121
104
|
events,
|
|
122
105
|
});
|
|
123
106
|
// Prepare form middleware and its constiuent parts
|
|
@@ -125,9 +108,7 @@ function configure(config = {}) {
|
|
|
125
108
|
const bodyParserMiddleware = (0, body_parser_js_1.default)();
|
|
126
109
|
const csrfMiddleware = (0, csrf_js_1.default)();
|
|
127
110
|
// Setup router to serve up bundled static assets
|
|
128
|
-
const staticRouter = (0, static_js_1.default)(
|
|
129
|
-
mountUrl,
|
|
130
|
-
});
|
|
111
|
+
const staticRouter = (0, static_js_1.default)();
|
|
131
112
|
// Setup ancillary router default stand-alone pages.
|
|
132
113
|
const ancillaryRouter = (0, ancillary_js_1.default)({
|
|
133
114
|
sessionTtl: session.ttl,
|
|
@@ -138,25 +119,63 @@ function configure(config = {}) {
|
|
|
138
119
|
pages,
|
|
139
120
|
plan,
|
|
140
121
|
csrfMiddleware,
|
|
141
|
-
mountUrl,
|
|
142
122
|
});
|
|
143
123
|
// Mount function
|
|
144
124
|
// This will mount all of these routes and middleware in the correct order on
|
|
145
125
|
// the given ExpressJS app.
|
|
146
126
|
// Once this is called, you will not be able to modify any of the routers as
|
|
147
127
|
// they will be "sealed".
|
|
148
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Mounting function.
|
|
130
|
+
*
|
|
131
|
+
* @type {Mounter} mount
|
|
132
|
+
*/
|
|
133
|
+
const mount = (app, { route = '/' } = {}) => {
|
|
149
134
|
nunjucksEnv.express(app);
|
|
150
135
|
app.set('view engine', 'njk');
|
|
136
|
+
// If a `mountUrl` has been defined, then we're potentially in "proxy mode",
|
|
137
|
+
// in which we strip the proxy path prefix from the incoming request URLs.
|
|
138
|
+
if (mountUrl) {
|
|
139
|
+
app.use((0, strip_proxy_path_js_1.default)({ mountUrl }));
|
|
140
|
+
}
|
|
141
|
+
// Attach a handler to redirect requests for `/` to the first waypoint in
|
|
142
|
+
// the plan
|
|
143
|
+
if (plan) {
|
|
144
|
+
const re = (0, path_to_regexp_1.pathToRegexp)(`${route}`.replace(/\/+/g, '/'));
|
|
145
|
+
app.use(re, (req, res) => {
|
|
146
|
+
const reqUrl = new URL(req.url, 'https://placeholder.test/');
|
|
147
|
+
const reqPath = (0, utils_js_1.validateUrlPath)(`${req.baseUrl}${reqUrl.pathname}${plan.getWaypoints()[0]}`);
|
|
148
|
+
let reqParams = reqUrl.searchParams.toString();
|
|
149
|
+
reqParams = reqParams ? `?${reqParams}` : '';
|
|
150
|
+
res.redirect(302, `${reqPath}${reqParams}`);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// Service static assets from the `app` rather than the `router`. The router
|
|
154
|
+
// may contain paramaterised path segments which would mean serving static
|
|
155
|
+
// assets over a dynamic URL each time, thus causing lots of cache misses on
|
|
156
|
+
// the browser.
|
|
157
|
+
const sealedStaticRouter = staticRouter.seal();
|
|
151
158
|
app.use(preMiddleware);
|
|
152
|
-
app.use(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
app
|
|
159
|
+
app.use(sealedStaticRouter);
|
|
160
|
+
const router = (0, express_1.Router)({
|
|
161
|
+
// Required so that any parameters in the URL are propagated to middleware
|
|
162
|
+
mergeParams: true,
|
|
163
|
+
});
|
|
164
|
+
router.use(preMiddleware);
|
|
165
|
+
// !!! DEPRECATE in v9 !!! For performance reasons, static assets will
|
|
166
|
+
// always be handled via the `app` middleware rather than `router`.
|
|
167
|
+
// Anywhere `mountUrl` is used in templates to service static assets must be
|
|
168
|
+
// changed to use `staticMountUrl`.
|
|
169
|
+
// TASK: remove this line below
|
|
170
|
+
router.use(sealedStaticRouter);
|
|
171
|
+
router.use(sessionMiddleware);
|
|
172
|
+
router.use(i18nMiddleware);
|
|
173
|
+
router.use(bodyParserMiddleware);
|
|
174
|
+
router.use(dataMiddleware);
|
|
175
|
+
router.use(ancillaryRouter.seal());
|
|
176
|
+
router.use(journeyRouter.seal());
|
|
177
|
+
router.use(postMiddleware);
|
|
178
|
+
app.use(route, router);
|
|
160
179
|
return app;
|
|
161
180
|
};
|
|
162
181
|
// 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
|
}
|
package/dist/middleware/data.js
CHANGED
|
@@ -7,6 +7,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
const lodash_1 = __importDefault(require("lodash"));
|
|
9
9
|
const JourneyContext_js_1 = __importDefault(require("../lib/JourneyContext.js"));
|
|
10
|
+
const utils_js_1 = require("../lib/utils.js");
|
|
10
11
|
const waypoint_url_js_1 = __importDefault(require("../lib/waypoint-url.js"));
|
|
11
12
|
const { has } = lodash_1.default;
|
|
12
13
|
const editOrigin = (req) => {
|
|
@@ -18,7 +19,7 @@ const editOrigin = (req) => {
|
|
|
18
19
|
}
|
|
19
20
|
return '';
|
|
20
21
|
};
|
|
21
|
-
function dataMiddleware({ plan,
|
|
22
|
+
function dataMiddleware({ plan, events, }) {
|
|
22
23
|
return [
|
|
23
24
|
(req, res, next) => {
|
|
24
25
|
/* ------------------------------------------------ Request decorations */
|
|
@@ -34,16 +35,25 @@ function dataMiddleware({ plan, mountUrl, events, }) {
|
|
|
34
35
|
// Grab chosen language from session
|
|
35
36
|
req.casa.journeyContext.nav.language = req.session.language;
|
|
36
37
|
/* ------------------------------------------------- Template variables */
|
|
38
|
+
// Figure out the mount URL of the current request
|
|
39
|
+
const mountUrl = (0, utils_js_1.validateUrlPath)(`${req.baseUrl}/`.replace(/\/+/g, '/'));
|
|
40
|
+
// For browser performance reasons, CASA's static assets are potentially
|
|
41
|
+
// delivered over a different route to the `mountUrl` if this CASA app has
|
|
42
|
+
// been mounted on a parameterised route.
|
|
43
|
+
const staticMountUrl = (0, utils_js_1.validateUrlPath)(`${(0, utils_js_1.stripProxyFromUrlPath)(req)}`.replace(/\/+/g, '/'));
|
|
37
44
|
// CASA and userland templates
|
|
38
45
|
res.locals.casa = {
|
|
39
46
|
mountUrl,
|
|
47
|
+
staticMountUrl,
|
|
40
48
|
editMode: req.casa.editMode,
|
|
41
49
|
editOrigin: req.casa.editOrigin,
|
|
42
50
|
};
|
|
43
51
|
res.locals.locale = req.language;
|
|
44
52
|
// Used by govuk-frontend template
|
|
45
|
-
//
|
|
53
|
+
// htmlLang = req.language is provided by i18n-http-middleware
|
|
54
|
+
// assetPath = used for linking to static assets in the govuk-frontend module
|
|
46
55
|
res.locals.htmlLang = req.language;
|
|
56
|
+
res.locals.assetPath = `${staticMountUrl}govuk/assets`;
|
|
47
57
|
// Function for building URLs. This will be curried with the `mountUrl`,
|
|
48
58
|
// `journeyContext`, `edit` and `editOrigin` for convenience. This means
|
|
49
59
|
// the template author does not have to be concerned about the current
|
package/dist/middleware/post.js
CHANGED
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
// 2 middleware: one as a fallback 404 handler, one to handle thrown errors
|
|
7
7
|
const logger_js_1 = __importDefault(require("../lib/logger.js"));
|
|
8
8
|
const log = (0, logger_js_1.default)('middleware:post');
|
|
9
|
-
function postMiddleware(
|
|
9
|
+
function postMiddleware() {
|
|
10
10
|
return [
|
|
11
11
|
(req, res) => {
|
|
12
12
|
res.status(404).render('casa/errors/404.njk');
|
|
@@ -20,7 +20,7 @@ function postMiddleware({ mountUrl, }) {
|
|
|
20
20
|
let TEMPLATE = 'casa/errors/500.njk';
|
|
21
21
|
if (!res.locals.t) {
|
|
22
22
|
res.locals.t = () => ('');
|
|
23
|
-
res.locals.casa = Object.assign(Object.assign({}, (_a = res.locals) === null || _a === void 0 ? void 0 : _a.casa), { mountUrl });
|
|
23
|
+
res.locals.casa = Object.assign(Object.assign({}, (_a = res.locals) === null || _a === void 0 ? void 0 : _a.casa), { mountUrl: `${req.baseUrl}/` });
|
|
24
24
|
TEMPLATE = 'casa/errors/static.njk';
|
|
25
25
|
}
|
|
26
26
|
// CSRF token is invalid in some way
|
package/dist/middleware/pre.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ declare function _default({ helmetConfigurator, }?: {
|
|
|
2
2
|
helmetConfigurator: HelmetConfigurator;
|
|
3
3
|
}): Function[];
|
|
4
4
|
export default _default;
|
|
5
|
-
export type HelmetConfigurator = import('../
|
|
5
|
+
export type HelmetConfigurator = import('../casa').HelmetConfigurator;
|
package/dist/middleware/pre.js
CHANGED
|
@@ -5,10 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const crypto_1 = require("crypto");
|
|
7
7
|
const helmet_1 = __importDefault(require("helmet"));
|
|
8
|
-
const GA_DOMAIN = '
|
|
8
|
+
const GA_DOMAIN = '*.google-analytics.com';
|
|
9
|
+
const GA_ANALYTICS_DOMAIN = '*.analytics.google.com';
|
|
9
10
|
const GTM_DOMAIN = 'www.googletagmanager.com';
|
|
10
11
|
/**
|
|
11
|
-
* @typedef {import('../
|
|
12
|
+
* @typedef {import('../casa').HelmetConfigurator} HelmetConfigurator
|
|
12
13
|
*/
|
|
13
14
|
/**
|
|
14
15
|
* Pre middleware.
|
|
@@ -54,8 +55,8 @@ exports.default = ({ helmetConfigurator = (config) => (config), } = {}) => [
|
|
|
54
55
|
directives: {
|
|
55
56
|
'default-src': ["'none'"],
|
|
56
57
|
'script-src': ["'self'", GA_DOMAIN, GTM_DOMAIN, (req, res) => `'nonce-${res.locals.cspNonce}'`],
|
|
57
|
-
'img-src': ["'self'", GA_DOMAIN],
|
|
58
|
-
'connect-src': ["'self'", GA_DOMAIN],
|
|
58
|
+
'img-src': ["'self'", GA_DOMAIN, GA_ANALYTICS_DOMAIN],
|
|
59
|
+
'connect-src': ["'self'", GA_DOMAIN, GA_ANALYTICS_DOMAIN],
|
|
59
60
|
'frame-src': ["'self'", GTM_DOMAIN],
|
|
60
61
|
'frame-ancestors': ["'self'"],
|
|
61
62
|
'form-action': ["'self'"],
|
|
@@ -20,7 +20,7 @@ const saveAndRedirect = (session, journeyContext, url, res, next) => {
|
|
|
20
20
|
res.redirect(302, url);
|
|
21
21
|
});
|
|
22
22
|
};
|
|
23
|
-
exports.default = ({ waypoint, plan,
|
|
23
|
+
exports.default = ({ waypoint, plan, }) => [
|
|
24
24
|
(req, res, next) => {
|
|
25
25
|
// Determine the next available waypoint after the current one
|
|
26
26
|
const traversed = plan.traverse(req.casa.journeyContext);
|
|
@@ -68,7 +68,7 @@ exports.default = ({ waypoint, plan, mountUrl, }) => [
|
|
|
68
68
|
// Construct the next url
|
|
69
69
|
const nextUrl = (0, waypoint_url_js_1.default)({
|
|
70
70
|
waypoint: nextWaypoint,
|
|
71
|
-
mountUrl
|
|
71
|
+
mountUrl: `${req.baseUrl}/`,
|
|
72
72
|
journeyContext: req.casa.journeyContext,
|
|
73
73
|
edit: req.casa.editMode,
|
|
74
74
|
editOrigin: req.casa.editOrigin,
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
export default function sessionMiddleware({ cookieParserMiddleware, secret, name, secure, ttl,
|
|
1
|
+
export default function sessionMiddleware({ cookieParserMiddleware, secret, name, secure, ttl, cookieSameSite, cookiePath, store, }: {
|
|
2
2
|
cookieParserMiddleware: any;
|
|
3
3
|
secret: any;
|
|
4
4
|
name: any;
|
|
5
5
|
secure: any;
|
|
6
6
|
ttl: any;
|
|
7
|
-
mountUrl?: string | undefined;
|
|
8
7
|
cookieSameSite?: boolean | undefined;
|
|
9
8
|
cookiePath?: string | undefined;
|
|
10
9
|
store?: any;
|
|
@@ -31,8 +31,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
31
31
|
// interrupting their journey.
|
|
32
32
|
const express_session_1 = __importStar(require("express-session"));
|
|
33
33
|
const logger_js_1 = __importDefault(require("../lib/logger.js"));
|
|
34
|
+
const utils_js_1 = require("../lib/utils.js");
|
|
34
35
|
const log = (0, logger_js_1.default)('middleware:session');
|
|
35
|
-
const sessionExpiryMiddleware = (
|
|
36
|
+
const sessionExpiryMiddleware = (ttl, getCookie, touchCookie, removeCookie) => (req, res, next) => {
|
|
36
37
|
var _a;
|
|
37
38
|
const lastModified = getCookie(req);
|
|
38
39
|
const age = Math.floor(Date.now() * 0.001) - lastModified;
|
|
@@ -50,7 +51,7 @@ const sessionExpiryMiddleware = (mountUrl, ttl, getCookie, touchCookie, removeCo
|
|
|
50
51
|
touchCookie(res);
|
|
51
52
|
if (req.method === 'POST') {
|
|
52
53
|
log.info('The CSRF token for this POST request will now be invalid for this regenerated session. Redirecting to app mount point.');
|
|
53
|
-
res.redirect(302,
|
|
54
|
+
res.redirect(302, (0, utils_js_1.validateUrlPath)(`${req.baseUrl}/`));
|
|
54
55
|
}
|
|
55
56
|
else {
|
|
56
57
|
next();
|
|
@@ -73,7 +74,8 @@ const sessionExpiryMiddleware = (mountUrl, ttl, getCookie, touchCookie, removeCo
|
|
|
73
74
|
referrer: req.originalUrl,
|
|
74
75
|
lang: language,
|
|
75
76
|
});
|
|
76
|
-
|
|
77
|
+
/* eslint-disable-next-line prefer-template */
|
|
78
|
+
res.redirect(302, (0, utils_js_1.validateUrlPath)(`${req.baseUrl}/session-timeout`) + `?${params.toString()}`);
|
|
77
79
|
}
|
|
78
80
|
});
|
|
79
81
|
}
|
|
@@ -87,7 +89,7 @@ const sessionExpiryMiddleware = (mountUrl, ttl, getCookie, touchCookie, removeCo
|
|
|
87
89
|
// - set the session cookie
|
|
88
90
|
// - parse request cookies
|
|
89
91
|
// - handle expiry of server-side session
|
|
90
|
-
function sessionMiddleware({ cookieParserMiddleware, secret, name, secure, ttl,
|
|
92
|
+
function sessionMiddleware({ cookieParserMiddleware, secret, name, secure, ttl, cookieSameSite = true, cookiePath = '/', store = new express_session_1.MemoryStore(), }) {
|
|
91
93
|
const commonCookieOptions = {
|
|
92
94
|
httpOnly: true,
|
|
93
95
|
path: cookiePath,
|
|
@@ -126,7 +128,7 @@ function sessionMiddleware({ cookieParserMiddleware, secret, name, secure, ttl,
|
|
|
126
128
|
store,
|
|
127
129
|
}),
|
|
128
130
|
cookieParserMiddleware,
|
|
129
|
-
sessionExpiryMiddleware(
|
|
131
|
+
sessionExpiryMiddleware(ttl, getCookie, touchCookie, removeCookie),
|
|
130
132
|
];
|
|
131
133
|
}
|
|
132
134
|
exports.default = sessionMiddleware;
|
|
@@ -10,7 +10,7 @@ const waypoint_url_js_1 = __importDefault(require("../lib/waypoint-url.js"));
|
|
|
10
10
|
const logger_js_1 = __importDefault(require("../lib/logger.js"));
|
|
11
11
|
const { has } = lodash_1.default;
|
|
12
12
|
const log = (0, logger_js_1.default)('middleware:skip-waypoint');
|
|
13
|
-
exports.default = ({ waypoint,
|
|
13
|
+
exports.default = ({ waypoint, }) => [
|
|
14
14
|
(req, res, next) => {
|
|
15
15
|
if (!has(req.query, 'skipto')) {
|
|
16
16
|
return next();
|
|
@@ -24,7 +24,7 @@ exports.default = ({ waypoint, mountUrl, }) => [
|
|
|
24
24
|
});
|
|
25
25
|
JourneyContext_js_1.default.putContext(req.session, req.casa.journeyContext);
|
|
26
26
|
const redirectUrl = (0, waypoint_url_js_1.default)({
|
|
27
|
-
mountUrl
|
|
27
|
+
mountUrl: `${req.baseUrl}/`,
|
|
28
28
|
waypoint: skipTo,
|
|
29
29
|
edit: req.casa.editMode,
|
|
30
30
|
editOrigin: req.casa.editOrigin,
|
|
@@ -18,11 +18,11 @@ const log = (0, logger_js_1.default)('middleware:steer-journey');
|
|
|
18
18
|
* @param {object} obj Options
|
|
19
19
|
* @param {string} obj.waypoint Current waypoint
|
|
20
20
|
* @param {Plan} obj.plan CASA Plan
|
|
21
|
-
* @param {string} obj.mountUrl Mount URL
|
|
22
21
|
* @returns {void}
|
|
23
22
|
*/
|
|
24
|
-
exports.default = ({ waypoint, plan,
|
|
23
|
+
exports.default = ({ waypoint, plan, }) => [
|
|
25
24
|
(req, res, next) => {
|
|
25
|
+
const mountUrl = `${req.baseUrl}/`;
|
|
26
26
|
// If the requested waypoint doesn't exist in the traversed journey, send
|
|
27
27
|
// the user back to the last good waypoint.
|
|
28
28
|
const traversed = plan.traverse(req.casa.journeyContext);
|