@dwp/govuk-casa 8.0.3 → 8.2.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 +30 -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 +107 -1
- package/dist/lib/CasaTemplateLoader.d.ts +7 -0
- package/dist/lib/JourneyContext.d.ts +15 -27
- package/dist/lib/JourneyContext.js +37 -16
- package/dist/lib/configuration-ingestor.d.ts +21 -139
- package/dist/lib/configuration-ingestor.js +39 -84
- package/dist/lib/configure.d.ts +6 -77
- package/dist/lib/configure.js +104 -40
- package/dist/lib/index.js +5 -1
- package/dist/lib/nunjucks.d.ts +1 -6
- package/dist/lib/nunjucks.js +1 -3
- package/dist/lib/utils.d.ts +21 -10
- package/dist/lib/utils.js +54 -8
- package/dist/lib/validators/dateObject.d.ts +1 -0
- package/dist/lib/validators/email.d.ts +2 -0
- package/dist/lib/validators/inArray.d.ts +2 -0
- package/dist/lib/validators/nino.d.ts +2 -0
- package/dist/lib/validators/postalAddressObject.d.ts +1 -0
- package/dist/lib/validators/regex.d.ts +2 -0
- package/dist/lib/validators/required.d.ts +4 -0
- package/dist/lib/validators/strlen.d.ts +2 -0
- package/dist/lib/validators/wordCount.d.ts +2 -0
- 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 +4 -2
- package/dist/middleware/pre.js +17 -6
- 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 +12 -6
- 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 +30 -28
- 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,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
|
*/
|
|
@@ -72,7 +59,7 @@ function configure(config = {}) {
|
|
|
72
59
|
}, pages = [], plan = null, hooks = [], plugins = [], events = [], i18n = {
|
|
73
60
|
dirs: [],
|
|
74
61
|
locales: ['en', 'cy'],
|
|
75
|
-
}, } = (0, configuration_ingestor_js_1.default)(config);
|
|
62
|
+
}, helmetConfigurator = undefined, } = (0, configuration_ingestor_js_1.default)(config);
|
|
76
63
|
// Prepare all page hooks so they are prefixed with the `journey.` scope.
|
|
77
64
|
pages.forEach((page) => {
|
|
78
65
|
var _a;
|
|
@@ -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'),
|
|
@@ -92,8 +78,8 @@ function configure(config = {}) {
|
|
|
92
78
|
// Prepare mandatory middleware
|
|
93
79
|
// These _must_ be added to the ExpressJS application at the start and end
|
|
94
80
|
// of all other middleware respectively.
|
|
95
|
-
const preMiddleware = (0, pre_js_1.default)();
|
|
96
|
-
const postMiddleware = (0, post_js_1.default)(
|
|
81
|
+
const preMiddleware = (0, pre_js_1.default)({ helmetConfigurator });
|
|
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,107 @@ 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
|
+
//
|
|
158
|
+
// This intervention would not be needed for apps that omit `mountUrl` as if
|
|
159
|
+
// so, it's assumed they're following the guidance for setting up a proxy
|
|
160
|
+
// as described in `docs/guides/setup-behind-a-proxy.md`.
|
|
161
|
+
if (config.mountUrl) {
|
|
162
|
+
log.warn('[DEPRECATION WARNING] Using configuration attribute, mountUrl. This will be removed in an upcoming major version');
|
|
163
|
+
app.use((req, res, next) => {
|
|
164
|
+
// Mimic what the `docs/guides/setup-behind-a-proxy.md` guidance
|
|
165
|
+
// recommends for stripping off any proxy prefixes to leave just the
|
|
166
|
+
// "mountUrl" remaining.
|
|
167
|
+
const originalBaseUrl = req.baseUrl;
|
|
168
|
+
req.baseUrl = mountUrl.replace(/\/$/, '');
|
|
169
|
+
// If the app has been mounted directly on the specific `mountUrl`, then
|
|
170
|
+
// there's nothing we need to do and can let this request pass-through.
|
|
171
|
+
if (req.baseUrl === originalBaseUrl) {
|
|
172
|
+
next();
|
|
173
|
+
}
|
|
174
|
+
else if (req.__CASA_BASE_URL_REWRITTEN__) {
|
|
175
|
+
delete req.__CASA_BASE_URL_REWRITTEN__;
|
|
176
|
+
next();
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// Issuing this call will re-run this same middleware, so we use this
|
|
180
|
+
// `__CASA_BASE_URL_REWRITTEN__` flag to prevent recursion.
|
|
181
|
+
req.__CASA_BASE_URL_REWRITTEN__ = true;
|
|
182
|
+
req.app.handle(req, res, next);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
// Attach a handler to redirect requests for `/` to the first waypoint in
|
|
187
|
+
// the plan
|
|
188
|
+
if (plan) {
|
|
189
|
+
const re = (0, path_to_regexp_1.pathToRegexp)(`${route}`.replace(/\/+/g, '/'));
|
|
190
|
+
app.use(re, (req, res) => {
|
|
191
|
+
const reqUrl = new URL(req.url, 'https://placeholder.test/');
|
|
192
|
+
const reqPath = (0, utils_js_1.validateUrlPath)(`${req.baseUrl}${reqUrl.pathname}${plan.getWaypoints()[0]}`);
|
|
193
|
+
let reqParams = reqUrl.searchParams.toString();
|
|
194
|
+
reqParams = reqParams ? `?${reqParams}` : '';
|
|
195
|
+
res.redirect(302, `${reqPath}${reqParams}`);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
// Service static assets from the `app` rather than the `router`. The router
|
|
199
|
+
// may contain paramaterised path segments which would mean serving static
|
|
200
|
+
// assets over a dynamic URL each time, thus causing lots of cache misses on
|
|
201
|
+
// the browser.
|
|
202
|
+
const sealedStaticRouter = staticRouter.seal();
|
|
151
203
|
app.use(preMiddleware);
|
|
152
|
-
app.use(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
app
|
|
204
|
+
app.use(sealedStaticRouter);
|
|
205
|
+
const router = (0, express_1.Router)({
|
|
206
|
+
// Required so that any parameters in the URL are propagated to middleware
|
|
207
|
+
mergeParams: true,
|
|
208
|
+
});
|
|
209
|
+
router.use(preMiddleware);
|
|
210
|
+
// !!! DEPRECATE in v9 !!! For performance reasons, static assets will
|
|
211
|
+
// always be handled via the `app` middleware rather than `router`.
|
|
212
|
+
// Anywhere `mountUrl` is used in templates to service static assets must be
|
|
213
|
+
// changed to use `staticMountUrl`.
|
|
214
|
+
// TASK: remove this line below
|
|
215
|
+
router.use(sealedStaticRouter);
|
|
216
|
+
router.use(sessionMiddleware);
|
|
217
|
+
router.use(i18nMiddleware);
|
|
218
|
+
router.use(bodyParserMiddleware);
|
|
219
|
+
router.use(dataMiddleware);
|
|
220
|
+
router.use(ancillaryRouter.seal());
|
|
221
|
+
router.use(journeyRouter.seal());
|
|
222
|
+
router.use(postMiddleware);
|
|
223
|
+
app.use(route, router);
|
|
160
224
|
return app;
|
|
161
225
|
};
|
|
162
226
|
// Prepare configuration result
|
package/dist/lib/index.js
CHANGED
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
7
|
if (k2 === undefined) k2 = k;
|
|
8
|
-
Object.
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
9
13
|
}) : (function(o, m, k, k2) {
|
|
10
14
|
if (k2 === undefined) k2 = k;
|
|
11
15
|
o[k2] = m[k];
|
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,9 +34,26 @@ 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;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Checks if the given string can be used as an object key.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} key Proposed Object key
|
|
45
|
+
* @returns {string} Same key if it's valid
|
|
46
|
+
* @throws {Error} if proposed key is an invalid keyword
|
|
47
|
+
*/
|
|
48
|
+
export function notProto(key: string): string;
|
|
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.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');
|
|
@@ -111,3 +118,42 @@ function validateHookPath(path) {
|
|
|
111
118
|
}
|
|
112
119
|
}
|
|
113
120
|
exports.validateHookPath = validateHookPath;
|
|
121
|
+
/**
|
|
122
|
+
* Checks if the given string can be used as an object key.
|
|
123
|
+
*
|
|
124
|
+
* @param {string} key Proposed Object key
|
|
125
|
+
* @returns {string} Same key if it's valid
|
|
126
|
+
* @throws {Error} if proposed key is an invalid keyword
|
|
127
|
+
*/
|
|
128
|
+
function notProto(key) {
|
|
129
|
+
if (['__proto__', 'constructor', 'prototype'].includes(String(key).toLowerCase())) {
|
|
130
|
+
throw new Error('Attempt to use prototype key disallowed');
|
|
131
|
+
}
|
|
132
|
+
return key;
|
|
133
|
+
}
|
|
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;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export default class Required extends ValidatorFactory {
|
|
2
2
|
name: string;
|
|
3
|
+
validate(value: any, dataContext?: {}): object[];
|
|
4
|
+
sanitise(value: any): string | (string | undefined)[] | {
|
|
5
|
+
[k: string]: string | undefined;
|
|
6
|
+
} | undefined;
|
|
3
7
|
}
|
|
4
8
|
import ValidatorFactory from "../ValidatorFactory.js";
|
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
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
declare function _default({ helmetConfigurator, }?: {
|
|
2
|
+
helmetConfigurator: HelmetConfigurator;
|
|
3
|
+
}): Function[];
|
|
3
4
|
export default _default;
|
|
5
|
+
export type HelmetConfigurator = import('../casa').HelmetConfigurator;
|
package/dist/middleware/pre.js
CHANGED
|
@@ -5,9 +5,20 @@ 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
|
+
/**
|
|
12
|
+
* @typedef {import('../casa').HelmetConfigurator} HelmetConfigurator
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Pre middleware.
|
|
16
|
+
*
|
|
17
|
+
* @param {object} opts Options
|
|
18
|
+
* @param {HelmetConfigurator} opts.helmetConfigurator Function to customise Helmet configuration
|
|
19
|
+
* @returns {Function[]} List of middleware
|
|
20
|
+
*/
|
|
21
|
+
exports.default = ({ helmetConfigurator = (config) => (config), } = {}) => [
|
|
11
22
|
// Only allow certain request methods
|
|
12
23
|
(req, res, next) => {
|
|
13
24
|
if (req.method !== 'GET' && req.method !== 'POST') {
|
|
@@ -37,15 +48,15 @@ exports.default = () => [
|
|
|
37
48
|
next();
|
|
38
49
|
},
|
|
39
50
|
// Helmet suite of headers
|
|
40
|
-
(0, helmet_1.default)({
|
|
51
|
+
(0, helmet_1.default)(helmetConfigurator({
|
|
41
52
|
// Allows GA which is typically used, and a known inline script nonce
|
|
42
53
|
contentSecurityPolicy: {
|
|
43
54
|
useDefaults: true,
|
|
44
55
|
directives: {
|
|
45
56
|
'default-src': ["'none'"],
|
|
46
57
|
'script-src': ["'self'", GA_DOMAIN, GTM_DOMAIN, (req, res) => `'nonce-${res.locals.cspNonce}'`],
|
|
47
|
-
'img-src': ["'self'", GA_DOMAIN],
|
|
48
|
-
'connect-src': ["'self'", GA_DOMAIN],
|
|
58
|
+
'img-src': ["'self'", GA_DOMAIN, GA_ANALYTICS_DOMAIN],
|
|
59
|
+
'connect-src': ["'self'", GA_DOMAIN, GA_ANALYTICS_DOMAIN],
|
|
49
60
|
'frame-src': ["'self'", GTM_DOMAIN],
|
|
50
61
|
'frame-ancestors': ["'self'"],
|
|
51
62
|
'form-action': ["'self'"],
|
|
@@ -55,5 +66,5 @@ exports.default = () => [
|
|
|
55
66
|
},
|
|
56
67
|
// // Require referrer to aid navigation
|
|
57
68
|
// referrerPolicy: 'no-referrer, same-origin',
|
|
58
|
-
}),
|
|
69
|
+
})),
|
|
59
70
|
];
|