hof 20.0.0-beta.7 → 20.0.0-beta.8
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/.github/workflows/automate-publish.yml +1 -1
- package/.github/workflows/automate-tag.yml +1 -1
- package/.nyc_output/e2fdc3eb-4fd2-47e0-a392-fe5f665776a4.json +1 -0
- package/.nyc_output/processinfo/e2fdc3eb-4fd2-47e0-a392-fe5f665776a4.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -1
- package/build/lib/mkdir.js +2 -2
- package/components/date/index.js +34 -23
- package/components/emailer/index.js +49 -41
- package/components/emailer/transports/debug.js +1 -2
- package/components/summary/index.js +18 -0
- package/config/hof-defaults.js +5 -3
- package/config/rate-limits.js +20 -0
- package/config/sanitisation-rules.js +29 -0
- package/controller/base-controller.js +26 -8
- package/controller/controller.js +11 -15
- package/frontend/govuk-template/build/config.js +1 -1
- package/frontend/template-mixins/mixins/template-mixins.js +3 -3
- package/frontend/template-partials/translations/src/en/errors.json +12 -0
- package/frontend/template-partials/views/rate-limit-error.html +10 -0
- package/frontend/themes/gov-uk/client-js/govuk-cookies.js +43 -44
- package/frontend/themes/gov-uk/client-js/index.js +5 -3
- package/frontend/themes/gov-uk/client-js/skip-to-main.js +18 -17
- package/index.js +9 -4
- package/lib/router.js +2 -1
- package/lib/settings.js +9 -8
- package/middleware/errors.js +32 -0
- package/middleware/index.js +2 -1
- package/middleware/rate-limiter.js +98 -0
- package/package.json +5 -6
- package/sandbox/apps/sandbox/fields.js +1 -1
- package/sandbox/server.js +5 -0
- package/transpiler/lib/write-files.js +1 -2
- package/utilities/helpers/index.js +16 -1
- package/wizard/index.js +1 -0
- package/.nyc_output/c8361c72-4aa7-47d0-9f96-d0b84d395382.json +0 -1
- package/.nyc_output/processinfo/c8361c72-4aa7-47d0-9f96-d0b84d395382.json +0 -1
@@ -1,6 +1,7 @@
|
|
1
|
+
/* eslint-disable no-undef, no-param-reassign, no-unused-vars */
|
1
2
|
(function () {
|
2
|
-
|
3
|
-
|
3
|
+
'use strict';
|
4
|
+
const root = this;
|
4
5
|
if(typeof root.GOVUK === 'undefined') { root.GOVUK = {}; }
|
5
6
|
|
6
7
|
/*
|
@@ -19,37 +20,35 @@
|
|
19
20
|
GOVUK.cookie('hobnob', null);
|
20
21
|
*/
|
21
22
|
GOVUK.cookie = function (name, value, options) {
|
22
|
-
if(typeof value !== 'undefined'){
|
23
|
+
if(typeof value !== 'undefined') {
|
23
24
|
if(value === false || value === null) {
|
24
25
|
return GOVUK.setCookie(name, '', { days: -1 });
|
25
|
-
} else {
|
26
|
-
return GOVUK.setCookie(name, value, options);
|
27
26
|
}
|
28
|
-
|
29
|
-
return GOVUK.getCookie(name);
|
27
|
+
return GOVUK.setCookie(name, value, options);
|
30
28
|
}
|
29
|
+
return GOVUK.getCookie(name);
|
31
30
|
};
|
32
31
|
GOVUK.setCookie = function (name, value, options) {
|
33
32
|
if(typeof options === 'undefined') {
|
34
33
|
options = {};
|
35
34
|
}
|
36
|
-
|
35
|
+
let cookieString = name + '=' + value + '; path=/';
|
37
36
|
if (options.days) {
|
38
|
-
|
37
|
+
const date = new Date();
|
39
38
|
date.setTime(date.getTime() + (options.days * 24 * 60 * 60 * 1000));
|
40
|
-
cookieString = cookieString +
|
39
|
+
cookieString = cookieString + '; expires=' + date.toGMTString();
|
41
40
|
}
|
42
|
-
if (document.location.protocol
|
43
|
-
cookieString = cookieString +
|
41
|
+
if (document.location.protocol === 'https:') {
|
42
|
+
cookieString = cookieString + '; Secure';
|
44
43
|
}
|
45
44
|
document.cookie = cookieString;
|
46
45
|
};
|
47
46
|
GOVUK.getCookie = function (name) {
|
48
|
-
|
49
|
-
|
50
|
-
for(
|
51
|
-
|
52
|
-
while (cookie.charAt(0)
|
47
|
+
const nameEQ = name + '=';
|
48
|
+
const cookies = document.cookie.split(';');
|
49
|
+
for(let i = 0, len = cookies.length; i < len; i++) {
|
50
|
+
let cookie = cookies[i];
|
51
|
+
while (cookie.charAt(0) === ' ') {
|
53
52
|
cookie = cookie.substring(1, cookie.length);
|
54
53
|
}
|
55
54
|
if (cookie.indexOf(nameEQ) === 0) {
|
@@ -60,33 +59,33 @@
|
|
60
59
|
};
|
61
60
|
}).call(this);
|
62
61
|
(function () {
|
63
|
-
'use strict'
|
64
|
-
|
65
|
-
if (typeof root.GOVUK === 'undefined') { root.GOVUK = {} }
|
62
|
+
'use strict';
|
63
|
+
const root = this;
|
64
|
+
if (typeof root.GOVUK === 'undefined') { root.GOVUK = {}; }
|
66
65
|
|
67
66
|
GOVUK.addCookieMessage = function () {
|
68
|
-
|
67
|
+
const message = document.getElementById('global-cookie-message');
|
69
68
|
|
70
|
-
|
69
|
+
const hasCookieMessage = (message && GOVUK.cookie('seen_cookie_message') === null);
|
71
70
|
|
72
71
|
if (hasCookieMessage) {
|
73
|
-
message.style.display = 'block'
|
74
|
-
GOVUK.cookie('seen_cookie_message', 'yes', { days: 28 })
|
72
|
+
message.style.display = 'block';
|
73
|
+
GOVUK.cookie('seen_cookie_message', 'yes', { days: 28 });
|
75
74
|
|
76
75
|
document.addEventListener('DOMContentLoaded', function (event) {
|
77
76
|
if (GOVUK.analytics && typeof GOVUK.analytics.trackEvent === 'function') {
|
78
77
|
GOVUK.analytics.trackEvent('cookieBanner', 'Cookie banner shown', {
|
79
78
|
value: 1,
|
80
79
|
nonInteraction: true
|
81
|
-
})
|
80
|
+
});
|
82
81
|
}
|
83
|
-
})
|
84
|
-
}
|
85
|
-
}
|
82
|
+
});
|
83
|
+
}
|
84
|
+
};
|
86
85
|
}).call(this)
|
87
86
|
;
|
88
|
-
(function() {
|
89
|
-
|
87
|
+
(function () {
|
88
|
+
'use strict';
|
90
89
|
|
91
90
|
// add cookie message
|
92
91
|
if (window.GOVUK && GOVUK.addCookieMessage) {
|
@@ -94,28 +93,28 @@
|
|
94
93
|
}
|
95
94
|
|
96
95
|
// header navigation toggle
|
97
|
-
if (document.querySelectorAll && document.addEventListener){
|
98
|
-
|
99
|
-
|
100
|
-
for(i=0,_i=els.length; i<_i; i++){
|
101
|
-
els[i].addEventListener('click', function(e){
|
96
|
+
if (document.querySelectorAll && document.addEventListener) {
|
97
|
+
const els = document.querySelectorAll('.js-header-toggle');
|
98
|
+
let i; let _i;
|
99
|
+
for(i = 0, _i = els.length; i < _i; i++) {
|
100
|
+
els[i].addEventListener('click', function (e) {
|
102
101
|
e.preventDefault();
|
103
|
-
|
104
|
-
|
105
|
-
|
102
|
+
const target = document.getElementById(this.getAttribute('href').substr(1));
|
103
|
+
const targetClass = target.getAttribute('class') || '';
|
104
|
+
const sourceClass = this.getAttribute('class') || '';
|
106
105
|
|
107
|
-
if(targetClass.indexOf('js-visible') !== -1){
|
106
|
+
if(targetClass.indexOf('js-visible') !== -1) {
|
108
107
|
target.setAttribute('class', targetClass.replace(/(^|\s)js-visible(\s|$)/, ''));
|
109
108
|
} else {
|
110
|
-
target.setAttribute('class', targetClass +
|
109
|
+
target.setAttribute('class', targetClass + ' js-visible');
|
111
110
|
}
|
112
|
-
if(sourceClass.indexOf('js-visible') !== -1){
|
111
|
+
if(sourceClass.indexOf('js-visible') !== -1) {
|
113
112
|
this.setAttribute('class', sourceClass.replace(/(^|\s)js-visible(\s|$)/, ''));
|
114
113
|
} else {
|
115
|
-
this.setAttribute('class', sourceClass +
|
114
|
+
this.setAttribute('class', sourceClass + ' js-visible');
|
116
115
|
}
|
117
|
-
this.setAttribute('aria-expanded', this.getAttribute('aria-expanded') !==
|
118
|
-
target.setAttribute('aria-hidden', target.getAttribute('aria-hidden') ===
|
116
|
+
this.setAttribute('aria-expanded', this.getAttribute('aria-expanded') !== 'true');
|
117
|
+
target.setAttribute('aria-hidden', target.getAttribute('aria-hidden') === 'false');
|
119
118
|
});
|
120
119
|
}
|
121
120
|
}
|
@@ -8,12 +8,14 @@ var formFocus = toolkit.formFocus;
|
|
8
8
|
var characterCount = toolkit.characterCount;
|
9
9
|
var validation = toolkit.validation;
|
10
10
|
|
11
|
-
var GOVUK = require('govuk-frontend')
|
12
|
-
|
13
|
-
window.GOVUK = GOVUK;
|
11
|
+
var GOVUK = require('govuk-frontend');
|
12
|
+
// eslint-disable-next-line no-unused-vars
|
14
13
|
var skipToMain = require('./skip-to-main');
|
14
|
+
// eslint-disable-next-line no-unused-vars
|
15
15
|
var cookie = require('./govuk-cookies');
|
16
16
|
var cookieSettings = require('./cookieSettings');
|
17
|
+
GOVUK.initAll();
|
18
|
+
window.GOVUK = GOVUK;
|
17
19
|
|
18
20
|
toolkit.detailsSummary();
|
19
21
|
|
@@ -1,18 +1,19 @@
|
|
1
1
|
const skipToMain = function () {
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
}
|
16
|
-
}
|
17
|
-
}
|
18
|
-
|
2
|
+
const skipToMainLink = document.getElementById('skip-to-main');
|
3
|
+
const firstControlId = skipToMainLink.hash.split('#')[1] ? skipToMainLink.hash.split('#')[1] : 'main-content';
|
4
|
+
if(firstControlId === 'main-content') {
|
5
|
+
skipToMainLink.setAttribute('href', '#main-content');
|
6
|
+
}
|
7
|
+
if(firstControlId) {
|
8
|
+
// eslint-disable-next-line no-unused-vars
|
9
|
+
skipToMainLink.onclick = function (e) {
|
10
|
+
// here timeout added just to make this functionality asynchronous
|
11
|
+
// to focus on form as well as non form contents
|
12
|
+
setTimeout(() => {
|
13
|
+
const firstControl = document.getElementById(firstControlId);
|
14
|
+
firstControl.focus();
|
15
|
+
}, 10);
|
16
|
+
};
|
17
|
+
}
|
18
|
+
};
|
19
|
+
skipToMain();
|
package/index.js
CHANGED
@@ -120,8 +120,9 @@ const getContentSecurityPolicy = (config, res) => {
|
|
120
120
|
* @param options.getTerms {boolean} Optional boolean - whether to mount the /terms endpoint
|
121
121
|
* @param options.getCookies {boolean} Optional boolean - whether to mount the /cookies endpoint
|
122
122
|
* @param options.noCache {boolean} Optional boolean - whether to disable caching
|
123
|
-
* @param options.getAccessibilityStatement {boolean} Optional boolean - whether to mount the
|
124
|
-
*
|
123
|
+
* @param options.getAccessibilityStatement {boolean} Optional boolean - whether to mount the
|
124
|
+
* /accessibility-statement endpoint
|
125
|
+
*
|
125
126
|
* @returns {object} A new HOF application using the configuration supplied in options
|
126
127
|
*/
|
127
128
|
function bootstrap(options) {
|
@@ -205,9 +206,13 @@ function bootstrap(options) {
|
|
205
206
|
}));
|
206
207
|
app.use(mixins());
|
207
208
|
app.use(markdown(config.markdown));
|
208
|
-
|
209
|
+
// rate limits have to be loaded before all routes so it is applied to them
|
210
|
+
if (config.rateLimits.requests.active) {
|
211
|
+
app.use(hofMiddleware.rateLimiter(config, 'requests'));
|
212
|
+
}
|
213
|
+
|
209
214
|
// Set up routing so <YOUR-SITE-URL>/assets are served from /node_modules/govuk-frontend/govuk/assets
|
210
|
-
app.use('/assets', express.static(path.join(__dirname, '/node_modules/govuk-frontend/govuk/assets')))
|
215
|
+
app.use('/assets', express.static(path.join(__dirname, '/node_modules/govuk-frontend/govuk/assets')));
|
211
216
|
|
212
217
|
if (config.getAccessibility === true) {
|
213
218
|
deprecate(
|
package/lib/router.js
CHANGED
@@ -19,7 +19,8 @@ function getWizardConfig(config) {
|
|
19
19
|
const wizardConfig = {
|
20
20
|
name: config.route.name || (config.route.baseUrl || '').replace('/', ''),
|
21
21
|
protocol: config.protocol,
|
22
|
-
env: config.env
|
22
|
+
env: config.env,
|
23
|
+
sanitiseInputs: config.sanitiseInputs
|
23
24
|
};
|
24
25
|
|
25
26
|
if (config.appConfig) {
|
package/lib/settings.js
CHANGED
@@ -7,19 +7,20 @@ const hoganExpressStrict = require('hogan-express-strict');
|
|
7
7
|
const expressPartialTemplates = require('express-partial-templates');
|
8
8
|
const bodyParser = require('body-parser');
|
9
9
|
|
10
|
-
const
|
11
|
-
return views.filter(view => dirExists(view))
|
12
|
-
}
|
13
|
-
|
14
|
-
const dirExists = (dir) => {
|
10
|
+
const dirExists = dir => {
|
15
11
|
try {
|
16
12
|
if (fs.existsSync(dir)) {
|
17
13
|
return true;
|
18
14
|
}
|
15
|
+
return false;
|
19
16
|
} catch(err) {
|
20
|
-
throw new Error(`${err}: Cannot check if the directory path exists`)
|
17
|
+
throw new Error(`${err}: Cannot check if the directory path exists`);
|
21
18
|
}
|
22
|
-
}
|
19
|
+
};
|
20
|
+
|
21
|
+
const filterEmptyViews = views => {
|
22
|
+
return views.filter(view => dirExists(view));
|
23
|
+
};
|
23
24
|
|
24
25
|
module.exports = async (app, config) => {
|
25
26
|
const viewEngine = config.viewEngine || 'html';
|
@@ -31,7 +32,7 @@ module.exports = async (app, config) => {
|
|
31
32
|
|
32
33
|
app.use(config.theme());
|
33
34
|
|
34
|
-
const filteredViews = filterEmptyViews(config.theme.views)
|
35
|
+
const filteredViews = filterEmptyViews(config.theme.views);
|
35
36
|
const viewPaths = [].concat(filteredViews);
|
36
37
|
app.set('view engine', viewEngine);
|
37
38
|
app.enable('view cache');
|
package/middleware/errors.js
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
/* eslint-disable no-unused-vars */
|
2
2
|
'use strict';
|
3
3
|
|
4
|
+
const rateLimitsConfig = require('../config/rate-limits');
|
5
|
+
|
4
6
|
const errorTitle = code => `${code}_ERROR`;
|
5
7
|
const errorMsg = code => `There is a ${code}_ERROR`;
|
6
8
|
// eslint-disable-next-line complexity
|
@@ -21,6 +23,36 @@ const getContent = (err, translate) => {
|
|
21
23
|
content.message = (translate && translate('errors.cookies-required.message'));
|
22
24
|
}
|
23
25
|
|
26
|
+
if (err.code === 'DDOS_RATE_LIMIT') {
|
27
|
+
err.status = 429;
|
28
|
+
err.template = 'rate-limit-error';
|
29
|
+
err.title = (translate && translate('errors.ddos-rate-limit.title'));
|
30
|
+
err.message = (translate && translate('errors.ddos-rate-limit.message'));
|
31
|
+
err.preTimeToWait = (translate && translate('errors.ddos-rate-limit.pre-time-to-wait'));
|
32
|
+
err.timeToWait = rateLimitsConfig.rateLimits.requests.windowSizeInMinutes;
|
33
|
+
err.postTimeToWait = (translate && translate('errors.ddos-rate-limit.post-time-to-wait'));
|
34
|
+
content.title = (translate && translate('errors.ddos-rate-limit.title'));
|
35
|
+
content.message = (translate && translate('errors.ddos-rate-limit.message'));
|
36
|
+
content.preTimeToWait = (translate && translate('errors.ddos-rate-limit.pre-time-to-wait'));
|
37
|
+
content.timeToWait = rateLimitsConfig.rateLimits.requests.windowSizeInMinutes;
|
38
|
+
content.postTimeToWait = (translate && translate('errors.ddos-rate-limit.post-time-to-wait'));
|
39
|
+
}
|
40
|
+
|
41
|
+
if (err.code === 'SUBMISSION_RATE_LIMIT') {
|
42
|
+
err.status = 429;
|
43
|
+
err.template = 'rate-limit-error';
|
44
|
+
err.title = (translate && translate('errors.submission-rate-limit.title'));
|
45
|
+
err.message = (translate && translate('errors.submission-rate-limit.message'));
|
46
|
+
err.preTimeToWait = (translate && translate('errors.submission-rate-limit.pre-time-to-wait'));
|
47
|
+
err.timeToWait = rateLimitsConfig.rateLimits.submissions.windowSizeInMinutes;
|
48
|
+
err.postTimeToWait = (translate && translate('errors.submission-rate-limit.post-time-to-wait'));
|
49
|
+
content.title = (translate && translate('errors.submission-rate-limit.title'));
|
50
|
+
content.message = (translate && translate('errors.submission-rate-limit.message'));
|
51
|
+
content.preTimeToWait = (translate && translate('errors.submission-rate-limit.pre-time-to-wait'));
|
52
|
+
content.timeToWait = rateLimitsConfig.rateLimits.submissions.windowSizeInMinutes;
|
53
|
+
content.postTimeToWait = (translate && translate('errors.submission-rate-limit.post-time-to-wait'));
|
54
|
+
}
|
55
|
+
|
24
56
|
err.code = err.code || 'UNKNOWN';
|
25
57
|
err.status = err.status || 500;
|
26
58
|
|
package/middleware/index.js
CHANGED
@@ -0,0 +1,98 @@
|
|
1
|
+
|
2
|
+
const moment = require('moment');
|
3
|
+
const redis = require('redis');
|
4
|
+
const config = require('./../config/hof-defaults');
|
5
|
+
|
6
|
+
module.exports = (options, rateLimitType) => {
|
7
|
+
// eslint-disable-next-line no-console
|
8
|
+
const logger = options.logger || { log: (func, msg) => console[func](msg) };
|
9
|
+
const rateLimits = options.rateLimits[rateLimitType];
|
10
|
+
const timestampName = `${rateLimitType}TimeStamp`;
|
11
|
+
const countName = `${rateLimitType}Count`;
|
12
|
+
|
13
|
+
const WINDOW_SIZE_IN_MINUTES = rateLimits.windowSizeInMinutes;
|
14
|
+
const MAX_WINDOW_REQUEST_COUNT = rateLimits.maxWindowRequestCount;
|
15
|
+
const WINDOW_LOG_INTERVAL_IN_MINUTES = rateLimits.windowLogIntervalInMinutes;
|
16
|
+
const ERROR_CODE = rateLimits.errCode;
|
17
|
+
|
18
|
+
return async (req, res, next) => {
|
19
|
+
const redisClient = redis.createClient(config.redis);
|
20
|
+
|
21
|
+
// check that redis client exists
|
22
|
+
if (!redisClient) {
|
23
|
+
logger.log('error', 'Redis client does not exist!');
|
24
|
+
return next();
|
25
|
+
}
|
26
|
+
|
27
|
+
const closeConnection = async err => {
|
28
|
+
await redisClient.quit();
|
29
|
+
return next(err);
|
30
|
+
};
|
31
|
+
|
32
|
+
try {
|
33
|
+
// fetch records of current user using IP address, returns null when no record is found
|
34
|
+
return await redisClient.get(req.ip, async (err, record) => {
|
35
|
+
if (err) {
|
36
|
+
logger.log('error', `Error with requesting redis session for rate limiting: ${err}`);
|
37
|
+
return await closeConnection();
|
38
|
+
}
|
39
|
+
const currentRequestTime = moment();
|
40
|
+
const windowStartTimestamp = moment().subtract(WINDOW_SIZE_IN_MINUTES, 'minutes').unix();
|
41
|
+
let oldRecord = false;
|
42
|
+
let data;
|
43
|
+
// if no record is found , create a new record for user and store to redis
|
44
|
+
if (record) {
|
45
|
+
data = JSON.parse(record);
|
46
|
+
oldRecord = data[data.length - 1][timestampName] < windowStartTimestamp;
|
47
|
+
}
|
48
|
+
|
49
|
+
if (!record || oldRecord) {
|
50
|
+
const newRecord = [];
|
51
|
+
const requestLog = {
|
52
|
+
[timestampName]: currentRequestTime.unix(),
|
53
|
+
[countName]: 1
|
54
|
+
};
|
55
|
+
newRecord.push(requestLog);
|
56
|
+
await redisClient.set(req.ip, JSON.stringify(newRecord));
|
57
|
+
return await closeConnection();
|
58
|
+
}
|
59
|
+
// if record is found, parse it's value and calculate number of requests users has made within the last window
|
60
|
+
const requestsWithinWindow = data.filter(entry => entry[timestampName] > windowStartTimestamp);
|
61
|
+
|
62
|
+
const totalWindowRequestsCount = requestsWithinWindow.reduce((accumulator, entry) => {
|
63
|
+
return accumulator + entry[countName];
|
64
|
+
}, 0);
|
65
|
+
|
66
|
+
if (!options.rateLimits.env || options.rateLimits.env === 'development') {
|
67
|
+
const requestsRemaining = MAX_WINDOW_REQUEST_COUNT - totalWindowRequestsCount;
|
68
|
+
const msg = `Requests made by client: ${totalWindowRequestsCount}\nRequests remaining: ${requestsRemaining}`;
|
69
|
+
logger.log('info', msg);
|
70
|
+
}
|
71
|
+
// if number of requests made is greater than or equal to the desired maximum, return error
|
72
|
+
if (totalWindowRequestsCount >= MAX_WINDOW_REQUEST_COUNT) {
|
73
|
+
return await closeConnection({ code: ERROR_CODE });
|
74
|
+
}
|
75
|
+
// if number of requests made is less than allowed maximum, log new entry
|
76
|
+
const lastRequestLog = data[data.length - 1];
|
77
|
+
const potentialCurrentWindowIntervalStartTimeStamp = currentRequestTime
|
78
|
+
.subtract(WINDOW_LOG_INTERVAL_IN_MINUTES, 'minutes')
|
79
|
+
.unix();
|
80
|
+
// if interval has not passed since last request log, increment counter
|
81
|
+
if (lastRequestLog[timestampName] > potentialCurrentWindowIntervalStartTimeStamp) {
|
82
|
+
lastRequestLog[countName]++;
|
83
|
+
data[data.length - 1] = lastRequestLog;
|
84
|
+
} else {
|
85
|
+
// if interval has passed, log new entry for current user and timestamp
|
86
|
+
data.push({
|
87
|
+
[timestampName]: currentRequestTime.unix(),
|
88
|
+
[countName]: 1
|
89
|
+
});
|
90
|
+
}
|
91
|
+
await redisClient.set(req.ip, JSON.stringify(data));
|
92
|
+
return await closeConnection();
|
93
|
+
});
|
94
|
+
} catch (err) {
|
95
|
+
return await closeConnection(err);
|
96
|
+
}
|
97
|
+
};
|
98
|
+
};
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "hof",
|
3
3
|
"description": "A bootstrap for HOF projects",
|
4
|
-
"version": "20.0.0-beta.
|
4
|
+
"version": "20.0.0-beta.8",
|
5
5
|
"license": "MIT",
|
6
6
|
"main": "index.js",
|
7
7
|
"author": "HomeOffice",
|
@@ -70,10 +70,9 @@
|
|
70
70
|
"lodash": "^4.17.21",
|
71
71
|
"markdown-it": "^12.3.2",
|
72
72
|
"minimatch": "^3.0.3",
|
73
|
-
"minimist": "^1.2.
|
73
|
+
"minimist": "^1.2.6",
|
74
74
|
"mixwith": "^0.1.1",
|
75
|
-
"
|
76
|
-
"moment": "^2.24.0",
|
75
|
+
"moment": "^2.29.2",
|
77
76
|
"morgan": "^1.10.0",
|
78
77
|
"mustache": "^2.3.0",
|
79
78
|
"nodemailer": "^6.6.3",
|
@@ -88,8 +87,8 @@
|
|
88
87
|
"serve-static": "^1.14.1",
|
89
88
|
"uglify-js": "^3.14.3",
|
90
89
|
"underscore": "^1.12.1",
|
91
|
-
"urijs": "^1.19.
|
92
|
-
"winston": "^3.
|
90
|
+
"urijs": "^1.19.11",
|
91
|
+
"winston": "^3.7.2"
|
93
92
|
},
|
94
93
|
"devDependencies": {
|
95
94
|
"@cucumber/cucumber": "^7.3.0",
|
@@ -100,7 +100,7 @@ module.exports = {
|
|
100
100
|
formatter: ['trim', 'hyphens'],
|
101
101
|
labelClassName: ['govuk-label--l'],
|
102
102
|
// attributes here are passed to the field element
|
103
|
-
validate: ['required', { type: 'maxlength', arguments:
|
103
|
+
validate: ['required', { type: 'maxlength', arguments: 10 }],
|
104
104
|
attributes: [{
|
105
105
|
attribute: 'rows',
|
106
106
|
value: 8
|
package/sandbox/server.js
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
const fs = require('fs');
|
4
4
|
const path = require('path');
|
5
|
-
const mkdir = require('mkdirp').sync;
|
6
5
|
const rm = require('rimraf').sync;
|
7
6
|
|
8
7
|
const debug = require('debug')('hof:transpiler');
|
@@ -16,7 +15,7 @@ module.exports = (dir, data) => {
|
|
16
15
|
const outputDir = path.resolve(dir, '..', lang);
|
17
16
|
rm(outputDir);
|
18
17
|
debug(`Emptied directory ${outputDir}`);
|
19
|
-
|
18
|
+
fs.mkdirSync(outputDir);
|
20
19
|
debug(`Made directory ${outputDir}`);
|
21
20
|
Object.keys(data[lang]).forEach(namespace => {
|
22
21
|
fs.writeFileSync(path.resolve(outputDir, `${namespace}.json`), JSON.stringify(data[lang][namespace], null, ' '));
|
@@ -94,7 +94,7 @@ module.exports = class Helpers {
|
|
94
94
|
/**
|
95
95
|
* utility function which returns undefined on
|
96
96
|
* failed translations instead of returning the key
|
97
|
-
* @param {Function} translate - the translate
|
97
|
+
* @param {Function} translate - the translate function
|
98
98
|
* @param {String} key - the key to translate
|
99
99
|
* @returns {String|undefined} the string result if successful, undefined if not
|
100
100
|
*/
|
@@ -131,4 +131,19 @@ module.exports = class Helpers {
|
|
131
131
|
`pages.${key}.header`
|
132
132
|
]) || key;
|
133
133
|
}
|
134
|
+
/**
|
135
|
+
* utility function which returns true or false on
|
136
|
+
* forks depending on whether a value exists on the page
|
137
|
+
* @param {Object} req - an http request object
|
138
|
+
* @param {Object} res - an http response object
|
139
|
+
* @param {Function|Object} condition - a field condition that is either a function or object
|
140
|
+
* @returns {Boolean} the boolean result of whether a field value is set on the page or session for a fork
|
141
|
+
*/
|
142
|
+
static isFieldValueInPageOrSessionValid(req, res, condition) {
|
143
|
+
return _.isFunction(condition) ?
|
144
|
+
condition(req, res) :
|
145
|
+
condition.value === (req.form.values[condition.field] ||
|
146
|
+
(!Object.keys(req.form.values).includes(condition.field) &&
|
147
|
+
_.get(req, `form.historicalValues[${condition.field}]`)));
|
148
|
+
}
|
134
149
|
};
|
package/wizard/index.js
CHANGED
@@ -69,6 +69,7 @@ const Wizard = (steps, fields, setts) => {
|
|
69
69
|
options.confirmStep = settings.confirmStep;
|
70
70
|
options.clearSession = options.clearSession || false;
|
71
71
|
options.fieldsConfig = _.cloneDeep(fields);
|
72
|
+
options.sanitiseInputs = settings.sanitiseInputs;
|
72
73
|
|
73
74
|
options.defaultFormatters = [].concat(settings.formatters);
|
74
75
|
|