@govuk-pay/pay-js-commons 7.0.21 → 7.0.22
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/lib/utils/middleware/csp.js +87 -0
- package/package.json +4 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _require = require('express-rate-limit'),
|
|
4
|
+
rateLimit = _require.rateLimit;
|
|
5
|
+
var hasSubstr = function hasSubstr(lookUpStrings, targetString) {
|
|
6
|
+
return lookUpStrings.some(function (str) {
|
|
7
|
+
return targetString.toLowerCase().includes(str.toLowerCase());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var rateLimitMiddleware = rateLimit({
|
|
11
|
+
windowMs: 5 * 60 * 1000,
|
|
12
|
+
// 5 minutes
|
|
13
|
+
limit: 10,
|
|
14
|
+
// Limit each IP to 10 requests per window
|
|
15
|
+
standardHeaders: 'draft-7',
|
|
16
|
+
// draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header
|
|
17
|
+
legacyHeaders: false // Disable the `X-RateLimit-*` headers.
|
|
18
|
+
});
|
|
19
|
+
var requestParseMiddleware = function requestParseMiddleware(maxPayloadBytes, express) {
|
|
20
|
+
return function (req, res, next) {
|
|
21
|
+
if (req.is('application/json') || req.is('application/reports+json') || req.is('application/csp-report')) {
|
|
22
|
+
express.json({
|
|
23
|
+
type: ['application/json', 'application/reports+json', 'application/csp-report'],
|
|
24
|
+
limit: maxPayloadBytes // limit body payload to maxPayloadBytes, validated prior to parsing
|
|
25
|
+
})(req, res, next);
|
|
26
|
+
} else {
|
|
27
|
+
return res.status(400).end();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
var detectErrorsMiddleware = function detectErrorsMiddleware(logger) {
|
|
32
|
+
return function (err, req, res, next) {
|
|
33
|
+
if (err) {
|
|
34
|
+
if (err.type === 'entity.too.large') logger.info('CSP violation request payload exceeds maximum size');
|
|
35
|
+
if (err.type === 'entity.parse.failed') logger.info('CSP violation request payload did not match expected content type');
|
|
36
|
+
return res.status(400).end();
|
|
37
|
+
}
|
|
38
|
+
next();
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
var captureEventMiddleware = function captureEventMiddleware(ignoredStrings, logger, sentry) {
|
|
42
|
+
return function (req, res) {
|
|
43
|
+
var reports = undefined;
|
|
44
|
+
if (Array.isArray(req.body) && req.body.length > 0) {
|
|
45
|
+
reports = req.body.filter(function (report) {
|
|
46
|
+
return report.type === 'csp-violation';
|
|
47
|
+
}); // new style reporting-api, can be batched into multiple reports
|
|
48
|
+
} else if (req.body['csp-report'] !== undefined) {
|
|
49
|
+
reports = [{
|
|
50
|
+
body: req.body['csp-report']
|
|
51
|
+
}]; // old style report-uri
|
|
52
|
+
}
|
|
53
|
+
var userAgent = req.headers['user-agent'];
|
|
54
|
+
if (reports !== undefined) {
|
|
55
|
+
reports.forEach(function (report) {
|
|
56
|
+
var _body$blockedUri, _body$violatedDirect;
|
|
57
|
+
var body = report.body;
|
|
58
|
+
var blockedUri = (_body$blockedUri = body['blocked-uri']) !== null && _body$blockedUri !== void 0 ? _body$blockedUri : body['blockedURL'];
|
|
59
|
+
var violatedDirective = (_body$violatedDirect = body['violated-directive']) !== null && _body$violatedDirect !== void 0 ? _body$violatedDirect : body['effectiveDirective']; // https://www.w3.org/TR/CSP3/#dom-securitypolicyviolationevent-violateddirective
|
|
60
|
+
if (violatedDirective === undefined || blockedUri === undefined) {
|
|
61
|
+
logger.info('CSP violation report is invalid');
|
|
62
|
+
return res.status(400).end();
|
|
63
|
+
} else {
|
|
64
|
+
if (hasSubstr(ignoredStrings, blockedUri)) return res.status(204).end();
|
|
65
|
+
sentry.captureEvent({
|
|
66
|
+
message: "Blocked ".concat(violatedDirective, " from ").concat(blockedUri),
|
|
67
|
+
level: 'warning',
|
|
68
|
+
extra: {
|
|
69
|
+
cspReport: body,
|
|
70
|
+
userAgent: userAgent
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
logger.info('CSP violation report missing');
|
|
77
|
+
return res.status(400).end();
|
|
78
|
+
}
|
|
79
|
+
return res.status(204).end();
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
module.exports = {
|
|
83
|
+
rateLimitMiddleware: rateLimitMiddleware,
|
|
84
|
+
captureEventMiddleware: captureEventMiddleware,
|
|
85
|
+
requestParseMiddleware: requestParseMiddleware,
|
|
86
|
+
detectErrorsMiddleware: detectErrorsMiddleware
|
|
87
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@govuk-pay/pay-js-commons",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.22",
|
|
4
4
|
"description": "Reusable js scripts for GOV.UK Pay Node.js projects",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": "^22.17.1"
|
|
@@ -120,6 +120,7 @@
|
|
|
120
120
|
"chai": "^4.2.0",
|
|
121
121
|
"eslint-plugin-import": "^2.18.0",
|
|
122
122
|
"eslint-plugin-promise": "^6.0.0",
|
|
123
|
+
"express": "^5.1.0",
|
|
123
124
|
"jest": "^29.4.3",
|
|
124
125
|
"jest-environment-jsdom": "^29.4.3",
|
|
125
126
|
"js-cookie": "^3.0.0",
|
|
@@ -136,6 +137,7 @@
|
|
|
136
137
|
"nock": "^13.5.1",
|
|
137
138
|
"sinon": "^15.0.0",
|
|
138
139
|
"standard": "^17.0.0",
|
|
140
|
+
"supertest": "^7.1.4",
|
|
139
141
|
"uglify-js": "^3.6.0",
|
|
140
142
|
"watchify": "^4.0.0",
|
|
141
143
|
"xo": "^0.54.0"
|
|
@@ -146,6 +148,7 @@
|
|
|
146
148
|
"dependencies": {
|
|
147
149
|
"axios": "^1.6.5",
|
|
148
150
|
"csrf": "^3.1.0",
|
|
151
|
+
"express-rate-limit": "^8.1.0",
|
|
149
152
|
"lodash": "4.17.21",
|
|
150
153
|
"moment-timezone": "0.5.43",
|
|
151
154
|
"rfc822-validate": "1.0.0",
|