hof 20.0.0-beta.1 → 20.0.0-beta.12

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.
Files changed (58) hide show
  1. package/.github/workflows/automate-publish.yml +1 -1
  2. package/.github/workflows/automate-tag.yml +1 -1
  3. package/.nyc_output/e2fdc3eb-4fd2-47e0-a392-fe5f665776a4.json +1 -0
  4. package/.nyc_output/processinfo/e2fdc3eb-4fd2-47e0-a392-fe5f665776a4.json +1 -0
  5. package/.nyc_output/processinfo/index.json +1 -1
  6. package/build/lib/mkdir.js +2 -2
  7. package/components/date/index.js +37 -24
  8. package/components/date/templates/date.html +3 -3
  9. package/components/emailer/index.js +49 -41
  10. package/components/emailer/transports/debug.js +1 -2
  11. package/components/summary/index.js +18 -0
  12. package/config/hof-defaults.js +5 -3
  13. package/config/rate-limits.js +20 -0
  14. package/config/sanitisation-rules.js +29 -0
  15. package/controller/base-controller.js +26 -8
  16. package/controller/controller.js +11 -15
  17. package/frontend/govuk-template/build/config.js +1 -1
  18. package/frontend/template-mixins/mixins/template-mixins.js +8 -8
  19. package/frontend/template-mixins/partials/forms/checkbox-group.html +3 -3
  20. package/frontend/template-mixins/partials/forms/input-text-group.html +3 -3
  21. package/frontend/template-mixins/partials/forms/option-group.html +3 -3
  22. package/frontend/template-mixins/partials/forms/select.html +2 -2
  23. package/frontend/template-mixins/partials/forms/textarea-group.html +2 -2
  24. package/frontend/template-mixins/partials/mixins/panel.html +3 -4
  25. package/frontend/template-partials/translations/src/en/errors.json +12 -0
  26. package/frontend/template-partials/views/partials/form.html +2 -1
  27. package/frontend/template-partials/views/rate-limit-error.html +10 -0
  28. package/frontend/themes/gov-uk/client-js/govuk-cookies.js +43 -44
  29. package/frontend/themes/gov-uk/client-js/index.js +2 -2
  30. package/frontend/themes/gov-uk/client-js/skip-to-main.js +18 -17
  31. package/frontend/themes/gov-uk/styles/_cookie-banner.scss +51 -1
  32. package/frontend/themes/gov-uk/styles/govuk.scss +5 -0
  33. package/frontend/toolkit/assets/javascript/form-focus.js +10 -1
  34. package/frontend/toolkit/assets/javascript/validation.js +6 -1
  35. package/index.js +9 -4
  36. package/lib/router.js +2 -1
  37. package/lib/settings.js +18 -2
  38. package/middleware/errors.js +32 -0
  39. package/middleware/index.js +2 -1
  40. package/middleware/rate-limiter.js +98 -0
  41. package/package.json +5 -6
  42. package/sandbox/.env +1 -1
  43. package/sandbox/apps/sandbox/fields.js +14 -9
  44. package/sandbox/apps/sandbox/index.js +1 -5
  45. package/sandbox/apps/sandbox/translations/en/default.json +4 -13
  46. package/sandbox/apps/sandbox/translations/src/en/fields.json +3 -2
  47. package/sandbox/apps/sandbox/translations/src/en/pages.json +2 -12
  48. package/sandbox/assets/scss/app.scss +0 -55
  49. package/sandbox/package.json +1 -0
  50. package/sandbox/public/css/app.css +4898 -4908
  51. package/sandbox/public/js/bundle.js +79 -65
  52. package/sandbox/server.js +7 -1
  53. package/sandbox/yarn.lock +20 -1
  54. package/transpiler/lib/write-files.js +1 -2
  55. package/utilities/helpers/index.js +16 -1
  56. package/wizard/index.js +1 -0
  57. package/.nyc_output/65af88d9-aebe-4d1b-a21d-6fbf7f2bbda4.json +0 -1
  58. package/.nyc_output/processinfo/65af88d9-aebe-4d1b-a21d-6fbf7f2bbda4.json +0 -1
@@ -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.1",
4
+ "version": "20.0.0-beta.12",
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.0",
73
+ "minimist": "^1.2.6",
74
74
  "mixwith": "^0.1.1",
75
- "mkdirp": "^0.5.1",
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.10",
92
- "winston": "^3.3.3"
90
+ "urijs": "^1.19.11",
91
+ "winston": "^3.7.2"
93
92
  },
