hof 23.0.4-session-timeout-beta → 23.0.4

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 CHANGED
@@ -1,3 +1,23 @@
1
+ ## 2026-04-16, Version 23.0.4 (Stable), @PaolaDMadd-Pro
2
+
3
+ ### Fixed
4
+ - Decoupled session timeout keep alive from analytics configuration.
5
+ - Updated CSP defaults to always include `connect-src 'self'` so same-origin keep alive requests are allowed.
6
+ - Updated GA CSP behavior to **extend** `connect-src` with analytics endpoints when `gaTagId` is set, rather than replacing defaults.
7
+
8
+ ### Changed
9
+ - Improved timeout dialog refresh behavior:
10
+ * On keep alive success, `timeSessionRefreshed` is updated and timeout controller logic is restarted.
11
+ * On keep alive failure a console.error is triggered.
12
+
13
+ ### Tests
14
+ - Added integration regression coverage for CSP:
15
+ * `gaTagId` set: GA and region analytics `connect-src` endpoints are present.
16
+ * `gaTagId` not set: default `connect-src 'self'` remains and GA region endpoints are absent.
17
+ - Added frontend Jest coverage for timeout refresh behavior:
18
+ * Success path (`$.get().done`) updates refresh time and calls controller.
19
+
20
+
1
21
  ## 2026-03-18, Version 23.0.3 (Stable), @vinodhasamiyappan-ho
2
22
 
3
23
  ### Security
package/README.md CHANGED
@@ -1407,8 +1407,8 @@ This feature allows you to customise the content related to the session timeout
1407
1407
 
1408
1408
  ### Usage
1409
1409
 
1410
- By default, the session timeout is set to the redis session ttl. To bypass this and display the session timeout message before the redis session ttl the following evironment variables must be set:
1411
- `CUSTOM_SESSION_EXPIRY` - e.g. `600`. Configure to expire before thte project's redis session ttl.
1410
+ By default, the session timeout is set to the redis session ttl. To bypass this and display the session timeout message before the redis session ttl the following environment variables must be set:
1411
+ `CUSTOM_SESSION_EXPIRY` - e.g. `600`. Configure to expire before the project's redis session ttl.
1412
1412
  `USE_CUSTOM_SESSION_TIMEOUT` - `false` by default. When set to `true` the '/session-timeout' page can run before the session expires without triggering a `404` middleware error.
1413
1413
 
1414
1414
  To enable and customise the session timeout behaviour, you need to set the component and translations in your project's `hof.settings.json` file:
@@ -1437,7 +1437,7 @@ To override the default session-timeout page completely, the path to the session
1437
1437
  "hof/components/session-timeout-warning"
1438
1438
  ],
1439
1439
  "translations": "./apps/common/translations",
1440
- "views": ["./apps/common/views"], // allows you to overide the HOF default session-timeout page and use a custom one from the specified views
1440
+ "views": ["./apps/common/views"], // allows you to override the HOF default session-timeout page and use a custom one from the specified views
1441
1441
  ...
1442
1442
  ```
1443
1443
  or in the project's `server.js` e.g.
@@ -1445,6 +1445,22 @@ or in the project's `server.js` e.g.
1445
1445
  settings.views = path.resolve(__dirname, './apps/common/views');
1446
1446
  ```
1447
1447
 
1448
+
1449
+ ### Session Timeout Keep-alive and CSP
1450
+
1451
+ From version 23.0.4 , session timeout keep alive is now independent of analytics tags.
1452
+
1453
+ - Default CSP always includes: `connect-src 'self'`
1454
+ - If `GA_TAG` (`gaTagId`) is configured:
1455
+ - Google Analytics endpoints are added to `connect-src`.
1456
+ - If `GA_TAG` is not configured: Only default same-origin `connect-src` is used (no GA region endpoints).
1457
+
1458
+ ### Timeout Dialog Behavior
1459
+ When a user clicks **Stay on this page** in the timeout dialog:
1460
+
1461
+ - If keep-alive succeeds: session refresh timestamp is updated and the timeout countdown/controller is restarted.
1462
+
1463
+
1448
1464
  ### Customising content in `pages.json`
1449
1465
  Once the variables are set, you can customise the session timeout warning and exit messages in your project's pages.json:
1450
1466
 
@@ -278,13 +278,10 @@ window.GOVUK.sessionDialog = {
278
278
 
279
279
  refreshSession: function () {
280
280
  $.get('')
281
- .done(function () {
282
- window.GOVUK.sessionDialog.timeSessionRefreshed = new Date();
283
- window.GOVUK.sessionDialog.controller();
284
- })
285
- .fail(function () {
286
- window.GOVUK.sessionDialog.redirect();
287
- });
281
+ .done(function () {
282
+ window.GOVUK.sessionDialog.timeSessionRefreshed = new Date();
283
+ window.GOVUK.sessionDialog.controller();
284
+ });
288
285
  },
289
286
 
290
287
  redirect: function () {
package/index.js CHANGED
@@ -93,7 +93,6 @@ const getContentSecurityPolicy = (config, res) => {
93
93
  'www.google.co.uk/ads/ga-audiences'
94
94
  ],
95
95
  connectSrc: [
96
- "'self'",
97
96
  'https://www.google-analytics.com',
98
97
  'https://region1.google-analytics.com',
99
98
  'https://region1.analytics.google.com'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hof",
3
3
  "description": "A bootstrap for HOF projects",
4
- "version": "23.0.4-session-timeout-beta",
4
+ "version": "23.0.4",
5
5
  "license": "MIT",
6
6
  "main": "index.js",
7
7
  "author": "HomeOffice",
@@ -1,118 +0,0 @@
1
-
2
- <!DOCTYPE html>
3
- <!--[if lt IE 9]><html class="lte-ie8" lang="{{htmlLang}}"><![endif]-->
4
- <!--[if gt IE 8]><!--><html lang="{{htmlLang}}" class="govuk-template--rebranded"><!--<![endif]-->
5
- <head>
6
- <meta charset="utf-8" />
7
- <title>{{$pageTitle}}{{/pageTitle}}</title>
8
- {{$head}}{{/head}}
9
- <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
10
- <meta name="theme-color" content="#1d70b8">
11
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
12
- <link rel="icon" sizes="48x48" href="{{govukAssetPath}}rebrand/images/favicon.ico">
13
- <link rel="icon" sizes="any" href="{{govukAssetPath}}rebrand/images/favicon.svg" type="image/svg+xml">
14
- <link rel="mask-icon" href="{{govukAssetPath}}rebrand/images/govuk-icon-mask.svg" color="#1d70b8">
15
- <link rel="apple-touch-icon" href="{{govukAssetPath}}rebrand/images/govuk-icon-180.png">
16
- <link rel="manifest" href="{{govukAssetPath}}rebrand/manifest.json">
17
- <meta property="og:image" content="{{govukAssetPath}}rebrand/images/govuk-opengraph-image.png">
18
- </head>
19
-
20
- <body class="{{$bodyClasses}}{{/bodyClasses}} govuk-template__body js-enabled" >
21
- <script {{#nonce}}nonce="{{nonce}}"{{/nonce}}>document.body.className += ' js-enabled' + ('noModule' in HTMLScriptElement.prototype ? ' govuk-frontend-supported' : '');</script>
22
-
23
-
24
- <div id="global-cookie-message" class="gem-c-cookie-banner govuk-clearfix" data-module="cookie-banner" role="region" aria-label="cookie banner" data-nosnippet="">
25
- {{$cookieMessage}}{{/cookieMessage}}
26
- </div>
27
-
28
- {{$bodyStart}}{{/bodyStart}}
29
-
30
- <header role="banner" id="govuk-header" class="{{$headerClass}}{{/headerClass}}">
31
- <div class="govuk-header__container govuk-width-container">
32
-
33
- <div class="govuk-header__logo">
34
- <a href="{{$homepageUrl}}https://www.gov.uk{{/homepageUrl}}" title="{{$logoLinkTitle}}Go to the GOV.UK homepage{{/logoLinkTitle}}" id="logo" class="govuk-header__link govuk-header__link--homepage" target="_blank" data-module="track-click" data-track-category="homeLinkClicked" data-track-action="homeHeader">
35
- <!--[if gt IE 8]><!-->
36
- <div id="govuk-header__logo"></div>
37
- <img src="/public/images/govuk-logo.svg" id="govuk-header__logo" alt="Logo" loading="lazy" />
38
- <!--<![endif]-->
39
- <!--[if IE 8]>
40
- <img src="{{govukAssetPath}}rebrand/images/govuk-logotype-tudor-crown.png" class="govuk-header__logotype-crown-fallback-image" width="32" height="30" alt="">
41
- <![endif]-->
42
- </a>
43
- </div>
44
- {{$insideHeader}}{{/insideHeader}}
45
-
46
- {{$propositionHeader}}{{/propositionHeader}}
47
- </div>
48
- </header>
49
-
50
-
51
- {{$afterHeader}}{{/afterHeader}}
52
-
53
-
54
- {{$main}}{{/main}}
55
-
56
- <footer class="govuk-footer">
57
- <div class="govuk-width-container">
58
- <svg
59
- focusable="false"
60
- role="presentation"
61
- xmlns="http://www.w3.org/2000/svg"
62
- viewBox="0 0 64 60"
63
- height="30"
64
- width="32"
65
- fill="currentcolor" class="govuk-footer__crown">
66
- <g>
67
- <circle cx="20" cy="17.6" r="3.7" />
68
- <circle cx="10.2" cy="23.5" r="3.7" />
69
- <circle cx="3.7" cy="33.2" r="3.7" />
70
- <circle cx="31.7" cy="30.6" r="3.7" />
71
- <circle cx="43.3" cy="17.6" r="3.7" />
72
- <circle cx="53.2" cy="23.5" r="3.7" />
73
- <circle cx="59.7" cy="33.2" r="3.7" />
74
- <circle cx="31.7" cy="30.6" r="3.7" />
75
- <path d="M33.1,9.8c.2-.1.3-.3.5-.5l4.6,2.4v-6.8l-4.6,1.5c-.1-.2-.3-.3-.5-.5l1.9-5.9h-6.7l1.9,5.9c-.2.1-.3.3-.5.5l-4.6-1.5v6.8l4.6-2.4c.1.2.3.3.5.5l-2.6,8c-.9,2.8,1.2,5.7,4.1,5.7h0c3,0,5.1-2.9,4.1-5.7l-2.6-8ZM37,37.9s-3.4,3.8-4.1,6.1c2.2,0,4.2-.5,6.4-2.8l-.7,8.5c-2-2.8-4.4-4.1-5.7-3.8.1,3.1.5,6.7,5.8,7.2,3.7.3,6.7-1.5,7-3.8.4-2.6-2-4.3-3.7-1.6-1.4-4.5,2.4-6.1,4.9-3.2-1.9-4.5-1.8-7.7,2.4-10.9,3,4,2.6,7.3-1.2,11.1,2.4-1.3,6.2,0,4,4.6-1.2-2.8-3.7-2.2-4.2.2-.3,1.7.7,3.7,3,4.2,1.9.3,4.7-.9,7-5.9-1.3,0-2.4.7-3.9,1.7l2.4-8c.6,2.3,1.4,3.7,2.2,4.5.6-1.6.5-2.8,0-5.3l5,1.8c-2.6,3.6-5.2,8.7-7.3,17.5-7.4-1.1-15.7-1.7-24.5-1.7h0c-8.8,0-17.1.6-24.5,1.7-2.1-8.9-4.7-13.9-7.3-17.5l5-1.8c-.5,2.5-.6,3.7,0,5.3.8-.8,1.6-2.3,2.2-4.5l2.4,8c-1.5-1-2.6-1.7-3.9-1.7,2.3,5,5.2,6.2,7,5.9,2.3-.4,3.3-2.4,3-4.2-.5-2.4-3-3.1-4.2-.2-2.2-4.6,1.6-6,4-4.6-3.7-3.7-4.2-7.1-1.2-11.1,4.2,3.2,4.3,6.4,2.4,10.9,2.5-2.8,6.3-1.3,4.9,3.2-1.8-2.7-4.1-1-3.7,1.6.3,2.3,3.3,4.1,7,3.8,5.4-.5,5.7-4.2,5.8-7.2-1.3-.2-3.7,1-5.7,3.8l-.7-8.5c2.2,2.3,4.2,2.7,6.4,2.8-.7-2.3-4.1-6.1-4.1-6.1h10.6,0Z" />
76
- </g>
77
- </svg>
78
- <div class="govuk-footer__meta">
79
- <div class="govuk-footer__meta-item govuk-footer__meta-item--grow">
80
- <h2 class="govuk-visually-hidden">Support links</h2>
81
- {{$footerSupportLinks}}{{/footerSupportLinks}}
82
- <svg
83
- aria-hidden="true"
84
- focusable="false"
85
- class="govuk-footer__licence-logo"
86
- xmlns="http://www.w3.org/2000/svg"
87
- viewBox="0 0 483.2 195.7"
88
- height="17"
89
- width="41">
90
- <path
91
- fill="currentColor"
92
- d="M421.5 142.8V.1l-50.7 32.3v161.1h112.4v-50.7zm-122.3-9.6A47.12 47.12 0 0 1 221 97.8c0-26 21.1-47.1 47.1-47.1 16.7 0 31.4 8.7 39.7 21.8l42.7-27.2A97.63 97.63 0 0 0 268.1 0c-36.5 0-68.3 20.1-85.1 49.7A98 98 0 0 0 97.8 0C43.9 0 0 43.9 0 97.8s43.9 97.8 97.8 97.8c36.5 0 68.3-20.1 85.1-49.7a97.76 97.76 0 0 0 149.6 25.4l19.4 22.2h3v-87.8h-80l24.3 27.5zM97.8 145c-26 0-47.1-21.1-47.1-47.1s21.1-47.1 47.1-47.1 47.2 21 47.2 47S123.8 145 97.8 145" />
93
- </svg>
94
- <span class="govuk-footer__licence-description">
95
- {{$licenceMessage}}All content is available under the <a href="https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/" id="open-government-licence" class="govuk-footer__link" target="_blank" rel="license">Open Government Licence v3.0</a>, except where otherwise stated{{/licenceMessage}}
96
- </span>
97
- </div>
98
- <div class="govuk-footer__meta-item">
99
- <a
100
- class="govuk-footer__link govuk-footer__copyright-logo"
101
- href="https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/">
102
- {{$crownCopyrightMessage}}© Crown copyright{{/crownCopyrightMessage}}
103
- </a>
104
- </div>
105
- </div>
106
- </div>
107
- </footer>
108
-
109
- <div id="global-app-error" class="app-error hidden"></div>
110
-
111
-
112
- {{$bodyEnd}}{{/bodyEnd}}
113
-
114
-
115
- <script {{#nonce}}nonce="{{nonce}}"{{/nonce}}>if (typeof window.GOVUK === 'undefined') document.body.className = document.body.className.replace('js-enabled', '');</script>
116
-
117
- </body>
118
- </html>
@@ -1,278 +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
- "amountWithUnitSelect": {
112
- "label": "Amount",
113
- "amountLabel": "Amount:",
114
- "unitLabel": "Unit:",
115
- "options": [
116
- {
117
- "null": "Select..."
118
- },
119
- {
120
- "label": "Litres",
121
- "value": "L"
122
- },
123
- {
124
- "label": "Kilograms",
125
- "value": "KG"
126
- }
127
- ]
128
- }
129
- },
130
- "journey": {
131
- "header": "HOF Bootstrap Sandbox Form",
132
- "serviceName": "HOF Bootstrap Sandbox Form",
133
- "confirmation": {
134
- "details": "Your reference number <br><strong>HDJ2123F</strong>"
135
- }
136
- },
137
- "pages": {
138
- "landing-page": {
139
- "header": "Landing page"
140
- },
141
- "build-your-own-form": {
142
- "title": "Build your own form",
143
- "subheader": "Access the build your own form guidance link"
144
- },
145
- "address": {
146
- "header": "What is your address in the UK?",
147
- "intro": "If you have no fixed address, enter an address where we can contact you."
148
- },
149
- "name": {
150
- "header": "What is your full name?"
151
- },
152
- "email": {
153
- "header": "Enter your email address"
154
- },
155
- "phone-number": {
156
- "header": "Enter your phone number"
157
- },
158
- "confirm": {
159
- "header": "Check your answers before submitting your application.",
160
- "sections": {
161
- "applicantsDetails": {
162
- "header": "Applicant's details"
163
- },
164
- "address": {
165
- "header": "Address"
166
- },
167
- "income": {
168
- "header": "Income"
169
- },
170
- "appealDetails": {
171
- "header": "Appeal details"
172
- },
173
- "countrySelect": {
174
- "header": "Country of residence"
175
- },
176
- "contactDetails": {
177
- "header": "Contact details"
178
- },
179
- "complaintDetails": {
180
- "header": "Complaint details"
181
- },
182
- "whatHappened": {
183
- "header": "What happened"
184
- },
185
- "amountWithUnitSelect": {
186
- "header": "Entered amount"
187
- }
188
- }
189
- },
190
- "confirmation": {
191
- "title": "Application sent",
192
- "alert": "Application sent",
193
- "subheader": "What happens next",
194
- "content": "We’ll contact you with the decision of your application or if we need more information from you."
195
- },
196
- "exit": {
197
- "message": "We have cleared your information to keep it secure. Your information has not been saved."
198
- },
199
- "session-timeout-warning": {
200
- "dialog-title": "{{^showSaveAndExit}}Your application will close soon{{/showSaveAndExit}}{{#showSaveAndExit}}You will be signed out soon{{/showSaveAndExit}}",
201
- "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}}",
202
- "timeout-continue-button": "{{^showSaveAndExit}}Stay on this page{{/showSaveAndExit}}{{#showSaveAndExit}}Stay signed in{{/showSaveAndExit}}",
203
- "dialog-exit-link": "{{^showSaveAndExit}}Exit this form{{/showSaveAndExit}}{{#showSaveAndExit}}Sign out{{/showSaveAndExit}}"
204
- },
205
- "save-and-exit": {
206
- "header": "You have been signed out",
207
- "paragraph-1": "Your form doesn't appear to have been worked on for 30 minutes so we closed it for security.",
208
- "paragraph-2": "Any answers you saved have not been affected.",
209
- "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>."
210
- }
211
- },
212
- "validation": {
213
- "landing-page-radio": {
214
- "required": "Select an option below and press continue"
215
- },
216
- "name": {
217
- "default": "Enter your full name"
218
- },
219
- "dateOfBirth": {
220
- "default": "Enter your date of birth in the correct format; for example, 31 10 1990",
221
- "after": "Enter a date after 1 1 1900",
222
- "before": "Enter a date that is in the past"
223
- },
224
- "building": {
225
- "default": "Enter details of your building and street"
226
- },
227
- "townOrCity": {
228
- "default": "Enter a town or city",
229
- "regex": "Enter a town or city without including digits"
230
- },
231
- "postcode": {
232
- "default": "Enter your postcode"
233
- },
234
- "incomeTypes": {
235
- "default": "Select all options that apply to you."
236
- },
237
- "countryOfHearing": {
238
- "default": "Select where the appeal hearing is to be held"
239
- },
240
- "countrySelect": {
241
- "default": "Enter a valid country of residence",
242
- "required": "Enter your country of residence"
243
- },
244
- "email": {
245
- "default": "Enter your email address in the correct format"
246
- },
247
- "phone": {
248
- "default": "Enter your phone number"
249
- },
250
- "int-phone-number": {
251
- "required": "Enter an international phone number",
252
- "internationalPhoneNumber": "Enter a valid international phone number"
253
- },
254
- "complaintDetails": {
255
- "default": "Enter details about why you are making a complaint",
256
- "maxlength": "Keep to the {{maxlength}} character limit"
257
- },
258
- "whatHappened": {
259
- "default": "Enter details about what happened",
260
- "maxword": "Keep to the {{maxword}} word limit"
261
- },
262
- "appealStages": {
263
- "required": "Select an appeal stage from the list"
264
- },
265
- "amountWithUnitSelect": {
266
- "default": "Enter the amount in the correct format; for example, 10 Litres",
267
- "alphanum": "The amount must not contain any special characters",
268
- "required": "Enter an amount and a unit value"
269
- },
270
- "amountWithUnitSelect-unit": {
271
- "default": "A valid value must be selected as the amount unit",
272
- "required": "A unit must be selected for the amount"
273
- },
274
- "amountWithUnitSelect-amount": {
275
- "required": "An amount must be selected with the unit"
276
- }
277
- }
278
- }