@blamejs/blamejs-shop 0.3.6 → 0.3.8
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 +4 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/vendor/MANIFEST.json +2 -2
- package/lib/vendor/blamejs/.github/workflows/ci.yml +27 -0
- package/lib/vendor/blamejs/.github/workflows/codeql.yml +2 -2
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +4 -4
- package/lib/vendor/blamejs/.github/workflows/scorecard.yml +1 -1
- package/lib/vendor/blamejs/CHANGELOG.md +2 -0
- package/lib/vendor/blamejs/README.md +1 -0
- package/lib/vendor/blamejs/SECURITY.md +2 -0
- package/lib/vendor/blamejs/api-snapshot.json +6 -2
- package/lib/vendor/blamejs/lib/cra-report.js +3 -3
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +20 -7
- package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +36 -35
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +17 -5
- package/lib/vendor/blamejs/lib/middleware/cors.js +28 -12
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +22 -14
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +27 -13
- package/lib/vendor/blamejs/lib/middleware/deny-response.js +140 -0
- package/lib/vendor/blamejs/lib/middleware/dpop.js +32 -19
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +21 -12
- package/lib/vendor/blamejs/lib/middleware/host-allowlist.js +19 -8
- package/lib/vendor/blamejs/lib/middleware/index.js +3 -0
- package/lib/vendor/blamejs/lib/middleware/network-allowlist.js +24 -10
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +22 -5
- package/lib/vendor/blamejs/lib/middleware/require-aal.js +25 -10
- package/lib/vendor/blamejs/lib/middleware/require-auth.js +32 -16
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +49 -18
- package/lib/vendor/blamejs/lib/middleware/require-content-type.js +19 -8
- package/lib/vendor/blamejs/lib/middleware/require-methods.js +17 -7
- package/lib/vendor/blamejs/lib/middleware/require-mtls.js +27 -14
- package/lib/vendor/blamejs/lib/network.js +4 -4
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.14.6.json +60 -0
- package/lib/vendor/blamejs/scripts/check-actions-currency.js +256 -0
- package/lib/vendor/blamejs/scripts/release.js +11 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +138 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/deny-response.test.js +228 -0
- package/package.json +1 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Deny-path response convention — every access-refusal middleware
|
|
4
|
+
* routes its refusal through lib/middleware/deny-response.js, giving a
|
|
5
|
+
* consumer one uniform way to shape it:
|
|
6
|
+
*
|
|
7
|
+
* - default — the middleware's existing body + Content-Type
|
|
8
|
+
* (no behavior change)
|
|
9
|
+
* - problemDetails:true — RFC 9457 application/problem+json
|
|
10
|
+
* - onDeny(req,res,info) — the consumer fully owns the response; a
|
|
11
|
+
* throwing or no-op hook falls through to the
|
|
12
|
+
* default rather than hanging the request
|
|
13
|
+
*
|
|
14
|
+
* The deny-path response headers (Allow / WWW-Authenticate /
|
|
15
|
+
* Retry-After / Accept) survive every mode.
|
|
16
|
+
*
|
|
17
|
+
* The shared b.testing mocks don't capture setHeader + writeHead body
|
|
18
|
+
* + writableEnded together (problem mode writes via setHeader; default
|
|
19
|
+
* mode via writeHead), so — like require-auth-cache-control.test.js —
|
|
20
|
+
* this file rolls one complete response mock.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
var helpers = require("../helpers");
|
|
24
|
+
var b = helpers.b;
|
|
25
|
+
var check = helpers.check;
|
|
26
|
+
|
|
27
|
+
function _mkRes() {
|
|
28
|
+
var hdrs = {};
|
|
29
|
+
return {
|
|
30
|
+
statusCode: 0,
|
|
31
|
+
headersSent: false,
|
|
32
|
+
writableEnded: false,
|
|
33
|
+
body: null,
|
|
34
|
+
setHeader: function (k, v) { hdrs[k.toLowerCase()] = v; },
|
|
35
|
+
getHeader: function (k) { return hdrs[k.toLowerCase()]; },
|
|
36
|
+
writeHead: function (sc, h) {
|
|
37
|
+
this.statusCode = sc;
|
|
38
|
+
this.headersSent = true;
|
|
39
|
+
if (h && typeof h === "object") {
|
|
40
|
+
for (var k in h) if (Object.prototype.hasOwnProperty.call(h, k)) {
|
|
41
|
+
hdrs[k.toLowerCase()] = h[k];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return this;
|
|
45
|
+
},
|
|
46
|
+
end: function (x) {
|
|
47
|
+
if (x !== undefined && x !== null) this.body = x;
|
|
48
|
+
this.writableEnded = true;
|
|
49
|
+
},
|
|
50
|
+
_hdrs: hdrs,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function _mkReq(opts) {
|
|
55
|
+
opts = opts || {};
|
|
56
|
+
return {
|
|
57
|
+
method: opts.method || "GET",
|
|
58
|
+
url: opts.url || "/x",
|
|
59
|
+
pathname: opts.pathname || "/x",
|
|
60
|
+
headers: opts.headers || {},
|
|
61
|
+
socket: { remoteAddress: "127.0.0.1", encrypted: false },
|
|
62
|
+
connection: {},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function _json(s) { try { return JSON.parse(s); } catch (_e) { return {}; } }
|
|
67
|
+
|
|
68
|
+
async function _drive(mw, req) {
|
|
69
|
+
var res = _mkRes();
|
|
70
|
+
await mw(req, res, function () { res._nextCalled = true; });
|
|
71
|
+
return res;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function run() {
|
|
75
|
+
var denyResponse = require("../../lib/middleware/deny-response").denyResponse;
|
|
76
|
+
|
|
77
|
+
// ---- Helper unit: the three resolution modes ----
|
|
78
|
+
(function () {
|
|
79
|
+
// default mode
|
|
80
|
+
var res = _mkRes();
|
|
81
|
+
denyResponse(_mkReq(), res, {
|
|
82
|
+
status: 403, contentType: "text/plain", body: "no",
|
|
83
|
+
headers: { "Allow": "GET" },
|
|
84
|
+
info: { status: 403, reason: "x" },
|
|
85
|
+
problemTitle: "Forbidden",
|
|
86
|
+
});
|
|
87
|
+
check("denyResponse default: status + content-type + body + extra header",
|
|
88
|
+
res.statusCode === 403 && res._hdrs["content-type"] === "text/plain" &&
|
|
89
|
+
res.body === "no" && res._hdrs["allow"] === "GET");
|
|
90
|
+
|
|
91
|
+
// problem mode
|
|
92
|
+
var res2 = _mkRes();
|
|
93
|
+
denyResponse(_mkReq(), res2, {
|
|
94
|
+
problem: true, status: 403, contentType: "text/plain", body: "no",
|
|
95
|
+
headers: { "Allow": "GET" }, problemCode: "x-refused", problemTitle: "Forbidden",
|
|
96
|
+
problemDetail: "nope", info: { status: 403, reason: "x" },
|
|
97
|
+
});
|
|
98
|
+
var p = _json(res2.body);
|
|
99
|
+
check("denyResponse problem: application/problem+json + RFC 9457 fields + extra header survives",
|
|
100
|
+
res2.statusCode === 403 && res2._hdrs["content-type"] === "application/problem+json" &&
|
|
101
|
+
p.status === 403 && p.title === "Forbidden" && p.detail === "nope" &&
|
|
102
|
+
typeof p.type === "string" && res2._hdrs["allow"] === "GET");
|
|
103
|
+
|
|
104
|
+
// onDeny owns
|
|
105
|
+
var seen = null;
|
|
106
|
+
var res3 = _mkRes();
|
|
107
|
+
denyResponse(_mkReq(), res3, {
|
|
108
|
+
onDeny: function (req, rs, info) { seen = info; rs.writeHead(418, {}); rs.end("teapot"); },
|
|
109
|
+
status: 403, contentType: "text/plain", body: "no", info: { status: 403, reason: "x" },
|
|
110
|
+
});
|
|
111
|
+
check("denyResponse onDeny owns response + receives info",
|
|
112
|
+
res3.statusCode === 418 && res3.body === "teapot" && seen && seen.reason === "x");
|
|
113
|
+
|
|
114
|
+
// onDeny throws -> falls through to default (audited via onThrow)
|
|
115
|
+
var threw = false;
|
|
116
|
+
var res4 = _mkRes();
|
|
117
|
+
denyResponse(_mkReq(), res4, {
|
|
118
|
+
onDeny: function () { throw new Error("boom"); },
|
|
119
|
+
onThrow: function () { threw = true; },
|
|
120
|
+
status: 403, contentType: "text/plain", body: "default-after-throw",
|
|
121
|
+
info: { status: 403, reason: "x" },
|
|
122
|
+
});
|
|
123
|
+
check("denyResponse onDeny throws -> default written + onThrow audited",
|
|
124
|
+
res4.statusCode === 403 && res4.body === "default-after-throw" && threw === true);
|
|
125
|
+
|
|
126
|
+
// onDeny no-op (doesn't write) -> falls through to default
|
|
127
|
+
var res5 = _mkRes();
|
|
128
|
+
denyResponse(_mkReq(), res5, {
|
|
129
|
+
onDeny: function () { /* returns without writing */ },
|
|
130
|
+
status: 403, contentType: "text/plain", body: "default-after-noop",
|
|
131
|
+
info: { status: 403, reason: "x" },
|
|
132
|
+
});
|
|
133
|
+
check("denyResponse onDeny no-op -> default written (no hang)",
|
|
134
|
+
res5.statusCode === 403 && res5.body === "default-after-noop");
|
|
135
|
+
})();
|
|
136
|
+
|
|
137
|
+
// ---- require-methods (405 / text/plain) ----
|
|
138
|
+
(function () {
|
|
139
|
+
var def = _mkRes();
|
|
140
|
+
b.middleware.requireMethods(["GET"])(_mkReq({ method: "DELETE" }), def, function () {});
|
|
141
|
+
check("requireMethods default 405 text/plain + Allow",
|
|
142
|
+
def.statusCode === 405 && def._hdrs["content-type"] === "text/plain; charset=utf-8" &&
|
|
143
|
+
def._hdrs["allow"] === "GET" && def.body === "Method Not Allowed");
|
|
144
|
+
|
|
145
|
+
var pj = _mkRes();
|
|
146
|
+
b.middleware.requireMethods(["GET"], { problemDetails: true })(_mkReq({ method: "DELETE" }), pj, function () {});
|
|
147
|
+
check("requireMethods problemDetails 405 problem+json + Allow survives",
|
|
148
|
+
pj.statusCode === 405 && pj._hdrs["content-type"] === "application/problem+json" &&
|
|
149
|
+
pj._hdrs["allow"] === "GET" && _json(pj.body).status === 405);
|
|
150
|
+
})();
|
|
151
|
+
|
|
152
|
+
// ---- rate-limit (429) — X-RateLimit-* + Retry-After survive problem mode ----
|
|
153
|
+
(function () {
|
|
154
|
+
var mw = b.middleware.rateLimit({ backend: "memory", algorithm: "fixed-window", limit: 1, windowMs: 60000, problemDetails: true });
|
|
155
|
+
var req = _mkReq();
|
|
156
|
+
mw(req, _mkRes(), function () {});
|
|
157
|
+
var res = _mkRes();
|
|
158
|
+
mw(req, res, function () {});
|
|
159
|
+
check("rateLimit problemDetails 429 problem+json + X-RateLimit-Limit survives",
|
|
160
|
+
res.statusCode === 429 && res._hdrs["content-type"] === "application/problem+json" &&
|
|
161
|
+
res._hdrs["x-ratelimit-limit"] === "1" && _json(res.body).status === 429);
|
|
162
|
+
})();
|
|
163
|
+
|
|
164
|
+
// ---- age-gate (451 / JSON envelope default) ----
|
|
165
|
+
(function () {
|
|
166
|
+
var opts = { getAge: function () { return 5; }, requireAge: 13, consentRequired: 13 };
|
|
167
|
+
var def = _mkRes();
|
|
168
|
+
b.middleware.ageGate(opts)(_mkReq(), def, function () {});
|
|
169
|
+
check("ageGate default 451 JSON envelope",
|
|
170
|
+
def.statusCode === 451 && def._hdrs["content-type"] === "application/json; charset=utf-8" &&
|
|
171
|
+
_json(def.body).parentalConsent === false);
|
|
172
|
+
|
|
173
|
+
var pj = _mkRes();
|
|
174
|
+
b.middleware.ageGate(Object.assign({ problemDetails: true }, opts))(_mkReq(), pj, function () {});
|
|
175
|
+
check("ageGate problemDetails 451 problem+json + requireAge extension",
|
|
176
|
+
pj.statusCode === 451 && pj._hdrs["content-type"] === "application/problem+json" &&
|
|
177
|
+
_json(pj.body).requireAge === 13);
|
|
178
|
+
})();
|
|
179
|
+
|
|
180
|
+
// ---- cors (403) ----
|
|
181
|
+
(function () {
|
|
182
|
+
var mw = b.middleware.cors({ origins: ["https://ok.example"], problemDetails: true });
|
|
183
|
+
var res = _mkRes();
|
|
184
|
+
mw(_mkReq({ headers: { origin: "https://evil.example" } }), res, function () {});
|
|
185
|
+
check("cors problemDetails 403 problem+json on disallowed origin",
|
|
186
|
+
res.statusCode === 403 && res._hdrs["content-type"] === "application/problem+json");
|
|
187
|
+
})();
|
|
188
|
+
|
|
189
|
+
// ---- require-bound-key — now reachable on b.middleware + RFC 6750 challenge ----
|
|
190
|
+
(function () {
|
|
191
|
+
check("requireBoundKey reachable on b.middleware (was unwired)",
|
|
192
|
+
typeof b.middleware.requireBoundKey === "function");
|
|
193
|
+
})();
|
|
194
|
+
|
|
195
|
+
await (async function () {
|
|
196
|
+
var mw = b.middleware.requireBoundKey({
|
|
197
|
+
resolver: async function () { return { id: "k1", scopes: ["other"], boundFields: {} }; },
|
|
198
|
+
requiredScopes: ["needed"],
|
|
199
|
+
});
|
|
200
|
+
var scope = await _drive(mw, _mkReq({ headers: { authorization: "Bearer abc" } }));
|
|
201
|
+
check("requireBoundKey 403 missing-scope -> WWW-Authenticate insufficient_scope (RFC 6750)",
|
|
202
|
+
scope.statusCode === 403 && /insufficient_scope/.test(scope._hdrs["www-authenticate"] || ""));
|
|
203
|
+
var noTok = await _drive(mw, _mkReq());
|
|
204
|
+
check("requireBoundKey 401 no-token omits error code (RFC 6750 §3)",
|
|
205
|
+
noTok.statusCode === 401 && noTok._hdrs["www-authenticate"] === 'Bearer realm="api"');
|
|
206
|
+
})();
|
|
207
|
+
|
|
208
|
+
// ---- bearer-auth — requiredScopes now accepted (was rejected by validateOpts) ----
|
|
209
|
+
await (async function () {
|
|
210
|
+
var mw = b.middleware.bearerAuth({
|
|
211
|
+
verify: async function () { return { id: 1, scopes: ["read"] }; },
|
|
212
|
+
requiredScopes: ["admin"],
|
|
213
|
+
});
|
|
214
|
+
var res = await _drive(mw, _mkReq({ headers: { authorization: "Bearer good" } }));
|
|
215
|
+
check("bearerAuth requiredScopes reachable -> 403 insufficient_scope",
|
|
216
|
+
res.statusCode === 403 && /insufficient_scope/.test(res._hdrs["www-authenticate"] || "") &&
|
|
217
|
+
_json(res.body).required[0] === "admin");
|
|
218
|
+
})();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
module.exports = { run: run };
|
|
222
|
+
|
|
223
|
+
if (require.main === module) {
|
|
224
|
+
run().then(
|
|
225
|
+
function () { console.log("OK — " + helpers.getChecks() + " checks passed"); },
|
|
226
|
+
function (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
|
|
227
|
+
);
|
|
228
|
+
}
|
package/package.json
CHANGED