94
93
  "devDependencies": {
95
94
  "@cucumber/cucumber": "^7.3.0",
package/sandbox/.env CHANGED
@@ -1 +1 @@
1
- GA_TAG=UA-215558609-1
1
+ // GA_TAG=UA-215558609-1
@@ -8,19 +8,20 @@ module.exports = {
8
8
  'landing-page-radio': {
9
9
  mixin: 'radio-group',
10
10
  validate: ['required'],
11
- legend: {
12
- className: 'visuallyhidden'
13
- },
11
+ isPageHeading: true,
12
+ // Design system says to avoid in-line unless it's two options,
13
+ // so just added as an example below but by default it isn't
14
14
  className: ['govuk-radios--inline'],
15
15
  options: ['basic-form', 'complex-form', 'build-your-own-form']
16
16
  },
17
17
  name: {
18
18
  validate: ['required', 'notUrl', { type: 'maxlength', arguments: 200 }],
19
+ // need to remove this for the heading to go
19
20
  labelClassName: ['govuk-label--l'],
20
- // noHeading: 'true'
21
+ isPageHeading: 'true'
21
22
  },
22
23
  'dateOfBirth': dateComponent('dateOfBirth', {
23
- controlType: 'date-input',
24
+ isPageHeading: 'true',
24
25
  validate: [
25
26
  'required',
26
27
  'date',
@@ -45,6 +46,7 @@ module.exports = {
45
46
  formatter: ['removespaces', 'uppercase']
46
47
  },
47
48
  incomeTypes: {
49
+ isPageHeading: 'true',
48
50
  mixin: 'checkbox-group',
49
51
  labelClassName: 'visuallyhidden',
50
52
  validate: ['required'],
@@ -57,11 +59,9 @@ module.exports = {
57
59
  ]
58
60
  },
59
61
  countryOfHearing: {
62
+ isPageHeading: 'true',
60
63
  mixin: 'radio-group',
61
64
  validate: ['required'],
62
- legend: {
63
- className: 'visuallyhidden'
64
- },
65
65
  options: [
66
66
  'englandAndWales',
67
67
  'scotland',
@@ -69,6 +69,8 @@ module.exports = {
69
69
  ]
70
70
  },
71
71
  email: {
72
+ isPageHeading: 'true',
73
+ labelClassName: ['govuk-label--l'],
72
74
  validate: ['required', 'email']
73
75
  },
74
76
  phone: {
@@ -80,6 +82,7 @@ module.exports = {
80
82
  },
81
83
  countrySelect: {
82
84
  mixin: 'select',
85
+ isPageHeading: 'true',
83
86
  className: ['typeahead'],
84
87
  options:[''].concat(require('homeoffice-countries').allCountries),
85
88
  legend: {
@@ -91,12 +94,13 @@ module.exports = {
91
94
  mixin: 'textarea',
92
95
  // we want to ignore default formatters as we want
93
96
  // to preserve white space
97
+ isPageHeading: 'true',
94
98
  'ignore-defaults': true,
95
99
  // apply the other default formatters
96
100
  formatter: ['trim', 'hyphens'],
97
101
  labelClassName: ['govuk-label--l'],
98
102
  // attributes here are passed to the field element
99
- validate: ['required', { type: 'maxlength', arguments: 100 }],
103
+ validate: ['required', { type: 'maxlength', arguments: 10 }],
100
104
  attributes: [{
101
105
  attribute: 'rows',
102
106
  value: 8
@@ -104,6 +108,7 @@ module.exports = {
104
108
  },
105
109
  appealStages: {
106
110
  mixin: 'select',
111
+ isPageHeading: 'true',
107
112
  validate: ['required'],
108
113
  options: [{
109
114
  value: '',
@@ -30,11 +30,7 @@ module.exports = {
30
30
  },
31
31
  '/dob': {
32
32
  fields: ['dateOfBirth'],
33
- next: '/address',
34
- locals: {
35
- step: 'dob',
36
- labelClassName: 'govuk-input'
37
- }
33
+ next: '/address'
38
34
  },
39
35
  '/address': {
40
36
  fields: ['building', 'street', 'townOrCity', 'postcode'],
@@ -2,6 +2,7 @@
2
2
  "fields": {
3
3
  "landing-page-radio": {
4
4
  "legend": "Which form would you like to explore?",
5
+ "hint": "Choose one of the options below and press continue.",
5
6
  "options": {
6
7
  "basic-form": {
7
8
  "label": "Basic form"
@@ -56,7 +57,7 @@
56
57
  }
57
58
  },
58
59
  "countryOfHearing": {
59
- "label": "Country of hearing",
60
+ "legend": "What country was the appeal lodged?",
60
61
  "options": {
61
62
  "englandAndWales": {
62
63
  "label": "England and Wales"
@@ -70,7 +71,7 @@
70
71
  }
71
72
  },
72
73
  "email": {
73
- "label": "Email address"
74
+ "label": "Enter your email address"
74
75
  },
75
76
  "phone": {
76
77
  "label": "Phone number",
@@ -103,8 +104,7 @@
103
104
  },
104
105
  "pages": {
105
106
  "landing-page": {
106
- "header": "Landing page",
107
- "intro": "Choose one of the options below and press continue."
107
+ "header": "Landing page"
108
108
  },
109
109
  "build-your-own-form": {
110
110
  "title": "Build your own form",
@@ -114,15 +114,6 @@
114
114
  "header": "What is your address in the UK?",
115
115
  "intro": "If you have no fixed address, enter an address where we can contact you."
116
116
  },
117
- "checkboxes": {
118
- "header": "Where does your money come from each month?"
119
- },
120
- "radio": {
121
- "header": "What country was the appeal lodged?"
122
- },
123
- "email": {
124
- "header": "Enter your email address"
125
- },
126
117
  "phone-number": {
127
118
  "header": "Enter your phone number"
128
119
  },
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "landing-page-radio": {
3
3
  "legend": "Which form would you like to explore?",
4
+ "hint": "Choose one of the options below and press continue.",
4
5
  "options": {
5
6
  "basic-form": {
6
7
  "label": "Basic form"
@@ -55,7 +56,7 @@
55
56
  }
56
57
  },
57
58
  "countryOfHearing": {
58
- "label": "Country of hearing",
59
+ "legend": "What country was the appeal lodged?",
59
60
  "options": {
60
61
  "englandAndWales": {
61
62
  "label": "England and Wales"
@@ -69,7 +70,7 @@
69
70
  }
70
71
  },
71
72
  "email" : {
72
- "label": "Email address"
73
+ "label": "Enter your email address"
73
74
  },
74
75
  "phone": {
75
76
  "label": "Phone number",
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "landing-page": {
3
- "header": "Landing page",
4
- "intro": "Choose one of the options below and press continue."
3
+ "header": "Landing page"
5
4
  },
6
5
  "build-your-own-form": {
7
6
  "title": "Build your own form",
@@ -11,15 +10,6 @@
11
10
  "header": "What is your address in the UK?",
12
11
  "intro": "If you have no fixed address, enter an address where we can contact you."
13
12
  },
14
- "checkboxes": {
15
- "header": "Where does your money come from each month?"
16
- },
17
- "radio": {
18
- "header": "What country was the appeal lodged?"
19
- },
20
- "email": {
21
- "header": "Enter your email address"
22
- },
23
13
  "phone-number": {
24
14
  "header": "Enter your phone number"
25
15
  },
@@ -55,4 +45,4 @@
55
45
  "subheader": "What happens next",
56
46
  "content": "We’ll contact you with the decision of your application or if we need more information from you."
57
47
  }
58
- }
48
+ }
@@ -26,58 +26,3 @@
26
26
  .twitter-typeahead {
27
27
  width: 100%;
28
28
  }
29
-
30
- // govuk cookie banner styling
31
- .govuk-banner--success {
32
- border-color: #00703c;
33
- color: #00703c;
34
- }
35
-
36
- .govuk-banner {
37
- border: 5px solid #1d70b8;
38
- font-size: 0;
39
- margin-bottom: 30px;
40
- padding: 10px;
41
- }
42
-
43
- .govuk-banner__icon{
44
- display: inline-block;
45
- }
46
-
47
- .govuk-banner__message {
48
- font-family: "GDS Transport", Arial, sans-serif;
49
- -webkit-font-smoothing: antialiased;
50
- font-weight: 400;
51
- font-size: 1rem;
52
- line-height: 1.25;
53
- color: #0b0c0c;
54
- display: block;
55
- overflow: hidden;
56
- display: inline-block;
57
- margin-left: 10px;
58
- }
59
-
60
- .govuk-banner__assistive {
61
- position: absolute !important;
62
- width: 1px !important;
63
- height: 1px !important;
64
- margin: 0 !important;
65
- padding: 0 !important;
66
- overflow: hidden !important;
67
- clip: rect(0 0 0 0) !important;
68
- clip-path: inset(50%) !important;
69
- border: 0 !important;
70
- white-space: nowrap !important;
71
- }
72
-
73
- .cookie-table-holder > table > tbody > tr > td:first-child{
74
- font-weight:bold;
75
- }
76
-
77
- .js-enabled #global-cookie-message {
78
- display: none;
79
- }
80
-
81
- #cookie-banner {
82
- max-width: none;
83
- }
@@ -16,6 +16,7 @@
16
16
  "author": "",
17
17
  "dependencies": {
18
18
  "jquery": "^3.6.0",
19
+ "sass": "^1.53.0",
19
20
  "typeahead-aria": "^1.0.4"
20
21
  },
21
22
  "devDependencies": {