hof 20.0.0-redis-beta.32-redis-beta → 20.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. package/.github/workflows/automate-publish.yml +1 -1
  2. package/.github/workflows/automate-tag.yml +4 -4
  3. package/README.md +6 -0
  4. package/components/notify/notify.js +1 -1
  5. package/config/hof-defaults.js +1 -2
  6. package/config/sanitisation-rules.js +20 -17
  7. package/controller/base-controller.js +5 -3
  8. package/controller/controller.js +5 -0
  9. package/frontend/template-partials/views/layout.html +2 -2
  10. package/frontend/template-partials/views/partials/maincontent-left.html +2 -2
  11. package/frontend/template-partials/views/partials/navigation.html +2 -2
  12. package/frontend/template-partials/views/partials/summary-table-row.html +2 -2
  13. package/frontend/template-partials/views/session-timeout.html +2 -1
  14. package/index.js +1 -1
  15. package/lib/health.js +1 -1
  16. package/lib/sessions.js +1 -1
  17. package/middleware/errors.js +2 -0
  18. package/middleware/rate-limiter.js +1 -3
  19. package/package.json +5 -4
  20. package/sandbox/server.js +1 -1
  21. package/wizard/middleware/check-progress.js +36 -1
  22. package/.nyc_output/cb764db8-e9f0-43bb-b3b1-7af57bb79bb5.json +0 -1
  23. package/.nyc_output/processinfo/cb764db8-e9f0-43bb-b3b1-7af57bb79bb5.json +0 -1
  24. package/.nyc_output/processinfo/index.json +0 -1
  25. package/sandbox/apps/sandbox/translations/en/default.json +0 -224
  26. package/sandbox/public/css/app.css +0 -2794
  27. package/sandbox/public/images/icons/icon-caret-left.png +0 -0
  28. package/sandbox/public/images/icons/icon-complete.png +0 -0
  29. package/sandbox/public/images/icons/icon-cross-remove-sign.png +0 -0
  30. package/sandbox/public/js/bundle.js +0 -32888
@@ -16,7 +16,7 @@ jobs:
16
16
  fetch-depth: 0
17
17
  - uses: actions/setup-node@v1
18
18
  with:
19
- node-version: 14
19
+ node-version: 10
20
20
  registry-url: https://registry.npmjs.org/
21
21
  - name: 'Get Previous tag'
22
22
  id: previoustag
@@ -7,7 +7,7 @@ jobs:
7
7
  runs-on: ubuntu-latest
8
8
  strategy:
9
9
  matrix:
10
- node-version: [14.x]
10
+ node-version: [10.x, 12.x, 14.x]
11
11
  redis-version: [4, 5, 6]
12
12
  steps:
13
13
  - uses: actions/checkout@v2.2.0
@@ -34,7 +34,7 @@ jobs:
34
34
  - uses: actions/checkout@v2.2.0
35
35
  - uses: actions/setup-node@v1
36
36
  with:
37
- node-version: 14
37
+ node-version: 10
38
38
  registry-url: https://registry.npmjs.org/
39
39
  - run: |
40
40
  git config --local user.email "$(git log --format='%ae' HEAD^!)"
@@ -52,7 +52,7 @@ jobs:
52
52
  - uses: actions/checkout@v2.2.0
53
53
  - uses: actions/setup-node@v1
54
54
  with:
55
- node-version: 14
55
+ node-version: 10
56
56
  registry-url: https://registry.npmjs.org/
57
57
  - run: |
58
58
  git config --local user.email "$(git log --format='%ae' HEAD^!)"
@@ -70,7 +70,7 @@ jobs:
70
70
  - uses: actions/checkout@v2.2.0
71
71
  - uses: actions/setup-node@v1
72
72
  with:
73
- node-version: 14
73
+ node-version: 10
74
74
  registry-url: https://registry.npmjs.org/
75
75
  - run: |
76
76
  git config --local user.email "$(git log --format='%ae' HEAD^!)"
package/README.md CHANGED
@@ -1722,3 +1722,9 @@ Additional vendor JavaScript files are included. These are:
1722
1722
  - safari-cachebuster.js
1723
1723
 
1724
1724
  Copy `assets/javascript/vendor` into your javascript directory (ie `hmpo/vendor`) and compile them with your JavaScript.
1725
+
1726
+ ## Journey Header Navigation.html page
1727
+
1728
+ - Navigation.html contains a journeyHeaderURL, which is set in the controller.
1729
+ - getJourneyHeaderURL within the controller translates an empty baseURL to '/'.
1730
+ - The above helps fix broken journey header URLs in the GRO and UKVIC services which both have a baseURL's set to '/'.
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
  const NotifyClient = require('notifications-node-client').NotifyClient;
