hof 22.11.0 → 22.12.0-beta.0
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 +20 -0
- package/config/hof-defaults.js +1 -1
- package/lib/encryption.js +43 -17
- package/lib/sessions.js +5 -2
- package/lib/settings.js +1 -1
- package/package.json +2 -3
- package/sandbox/apps/sandbox/translations/en/default.json +0 -245
- package/sandbox/public/js/bundle.js +0 -36720
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## 2025-11-20, Version 22.12.1 (Stable), @dk4g @jamiecarterHO
|
|
2
|
+
|
|
3
|
+
### Infrastructure
|
|
4
|
+
- Updated CI/CD pipeline to test against Node.js 20.x, 22.x, and 24.x
|
|
5
|
+
- Updated Redis testing versions to 7 and 8
|
|
6
|
+
- Added `NODE_VERSION` environment variable for consistent Node.js version across jobs
|
|
7
|
+
- Updated release process to use Node.js 24 for tagging and publishing operations
|
|
8
|
+
|
|
9
|
+
### Security
|
|
10
|
+
- Replaced deprecated `crypto.createCipher`/`crypto.createDecipher` with `crypto.createCipheriv`/`crypto.createDecipheriv`
|
|
11
|
+
- Added proper initialisation vector (IV) handling for enhanced security
|
|
12
|
+
- Enforced 32-byte session secret requirement for AES-256 encryption compatibility
|
|
13
|
+
- Removed insecure default session secret ('changethis') - now requires explicit configuration
|
|
14
|
+
|
|
15
|
+
### Migration Notes
|
|
16
|
+
- **Session Reset Required**: Due to enhanced encryption security, existing user sessions will be invalidated and users will need to re-authenticate after this update
|
|
17
|
+
- **Session Secret**: You must now set a unique `SESSION_SECRET` environment variable of exactly 32 bytes for encryption compatibility.
|
|
18
|
+
For testing purposes, you can use the following command to generate a random value. For production environments, consult a security expert or refer to official cryptographic guidelines to generate a secure secret
|
|
19
|
+
`node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"`
|
|
20
|
+
|
|
1
21
|
## 2025-11-15, Version 22.11.0 (Stable), @Rhodine-orleans-lindsay
|
|
2
22
|
|
|
3
23
|
### Changed
|
package/config/hof-defaults.js
CHANGED
package/lib/encryption.js
CHANGED
|
@@ -1,23 +1,49 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
1
|
'use strict';
|
|
3
2
|
|
|
4
|
-
const crypto = require('crypto');
|
|
3
|
+
const crypto = require('node:crypto');
|
|
5
4
|
const algorithm = 'aes-256-cbc';
|
|
5
|
+
const ivLength = 16;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return dec;
|
|
7
|
+
/**
|
|
8
|
+
* Creates an encryption utility with AES-256-CBC algorithm.
|
|
9
|
+
* Provides encrypt and decrypt methods that use a random IV for each encryption operation.
|
|
10
|
+
*
|
|
11
|
+
* @module encryption
|
|
12
|
+
* @param {string|Buffer} secret - Must be exactly 32 bytes
|
|
13
|
+
* @returns {Object} Encryption utility object
|
|
14
|
+
* @throws {Error} If secret is not exactly 32 bytes
|
|
15
|
+
*/
|
|
16
|
+
module.exports = secret => {
|
|
17
|
+
const encryptionKey = Buffer.from(secret, 'utf8');
|
|
18
|
+
if (encryptionKey.byteLength !== 32) {
|
|
19
|
+
throw new Error(`Encryption secret must be exactly 32 bytes. Provided: ${encryptionKey.byteLength} bytes.`);
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
return {
|
|
23
|
+
encrypt: text => {
|
|
24
|
+
try {
|
|
25
|
+
const iv = crypto.randomBytes(ivLength);
|
|
26
|
+
const cipher = crypto.createCipheriv(algorithm, encryptionKey, iv);
|
|
27
|
+
let encrypted = cipher.update(text, 'utf8');
|
|
28
|
+
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
|
29
|
+
return iv.toString('hex') + ':' + encrypted.toString('hex');
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw new Error(`Encryption failed: ${error.message}`);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
decrypt: text => {
|
|
36
|
+
try {
|
|
37
|
+
const textParts = text.split(':');
|
|
38
|
+
const iv = Buffer.from(textParts.shift(), 'hex');
|
|
39
|
+
const encryptedText = Buffer.from(textParts.join(':'), 'hex');
|
|
40
|
+
const decipher = crypto.createDecipheriv(algorithm, encryptionKey, iv);
|
|
41
|
+
let decrypted = decipher.update(encryptedText);
|
|
42
|
+
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
43
|
+
return decrypted.toString('utf8');
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new Error(`Decryption failed: ${error.message}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
};
|
package/lib/sessions.js
CHANGED
|
@@ -10,8 +10,11 @@ const secureHttps = config => config.protocol === 'https' || config.env === 'pro
|
|
|
10
10
|
module.exports = (app, config) => {
|
|
11
11
|
const logger = config.logger || console;
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const secretBuffer = Buffer.from(config.session.secret, 'utf8');
|
|
14
|
+
if (secretBuffer.byteLength !== 32) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Session secret must be exactly 32 bytes. Current: ${secretBuffer.byteLength} bytes.`
|
|
17
|
+
);
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
app.use(cookieParser(config.session.secret, {
|
package/lib/settings.js
CHANGED
|
@@ -42,7 +42,7 @@ module.exports = async (app, config) => {
|
|
|
42
42
|
viewsArray.slice().reverse().forEach(view => {
|
|
43
43
|
const customViewPath = path.resolve(config.root, view);
|
|
44
44
|
try {
|
|
45
|
-
fs.accessSync(customViewPath, fs.F_OK);
|
|
45
|
+
fs.accessSync(customViewPath, fs.constants.F_OK);
|
|
46
46
|
} catch (err) {
|
|
47
47
|
throw new Error(`Cannot find views at ${customViewPath}`);
|
|
48
48
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hof",
|
|
3
3
|
"description": "A bootstrap for HOF projects",
|
|
4
|
-
"version": "22.
|
|
4
|
+
"version": "22.12.0-beta.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"author": "HomeOffice",
|
|
8
8
|
"engines": {
|
|
9
|
-
"node": ">=
|
|
10
|
-
"npm": ">=6.14.0"
|
|
9
|
+
"node": ">=14.0.0"
|
|
11
10
|
},
|
|
12
11
|
"bin": {
|
|
13
12
|
"hof-build": "./bin/hof-build",
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"errors": {
|
|
3
|
-
"service-unavailable": {
|
|
4
|
-
"contact": "You can email for more information"
|
|
5
|
-
}
|
|
6
|
-
},
|
|
7
|
-
"exit": {
|
|
8
|
-
"header": "You have left this form",
|
|
9
|
-
"title": "You have left this form"
|
|
10
|
-
},
|
|
11
|
-
"fields": {
|
|
12
|
-
"landing-page-radio": {
|
|
13
|
-
"legend": "Which form would you like to explore?",
|
|
14
|
-
"hint": "Choose one of the options below and press continue.",
|
|
15
|
-
"options": {
|
|
16
|
-
"basic-form": {
|
|
17
|
-
"label": "Basic form"
|
|
18
|
-
},
|
|
19
|
-
"complex-form": {
|
|
20
|
-
"label": "Complex form"
|
|
21
|
-
},
|
|
22
|
-
"build-your-own-form": {
|
|
23
|
-
"label": "Build your own form"
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
"name": {
|
|
28
|
-
"label": "Full name"
|
|
29
|
-
},
|
|
30
|
-
"dateOfBirth": {
|
|
31
|
-
"legend": "What is your date of birth?",
|
|
32
|
-
"hint": "For example, 31 10 1990"
|
|
33
|
-
},
|
|
34
|
-
"building": {
|
|
35
|
-
"label": "Building and street"
|
|
36
|
-
},
|
|
37
|
-
"street": {
|
|
38
|
-
"label": "Address line 2"
|
|
39
|
-
},
|
|
40
|
-
"townOrCity": {
|
|
41
|
-
"label": "Town or city"
|
|
42
|
-
},
|
|
43
|
-
"postcode": {
|
|
44
|
-
"label": "Postcode"
|
|
45
|
-
},
|
|
46
|
-
"incomeTypes": {
|
|
47
|
-
"label": "Sources of income",
|
|
48
|
-
"legend": "Select the options where you receive income from",
|
|
49
|
-
"hint": "Select all options that apply to you.",
|
|
50
|
-
"options": {
|
|
51
|
-
"salary": {
|
|
52
|
-
"label": "Salary"
|
|
53
|
-
},
|
|
54
|
-
"universal_credit": {
|
|
55
|
-
"label": "Universal Credit"
|
|
56
|
-
},
|
|
57
|
-
"child_benefit": {
|
|
58
|
-
"label": "Child Benefit"
|
|
59
|
-
},
|
|
60
|
-
"housing_benefit": {
|
|
61
|
-
"label": "Housing Benefit"
|
|
62
|
-
},
|
|
63
|
-
"other": {
|
|
64
|
-
"label": "Other"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
"countryOfHearing": {
|
|
69
|
-
"legend": "What country was the appeal lodged?",
|
|
70
|
-
"options": {
|
|
71
|
-
"englandAndWales": {
|
|
72
|
-
"label": "England and Wales"
|
|
73
|
-
},
|
|
74
|
-
"scotland": {
|
|
75
|
-
"label": "Scotland"
|
|
76
|
-
},
|
|
77
|
-
"northernIreland": {
|
|
78
|
-
"label": "Northern Ireland"
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
"email": {
|
|
83
|
-
"label": "Email address"
|
|
84
|
-
},
|
|
85
|
-
"phone": {
|
|
86
|
-
"label": "Phone number",
|
|
87
|
-
"hint": "International phone numbers require the international dialling code, for example +33235066182"
|
|
88
|
-
},
|
|
89
|
-
"int-phone-number": {
|
|
90
|
-
"legend": "International phone number"
|
|
91
|
-
},
|
|
92
|
-
"complaintDetails": {
|
|
93
|
-
"label": "Complaint details",
|
|
94
|
-
"hint": "Briefly summarise your complaint. Include anything that can help our investigation."
|
|
95
|
-
},
|
|
96
|
-
"whatHappened": {
|
|
97
|
-
"label": "What happened",
|
|
98
|
-
"hint": "Briefly summarise what happened."
|
|
99
|
-
},
|
|
100
|
-
"countrySelect": {
|
|
101
|
-
"label": "Which country are you based in?",
|
|
102
|
-
"hint": "Start to type the country name and options will appear"
|
|
103
|
-
},
|
|
104
|
-
"appealStages": {
|
|
105
|
-
"label": "Appeal stage",
|
|
106
|
-
"hint": "Choose an appeal stage from the drop down menu",
|
|
107
|
-
"options": {
|
|
108
|
-
"null": "Select..."
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
"journey": {
|
|
113
|
-
"header": "HOF Bootstrap Sandbox Form",
|
|
114
|
-
"serviceName": "HOF Bootstrap Sandbox Form",
|
|
115
|
-
"confirmation": {
|
|
116
|
-
"details": "Your reference number <br><strong>HDJ2123F</strong>"
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
"pages": {
|
|
120
|
-
"landing-page": {
|
|
121
|
-
"header": "Landing page"
|
|
122
|
-
},
|
|
123
|
-
"build-your-own-form": {
|
|
124
|
-
"title": "Build your own form",
|
|
125
|
-
"subheader": "Access the build your own form guidance link"
|
|
126
|
-
},
|
|
127
|
-
"address": {
|
|
128
|
-
"header": "What is your address in the UK?",
|
|
129
|
-
"intro": "If you have no fixed address, enter an address where we can contact you."
|
|
130
|
-
},
|
|
131
|
-
"name": {
|
|
132
|
-
"header": "What is your full name?"
|
|
133
|
-
},
|
|
134
|
-
"email": {
|
|
135
|
-
"header": "Enter your email address"
|
|
136
|
-
},
|
|
137
|
-
"phone-number": {
|
|
138
|
-
"header": "Enter your phone number"
|
|
139
|
-
},
|
|
140
|
-
"confirm": {
|
|
141
|
-
"header": "Check your answers before submitting your application.",
|
|
142
|
-
"sections": {
|
|
143
|
-
"applicantsDetails": {
|
|
144
|
-
"header": "Applicant's details"
|
|
145
|
-
},
|
|
146
|
-
"address": {
|
|
147
|
-
"header": "Address"
|
|
148
|
-
},
|
|
149
|
-
"income": {
|
|
150
|
-
"header": "Income"
|
|
151
|
-
},
|
|
152
|
-
"appealDetails": {
|
|
153
|
-
"header": "Appeal details"
|
|
154
|
-
},
|
|
155
|
-
"countrySelect": {
|
|
156
|
-
"header": "Country of residence"
|
|
157
|
-
},
|
|
158
|
-
"contactDetails": {
|
|
159
|
-
"header": "Contact details"
|
|
160
|
-
},
|
|
161
|
-
"complaintDetails": {
|
|
162
|
-
"header": "Complaint details"
|
|
163
|
-
},
|
|
164
|
-
"whatHappened": {
|
|
165
|
-
"header": "What happened"
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
"confirmation": {
|
|
170
|
-
"title": "Application sent",
|
|
171
|
-
"alert": "Application sent",
|
|
172
|
-
"subheader": "What happens next",
|
|
173
|
-
"content": "We’ll contact you with the decision of your application or if we need more information from you."
|
|
174
|
-
},
|
|
175
|
-
"exit": {
|
|
176
|
-
"message": "We have cleared your information to keep it secure. Your information has not been saved."
|
|
177
|
-
},
|
|
178
|
-
"session-timeout-warning": {
|
|
179
|
-
"dialog-title": "{{^showSaveAndExit}}Your application will close soon{{/showSaveAndExit}}{{#showSaveAndExit}}You will be signed out soon{{/showSaveAndExit}}",
|
|
180
|
-
"dialog-text": "{{^showSaveAndExit}}If that happens, your progress will not be saved.{{/showSaveAndExit}}{{#showSaveAndExit}}Any answers you have saved will not be affected, but your progress on this page will not be saved.{{/showSaveAndExit}}",
|
|
181
|
-
"timeout-continue-button": "{{^showSaveAndExit}}Stay on this page{{/showSaveAndExit}}{{#showSaveAndExit}}Stay signed in{{/showSaveAndExit}}",
|
|
182
|
-
"dialog-exit-link": "{{^showSaveAndExit}}Exit this form{{/showSaveAndExit}}{{#showSaveAndExit}}Sign out{{/showSaveAndExit}}"
|
|
183
|
-
},
|
|
184
|
-
"save-and-exit": {
|
|
185
|
-
"header": "You have been signed out",
|
|
186
|
-
"paragraph-1": "Your form doesn't appear to have been worked on for 30 minutes so we closed it for security.",
|
|
187
|
-
"paragraph-2": "Any answers you saved have not been affected.",
|
|
188
|
-
"paragraph-3": "You can sign back in to your application at any time by returning to the <a href='/' class='govuk-link'>start page</a>."
|
|
189
|
-
}
|
|
190
|
-
},
|
|
191
|
-
"validation": {
|
|
192
|
-
"landing-page-radio": {
|
|
193
|
-
"required": "Select an option below and press continue"
|
|
194
|
-
},
|
|
195
|
-
"name": {
|
|
196
|
-
"default": "Enter your full name"
|
|
197
|
-
},
|
|
198
|
-
"dateOfBirth": {
|
|
199
|
-
"default": "Enter your date of birth in the correct format; for example, 31 10 1990",
|
|
200
|
-
"after": "Enter a date after 1 1 1900",
|
|
201
|
-
"before": "Enter a date that is in the past"
|
|
202
|
-
},
|
|
203
|
-
"building": {
|
|
204
|
-
"default": "Enter details of your building and street"
|
|
205
|
-
},
|
|
206
|
-
"townOrCity": {
|
|
207
|
-
"default": "Enter a town or city",
|
|
208
|
-
"regex": "Enter a town or city without including digits"
|
|
209
|
-
},
|
|
210
|
-
"postcode": {
|
|
211
|
-
"default": "Enter your postcode"
|
|
212
|
-
},
|
|
213
|
-
"incomeTypes": {
|
|
214
|
-
"default": "Select all options that apply to you."
|
|
215
|
-
},
|
|
216
|
-
"countryOfHearing": {
|
|
217
|
-
"default": "Select where the appeal hearing is to be held"
|
|
218
|
-
},
|
|
219
|
-
"countrySelect": {
|
|
220
|
-
"default": "Enter a valid country of residence",
|
|
221
|
-
"required": "Enter your country of residence"
|
|
222
|
-
},
|
|
223
|
-
"email": {
|
|
224
|
-
"default": "Enter your email address in the correct format"
|
|
225
|
-
},
|
|
226
|
-
"phone": {
|
|
227
|
-
"default": "Enter your phone number"
|
|
228
|
-
},
|
|
229
|
-
"int-phone-number": {
|
|
230
|
-
"required": "Enter an international phone number",
|
|
231
|
-
"internationalPhoneNumber": "Enter a valid international phone number"
|
|
232
|
-
},
|
|
233
|
-
"complaintDetails": {
|
|
234
|
-
"default": "Enter details about why you are making a complaint",
|
|
235
|
-
"maxlength": "Keep to the {{maxlength}} character limit"
|
|
236
|
-
},
|
|
237
|
-
"whatHappened": {
|
|
238
|
-
"default": "Enter details about what happened",
|
|
239
|
-
"maxword": "Keep to the {{maxword}} word limit"
|
|
240
|
-
},
|
|
241
|
-
"appealStages": {
|
|
242
|
-
"required": "Select an appeal stage from the list"
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|