hof 20.0.0-beta.3 → 20.0.0-beta.31
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/README.md +329 -256
- package/build/lib/mkdir.js +2 -2
- package/components/date/index.js +37 -26
- package/components/date/templates/date.html +3 -3
- package/components/emailer/index.js +49 -41
- package/components/emailer/transports/debug.js +1 -2
- package/components/index.js +2 -1
- package/components/notify/index.js +60 -0
- package/components/notify/notify.js +25 -0
- 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 +23 -17
- package/frontend/govuk-template/build/config.js +1 -1
- package/frontend/template-mixins/mixins/template-mixins.js +12 -8
- package/frontend/template-mixins/partials/forms/checkbox-group.html +13 -4
- package/frontend/template-mixins/partials/forms/input-text-date.html +1 -1
- package/frontend/template-mixins/partials/forms/input-text-group.html +6 -4
- package/frontend/template-mixins/partials/forms/option-group.html +12 -3
- package/frontend/template-mixins/partials/forms/select.html +3 -3
- package/frontend/template-mixins/partials/forms/textarea-group.html +3 -3
- package/frontend/template-mixins/partials/mixins/panel.html +1 -2
- package/frontend/template-partials/translations/src/en/errors.json +12 -0
- package/frontend/template-partials/views/layout.html +10 -3
- package/frontend/template-partials/views/partials/cookie-banner.html +1 -1
- package/frontend/template-partials/views/partials/form.html +2 -1
- package/frontend/template-partials/views/partials/warn.html +7 -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 +2 -2
- package/frontend/themes/gov-uk/client-js/skip-to-main.js +18 -17
- package/frontend/themes/gov-uk/styles/govuk.scss +4 -0
- package/frontend/themes/gov-uk/styles/modules/_validation.scss +2 -2
- package/frontend/toolkit/assets/javascript/form-focus.js +10 -1
- package/frontend/toolkit/assets/javascript/validation.js +6 -1
- 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 +6 -6
- package/sandbox/apps/sandbox/fields.js +12 -12
- package/sandbox/apps/sandbox/index.js +1 -5
- package/sandbox/apps/sandbox/translations/en/default.json +24 -0
- package/sandbox/assets/scss/app.scss +0 -52
- package/sandbox/package.json +2 -0
- package/sandbox/public/css/app.css +4908 -4965
- package/sandbox/public/js/bundle.js +79 -65
- package/sandbox/server.js +7 -1
- package/sandbox/yarn.lock +39 -564
- package/transpiler/lib/write-files.js +1 -2
- package/utilities/helpers/index.js +16 -1
- package/wizard/index.js +1 -0
- package/.nyc_output/65af88d9-aebe-4d1b-a21d-6fbf7f2bbda4.json +0 -1
- package/.nyc_output/processinfo/65af88d9-aebe-4d1b-a21d-6fbf7f2bbda4.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- package/.vscode/settings.json +0 -6
- package/sandbox/.env +0 -1
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.31",
|
5
5
|
"license": "MIT",
|
6
6
|
"main": "index.js",
|
7
7
|
"author": "HomeOffice",
|
@@ -70,16 +70,16 @@
|
|
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",
|
80
79
|
"nodemailer-ses-transport": "^1.5.0",
|
81
80
|
"nodemailer-smtp-transport": "^2.7.4",
|
82
81
|
"nodemailer-stub-transport": "^1.1.0",
|
82
|
+
"notifications-node-client": "^5.1.1",
|
83
83
|
"redis": "^3.1.2",
|
84
84
|
"reqres": "^3.0.1",
|
85
85
|
"request": "^2.79.0",
|
@@ -88,8 +88,8 @@
|
|
88
88
|
"serve-static": "^1.14.1",
|
89
89
|
"uglify-js": "^3.14.3",
|
90
90
|
"underscore": "^1.12.1",
|
91
|
-
"urijs": "^1.19.
|
92
|
-
"winston": "^3.
|
91
|
+
"urijs": "^1.19.11",
|
92
|
+
"winston": "^3.7.2"
|
93
93
|
},
|
94
94
|
"devDependencies": {
|
95
95
|
"@cucumber/cucumber": "^7.3.0",
|
@@ -8,7 +8,7 @@ module.exports = {
|
|
8
8
|
'landing-page-radio': {
|
9
9
|
mixin: 'radio-group',
|
10
10
|
validate: ['required'],
|
11
|
-
|
11
|
+
isPageHeading: true,
|
12
12
|
// Design system says to avoid in-line unless it's two options,
|
13
13
|
// so just added as an example below but by default it isn't
|
14
14
|
className: ['govuk-radios--inline'],
|
@@ -16,12 +16,13 @@ module.exports = {
|
|
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
|
-
|
21
|
+
isPageHeading: 'true'
|
21
22
|
},
|
22
23
|
'dateOfBirth': dateComponent('dateOfBirth', {
|
23
|
-
|
24
|
-
|
24
|
+
mixin: 'input-date',
|
25
|
+
isPageHeading: 'true',
|
25
26
|
validate: [
|
26
27
|
'required',
|
27
28
|
'date',
|
@@ -29,28 +30,24 @@ module.exports = {
|
|
29
30
|
]
|
30
31
|
}),
|
31
32
|
building: {
|
32
|
-
noHeading: 'true',
|
33
33
|
validate: ['required', 'notUrl', { type: 'maxlength', arguments: 100 }]
|
34
34
|
},
|
35
35
|
street: {
|
36
|
-
noHeading: 'true',
|
37
36
|
validate: ['notUrl', { type: 'maxlength', arguments: 50 }],
|
38
37
|
labelClassName: 'visuallyhidden'
|
39
38
|
},
|
40
39
|
townOrCity: {
|
41
|
-
noHeading: 'true',
|
42
40
|
validate: ['required', 'notUrl',
|
43
41
|
{ type: 'regex', arguments: /^([^0-9]*)$/ },
|
44
42
|
{ type: 'maxlength', arguments: 100 }
|
45
43
|
]
|
46
44
|
},
|
47
45
|
postcode: {
|
48
|
-
noHeading: 'true',
|
49
46
|
validate: ['required', 'postcode'],
|
50
47
|
formatter: ['removespaces', 'uppercase']
|
51
48
|
},
|
52
49
|
incomeTypes: {
|
53
|
-
|
50
|
+
isPageHeading: 'true',
|
54
51
|
mixin: 'checkbox-group',
|
55
52
|
labelClassName: 'visuallyhidden',
|
56
53
|
validate: ['required'],
|
@@ -63,7 +60,7 @@ module.exports = {
|
|
63
60
|
]
|
64
61
|
},
|
65
62
|
countryOfHearing: {
|
66
|
-
|
63
|
+
isPageHeading: 'true',
|
67
64
|
mixin: 'radio-group',
|
68
65
|
validate: ['required'],
|
69
66
|
options: [
|
@@ -73,7 +70,7 @@ module.exports = {
|
|
73
70
|
]
|
74
71
|
},
|
75
72
|
email: {
|
76
|
-
|
73
|
+
isPageHeading: 'true',
|
77
74
|
labelClassName: ['govuk-label--l'],
|
78
75
|
validate: ['required', 'email']
|
79
76
|
},
|
@@ -86,6 +83,7 @@ module.exports = {
|
|
86
83
|
},
|
87
84
|
countrySelect: {
|
88
85
|
mixin: 'select',
|
86
|
+
isPageHeading: 'true',
|
89
87
|
className: ['typeahead'],
|
90
88
|
options:[''].concat(require('homeoffice-countries').allCountries),
|
91
89
|
legend: {
|
@@ -97,12 +95,13 @@ module.exports = {
|
|
97
95
|
mixin: 'textarea',
|
98
96
|
// we want to ignore default formatters as we want
|
99
97
|
// to preserve white space
|
98
|
+
isPageHeading: 'true',
|
100
99
|
'ignore-defaults': true,
|
101
100
|
// apply the other default formatters
|
102
101
|
formatter: ['trim', 'hyphens'],
|
103
102
|
labelClassName: ['govuk-label--l'],
|
104
103
|
// attributes here are passed to the field element
|
105
|
-
validate: ['required', { type: 'maxlength', arguments:
|
104
|
+
validate: ['required', { type: 'maxlength', arguments: 10 }],
|
106
105
|
attributes: [{
|
107
106
|
attribute: 'rows',
|
108
107
|
value: 8
|
@@ -110,6 +109,7 @@ module.exports = {
|
|
110
109
|
},
|
111
110
|
appealStages: {
|
112
111
|
mixin: 'select',
|
112
|
+
isPageHeading: 'true',
|
113
113
|
validate: ['required'],
|
114
114
|
options: [{
|
115
115
|
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'],
|
@@ -94,6 +94,30 @@
|
|
94
94
|
"options": {
|
95
95
|
"null": "Select..."
|
96
96
|
}
|
97
|
+
},
|
98
|
+
"purposeTypes": {
|
99
|
+
"legend": "What will you use the loan for?",
|
100
|
+
"hint": "Select all options that apply to you.",
|
101
|
+
"options": {
|
102
|
+
"housing": {
|
103
|
+
"label": "Housing",
|
104
|
+
"hint": "Deposit, rent payment or moving costs."
|
105
|
+
},
|
106
|
+
"essential_items": {
|
107
|
+
"label": "Essential items",
|
108
|
+
"hint": "For example, furniture, fridge, curtains or carpets."
|
109
|
+
},
|
110
|
+
"basic_living_costs": {
|
111
|
+
"label": "Basic living costs",
|
112
|
+
"hint": "For example, food or household bills."
|
113
|
+
},
|
114
|
+
"training_or_retraining": {
|
115
|
+
"label": "Training or education"
|
116
|
+
},
|
117
|
+
"work_clothing_and_equipment": {
|
118
|
+
"label": "Work clothing and equipment"
|
119
|
+
}
|
120
|
+
}
|
97
121
|
}
|
98
122
|
},
|
99
123
|
"journey": {
|
@@ -1,6 +1,5 @@
|
|
1
1
|
|
2
2
|
@import "../../../frontend/themes/gov-uk/styles/govuk";
|
3
|
-
@import "../../../node_modules/govuk-frontend/govuk/all";
|
4
3
|
|
5
4
|
//autocomplete styling
|
6
5
|
.tt-menu {
|
@@ -26,54 +25,3 @@
|
|
26
25
|
.twitter-typeahead {
|
27
26
|
width: 100%;
|
28
27
|
}
|
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
|
-
}
|