3
- const {v4: uuidv4} = require('uuid');
3
+ const { v4: uuidv4 } = require('uuid');
4
4
 
5
5
  module.exports = class Notify {
6
6
  constructor(opts) {
@@ -27,8 +27,7 @@ const defaults = {
27
27
  ignoreMiddlewareLogs: ['/healthz'],
28
28
  redis: {
29
29
  port: process.env.REDIS_PORT || '6379',
30
- host: process.env.REDIS_HOST || '127.0.0.1',
31
- legacyMode: true
30
+ host: process.env.REDIS_HOST || '127.0.0.1'
32
31
  },
33
32
  session: {
34
33
  ttl: process.env.SESSION_TTL || 1800,
@@ -3,27 +3,30 @@
3
3
 
4
4
  const sanitisationBlacklistArray = {
5
5
  // Input will be sanitised using the below rules
6
+ // Each one is an array which will run sequentially
7
+ // Some have multiple steps which allow us to remove dups then append the suffix
8
+ // Useful for ensuring we're not re-sanitising the same input multiple times (and appending extra hypens each time)
6
9
  // The key is what we're sanitising out
7
10
  // The regex is the rule we used to find them (note some dictate repeating characters)
8
11
  // And the replace is what we're replacing that pattern with. Usually nothing sometimes a
9
12
  // single character or sometimes a single character followed by a "-"
10
- '/*': { regex: '\/\\*', replace: '-' },
11
- '*/': { regex: '\\*\\/', replace: '-' },
12
- '|': { regex: '\\|', replace: '-' },
13
- '&&': { regex: '&&+', replace: '&' },
14
- '@@': { regex: '@@+', replace: '@' },
15
- '/..;/': { regex: '/\\.\\.;/', replace: '-' }, // Purposely input before ".." as they conflict
16
- // '..': { regex: '\\.\\.+', replace: '.' }, // Agreed to disable this rule for now unless its specifically required
17
- '/etc/passwd': { regex: '\/etc\/passwd', replace: '-' },
18
- 'c:\\': { regex: 'c:\\\\', replace: '-' },
19
- 'cmd.exe': { regex: 'cmd\\.exe', replace: '-' },
20
- '<': { regex: '<', replace: '<-' },
21
- '>': { regex: '>', replace: '>-' },
22
- '[': { regex: '\\[+', replace: '[-' },
23
- ']': { regex: '\\]+', replace: ']-' },
24
- '~': { regex: '~', replace: '~-' },
25
- '&#': { regex: '&#', replace: '-' },
26
- '%U': { regex: '%U', replace: '-' }
13
+ '/*': [{ regex: '\/\\*', replace: '-' }],
14
+ '*/': [{ regex: '\\*\\/', replace: '-' }],
15
+ '|': [{ regex: '\\|', replace: '-' }],
16
+ '&&': [{ regex: '&&+', replace: '&' }],
17
+ '@@': [{ regex: '@@+', replace: '@' }],
18
+ '/..;/': [{ regex: '/\\.\\.;/', replace: '-' }], // Purposely input before ".." as they conflict
19
+ // '..': [{ regex: '\\.\\.+', replace: '.' }], // Agreed to disable this rule for now unless its specifically required
20
+ '/etc/passwd': [{ regex: '\/etc\/passwd', replace: '-' }],
21
+ 'c:\\': [{ regex: 'c:\\\\', replace: '-' }],
22
+ 'cmd.exe': [{ regex: 'cmd\\.exe', replace: '-' }],
23
+ '<': [{ regex: '<+', replace: '<' }, { regex: '<(?!-)', replace: '<-' }],
24
+ '>': [{ regex: '>+', replace: '>' }, { regex: '>(?!-)', replace: '>-' }],
25
+ '[': [{ regex: '\\[+', replace: '[' }, { regex: '\\[(?!-)', replace: '[-' }],
26
+ ']': [{ regex: '\\]+', replace: ']-' }, { regex: '\\](?!-)', replace: ']-' }],
27
+ '~': [{ regex: '~+', replace: '~' }, { regex: '~(?!-)', replace: '~-' }],
28
+ '&#': [{ regex: '&#', replace: '-' }],
29
+ '%U': [{ regex: '%U', replace: '-' }]
27
30
  };
28
31
 
29
32
  module.exports = sanitisationBlacklistArray;
@@ -177,9 +177,11 @@ module.exports = class BaseController extends EventEmitter {
177
177
  // For each property in our form data
178
178
  Object.keys(sanitisationBlacklistArray).forEach(function (blacklisted, blacklistedIndex) {
179
179
  const blacklistedDetail = sanitisationBlacklistArray[blacklisted];
180
- const regexQuery = new RegExp(blacklistedDetail.regex, 'gi');
181
- // Will perform the required replace based on our passed in regex and the replace string
182
- req.form.values[property] = req.form.values[property].replace(regexQuery, blacklistedDetail.replace);
180
+ blacklistedDetail.forEach(step => {
181
+ const regexQuery = new RegExp(step.regex, 'gi');
182
+ // Will perform the required replace based on our passed in regex and the replace string
183
+ req.form.values[property] = req.form.values[property].replace(regexQuery, step.replace);
184
+ });
183
185
  });
184
186
  }
185
187
  });
@@ -113,6 +113,7 @@ module.exports = class Controller extends BaseController {
113
113
  baseUrl: req.baseUrl,
114
114
  skipToMain: this.getFirstFormItem(req.form.options.fields),
115
115
  title: this.getTitle(route, lookup, req.form.options.fields, res.locals),
116
+ journeyHeaderURL: this.getJourneyHeaderURL(req.baseUrl),
116
117
  header: this.getHeader(route, lookup, res.locals),
117
118
  captionHeading: this.getCaptionHeading(route, lookup, res.locals),
118
119
  warning: this.getWarning(route, lookup, res.locals),
@@ -124,6 +125,10 @@ module.exports = class Controller extends BaseController {
124
125
  }, stepLocals);
125
126
  }
126
127
 
128
+ getJourneyHeaderURL(url) {
129
+ return url === '' ? '/' : url;
130
+ }
131
+
127
132
  getFirstFormItem(fields) {
128
133
  let firstFieldKey;
129
134
  if (_.size(fields)) {
@@ -44,9 +44,9 @@
44
44
  {{/gaTagId}}
45
45
  {{/cookieMessage}}
46
46
  {{$footerSupportLinks}}
47
- <ul>
47
+ <ul class="govuk-footer__inline-list">
48
48
  {{#footerSupportLinks}}
49
- <li class="govuk-footer__inline-list-item"><a href="{{path}}">{{#t}}{{property}}{{/t}}</a></li>
49
+ <li class="govuk-footer__inline-list-item"><a class="govuk-footer__link" href="{{path}}">{{#t}}{{property}}{{/t}}</a></li>
50
50
  {{/footerSupportLinks}}
51
51
  {{^footerSupportLinks}}
52
52
  <li class="govuk-footer__inline-list-item"><a class="govuk-footer__link" href="/cookies">{{#t}}base.cookies{{/t}}</a></li>
@@ -3,8 +3,8 @@
3
3
  <div class="govuk-grid-column-two-thirds">
4
4
  {{$validationSummary}}{{/validationSummary}}
5
5
  {{#captionHeading}}<span class="govuk-caption-l">{{captionHeading}}</span>{{/captionHeading}}
6
- {{#header}}<h1 class="govuk-heading-xl">{{header}}</h1>{{/header}}
7
- {{#subHeading}}<div id="page-header" class="govuk-heading-l">{{subHeading}}</div>{{/subHeading}}
6
+ {{#header}}<h1 class="govuk-heading-l">{{header}}</h1>{{/header}}
7
+ {{#subHeading}}<div id="page-header"><h2 class="govuk-heading-m">{{subHeading}}</h2></div>{{/subHeading}}
8
8
  {{$content}}{{/content}}
9
9
  </div>
10
10
  {{/content-outer}}
@@ -1,8 +1,8 @@
1
1
  <div class="govuk-header__content">
2
2
  {{#startPageRedirectUrl}}
3
- <a href="{{startPageRedirectUrl}}" class="govuk-header__link govuk-header__link--service-name" id="proposition-name">{{$journeyHeader}}{{/journeyHeader}}</a>
3
+ <a href="{{startPageRedirectUrl}}" class="govuk-header__link govuk-header__link--service-name" id="proposition-name">{{$journeyHeader}}{{/journeyHeader}}</a>
4
4
  {{/startPageRedirectUrl}}
5
5
  {{^startPageRedirectUrl}}
6
- <a href="{{baseUrl}}" class="govuk-header__link govuk-header__link--service-name" id="proposition-name">{{$journeyHeader}}{{/journeyHeader}}</a>
6
+ <a href="{{journeyHeaderURL}}" class="govuk-header__link govuk-header__link--service-name" id="proposition-name">{{$journeyHeader}}{{/journeyHeader}}</a>
7
7
  {{/startPageRedirectUrl}}
8
8
  </div>
@@ -5,10 +5,10 @@
5
5
  <dt class="confirm-label">{{label}}</dt>
6
6
  <dd class="confirm-value" data-value="{{value}}">{{value}}</dd>
7
7
  {{#changeLink}}
8
- <dd><span class="link"><a href="{{changeLink}}" id="{{field}}-change-{{index}}">{{#t}}buttons.change{{/t}} <span class="visuallyhidden">{{changeLinkDescription}} from {{value}}</span></a></span></dd>
8
+ <dd><span><a class="govuk-link" href="{{changeLink}}" id="{{field}}-change-{{index}}">{{#t}}buttons.change{{/t}} <span class="visuallyhidden">{{changeLinkDescription}} from {{value}}</span></a></span></dd>
9
9
  {{/changeLink}}
10
10
  {{^changeLink}}
11
- <dd><span class="link"><a href="{{baseUrl}}{{step}}/edit#{{field}}" id="{{field}}-change">{{#t}}buttons.change{{/t}} <span class="visuallyhidden">{{changeLinkDescription}} from {{value}}</span></a></span></dd>
11
+ <dd><span><a class="govuk-link" href="{{baseUrl}}{{step}}/edit#{{field}}" id="{{field}}-change">{{#t}}buttons.change{{/t}} <span class="visuallyhidden">{{changeLinkDescription}} from {{value}}</span></a></span></dd>
12
12
  {{/changeLink}}
13
13
  {{/isSeparator}}
14
14
  </div>
@@ -1,6 +1,7 @@
1
1
  {{<error}}
2
2
  {{$content}}
3
- <p>{{{content.message}}}</p>
3
+ <h1 class="govuk-heading-l">{{{content.title}}}</h1>
4
+ <h2 class="govuk-heading-m">{{{content.message}}}</h2>
4
5
  <a href="{{startLink}}" class="govuk-button" role="button">{{#t}}buttons.start-again{{/t}}</a>
5
6
  {{/content}}
6
7
  {{/error}}
package/index.js CHANGED
@@ -79,7 +79,7 @@ const getContentSecurityPolicy = (config, res) => {
79
79
  fontSrc: ['fonts.gstatic.com '],
80
80
  scriptSrc: ['www.google-analytics.com', 'ssl.google-analytics.com'],
81
81
  imgSrc: ['www.google-analytics.com', 'ssl.gstatic.com'],
82
- connectSrc: ['www.google-analytics.com']
82
+ connectSrc: ['https://www.google-analytics.com', 'https://region1.google-analytics.com']
83
83
  };
84
84
 
85
85
  if (config.gaTagId) {
package/lib/health.js CHANGED
@@ -15,7 +15,7 @@ module.exports = redis => {
15
15
 
16
16
  router.get('/readiness', healthCheck({
17
17
  test: () => {
18
- if (!redis.isOpen && !redis.isReady) {
18
+ if (!redis.connected && !redis.ready) {
19
19
  return new Error('Session store unhealthy');
20
20
  }
21
21
  return 0;
package/lib/sessions.js CHANGED
@@ -50,7 +50,7 @@ module.exports = (app, config) => {
50
50
  logger.error(e);
51
51
  });
52
52
  }
53
- client.connect();
53
+
54
54
  const store = new RedisStore({
55
55
  client: client,
56
56
  ttl: config.session.ttl,
@@ -12,6 +12,8 @@ const getContent = (err, translate) => {
12
12
  if (err.code === 'SESSION_TIMEOUT') {
13
13
  err.status = 401;
14
14
  err.template = 'session-timeout';
15
+ err.title = (translate && translate('errors.session.title'));
16
+ err.message = (translate && translate('errors.session.message'));
15
17
  content.title = (translate && translate('errors.session.title'));
16
18
  content.message = (translate && translate('errors.session.message'));
17
19
  }
@@ -25,11 +25,9 @@ module.exports = (options, rateLimitType) => {
25
25
  }
26
26
 
27
27
  const closeConnection = async err => {
28
- await redisClient.v4.QUIT();
28
+ await redisClient.quit();
29
29
  return next(err);
30
30
  };
31
- redisClient.on('error', err => logger.log('error', err));
32
- await redisClient.connect();
33
31
 
34
32
  try {
35
33
  // fetch records of current user using IP address, returns null when no record is found
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-redis-beta.32-redis-beta",
4
+ "version": "20.0.1",
5
5
  "license": "MIT",
6
6
  "main": "index.js",
7
7
  "author": "HomeOffice",
@@ -42,7 +42,7 @@
42
42
  "callsite": "^1.0.0",
43
43
  "chalk": "^2.0.0",
44
44
  "chokidar": "^3.4.0",
45
- "connect-redis": "^6.1.3",
45
+ "connect-redis": "^5.2.0",
46
46
  "cookie-parser": "^1.4.6",
47
47
  "cp": "^0.2.0",
48
48
  "csrf": "^3.0.2",
@@ -72,7 +72,7 @@
72
72
  "minimatch": "^3.0.3",
73
73
  "minimist": "^1.2.6",
74
74
  "mixwith": "^0.1.1",
75
- "moment": "^2.29.2",
75
+ "moment": "^2.29.4",
76
76
  "morgan": "^1.10.0",
77
77
  "mustache": "^2.3.0",
78
78
  "nodemailer": "^6.6.3",
@@ -80,7 +80,7 @@
80
80
  "nodemailer-smtp-transport": "^2.7.4",
81
81
  "nodemailer-stub-transport": "^1.1.0",
82
82
  "notifications-node-client": "^5.1.1",
83
- "redis": "^4.3.1",
83
+ "redis": "^3.1.2",
84
84
  "reqres": "^3.0.1",
85
85
  "request": "^2.79.0",
86
86
  "rimraf": "^3.0.2",
@@ -89,6 +89,7 @@
89
89
  "uglify-js": "^3.14.3",
90
90
  "underscore": "^1.12.1",
91
91
  "urijs": "^1.19.11",
92
+ "uuid": "^8.3.2",
92
93
  "winston": "^3.7.2"
93
94
  },
94
95
  "devDependencies": {
package/sandbox/server.js CHANGED
@@ -14,5 +14,5 @@ bootstrap({
14
14
  }
15
15
  },
16
16
  getAccessibility: true,
17
- "port": 8080
17
+ "port": 8082
18
18
  });
@@ -28,6 +28,41 @@ module.exports = (route, controller, steps, begin) => {
28
28
  return _.uniq(allSteps);
29
29
  };
30
30
 
31
+ // Gets all the possible routes
32
+ const walkAllPossibleSteps = (stepName, allStepsInService, allVisitedSteps) => {
33
+ const stepIncludingFieldsAndForks = allStepsInService[stepName];
34
+ if (!stepIncludingFieldsAndForks) { return; }
35
+
36
+ const forkTargets = _.map(stepIncludingFieldsAndForks.forks, 'target') || [];
37
+ const nextStep = stepIncludingFieldsAndForks.next || '';
38
+
39
+ // If there is no next step or fork, then we have reached the end of the journey
40
+ if (!forkTargets.length && !nextStep) {
41
+ allVisitedSteps.push(stepName);
42
+ // eslint-disable-next-line consistent-return
43
+ return _.uniq(allVisitedSteps);
44
+ }
45
+
46
+ const nextStepsAndForks = _.uniq(forkTargets.concat(nextStep));
47
+
48
+ // We need to transverse through all 'Next' and 'Forks' for this route
49
+ nextStepsAndForks.map(each => {
50
+ if (!allVisitedSteps.includes(each)) {
51
+ allVisitedSteps.push(each);
52
+ walkAllPossibleSteps(each, allStepsInService, allVisitedSteps);
53
+ }
54
+ });
55
+ };
56
+
57
+ // Create a list of all visited steps
58
+ const createAllVisitedSteps = (stepName, allStepsInService) => {
59
+ const allVisitedSteps = [stepName];
60
+ if (allStepsInService[stepName]) {
61
+ walkAllPossibleSteps(stepName, allStepsInService, allVisitedSteps);
62
+ }
63
+ return _.uniq(allVisitedSteps);
64
+ };
65
+
31
66
  const invalidateStep = (stepName, scopedSteps, sessionModel) => {
32
67
  debug('Invalidating', stepName);
33
68
  const step = scopedSteps[stepName] || {};
@@ -55,7 +90,7 @@ module.exports = (route, controller, steps, begin) => {
55
90
  debug('next', nextStep);
56
91
  debug('potential paths', potentialPaths);
57
92
  // if we're following a loop then allow the loop to be invalidated
58
- const whitelist = isLoop(nextStep, req.path) ? [route] : getAllPossibleSteps(nextStep, steps);
93
+ const whitelist = isLoop(nextStep, req.path) ? [route] : createAllVisitedSteps(nextStep, steps);
59
94
  // aggregate all potential journeys from the invalidating step
60
95
  const invalidateSteps = potentialPaths.reduce((arr, step) => arr.concat(getAllPossibleSteps(step, steps)), []);
61
96