@blamejs/core 0.7.0 → 0.7.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 +8 -0
- package/README.md +2 -2
- package/index.js +2 -0
- package/lib/api-key.js +1 -10
- package/lib/cache-redis.js +1 -11
- package/lib/cache.js +11 -16
- package/lib/file-upload.js +933 -0
- package/lib/framework-error.js +13 -0
- package/lib/log-stream-syslog.js +1 -1
- package/lib/middleware/api-encrypt.js +415 -52
- package/lib/middleware/db-role-for.js +3 -8
- package/lib/notify.js +3 -5
- package/lib/object-store/azure-blob.js +42 -5
- package/lib/object-store/gcs.js +47 -7
- package/lib/object-store/sigv4.js +55 -7
- package/lib/pubsub-redis.js +1 -11
- package/lib/queue-redis.js +1 -8
- package/lib/redis-client.js +30 -0
- package/lib/request-helpers.js +21 -17
- package/lib/seeders.js +5 -17
- package/lib/static.js +699 -114
- package/lib/validate-opts.js +49 -0
- package/lib/webhook.js +3 -6
- package/lib/websocket.js +38 -6
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/lib/validate-opts.js
CHANGED
|
@@ -219,6 +219,53 @@ function requireNonEmptyString(value, label, errorClass, code) {
|
|
|
219
219
|
return value;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
// optionalNonEmptyStringArray — required-shape validator for optional
|
|
223
|
+
// array-of-non-empty-strings opts (scopes / allowedFileTypes / dependsOn /
|
|
224
|
+
// rtlLanguages / eagerLocales / etc.). Replaces the recurring four-line
|
|
225
|
+
// cascade `if (opts.X !== undefined) { if (!Array.isArray) throw; for
|
|
226
|
+
// (i...) if (typeof !== "string" || === "") throw }` that 5+ primitives
|
|
227
|
+
// previously rolled by hand.
|
|
228
|
+
//
|
|
229
|
+
// undefined / null → returns the value unchanged (caller can default).
|
|
230
|
+
// non-array → throws via errorClass with the provided code.
|
|
231
|
+
// any non-string or empty-string element → throws with index-pointing message.
|
|
232
|
+
function optionalNonEmptyStringArray(value, label, errorClass, code) {
|
|
233
|
+
if (value === undefined || value === null) return value;
|
|
234
|
+
if (!Array.isArray(value)) {
|
|
235
|
+
_throw(errorClass, code, (label || "opt") +
|
|
236
|
+
" must be an array of non-empty strings, got " + typeof value,
|
|
237
|
+
"validate-opts/bad-string-array");
|
|
238
|
+
}
|
|
239
|
+
for (var i = 0; i < value.length; i += 1) {
|
|
240
|
+
if (typeof value[i] !== "string" || value[i].length === 0) {
|
|
241
|
+
_throw(errorClass, code, (label || "opt") +
|
|
242
|
+
"[" + i + "] must be a non-empty string",
|
|
243
|
+
"validate-opts/bad-string-array-element");
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return value;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// optionalObjectWithMethod — required-shape validator for optional opts
|
|
250
|
+
// that accept a "duck-typed handle": an object that exposes a specific
|
|
251
|
+
// method. Replaces the recurring `if (opts.X !== undefined && opts.X !==
|
|
252
|
+
// null) { if (typeof opts.X !== "object" || typeof opts.X.method !==
|
|
253
|
+
// "function") throw ... }` cascade shared by file-upload (permissions),
|
|
254
|
+
// notify (queue), seeders (db), webhook (nonceStore), and others.
|
|
255
|
+
//
|
|
256
|
+
// undefined / null → returns the value unchanged (caller can default).
|
|
257
|
+
// non-object OR missing method → throws via errorClass with the operator-
|
|
258
|
+
// facing description (e.g. "must be a b.permissions instance (check fn)").
|
|
259
|
+
function optionalObjectWithMethod(value, method, label, errorClass, code, description) {
|
|
260
|
+
if (value === undefined || value === null) return value;
|
|
261
|
+
if (typeof value !== "object" || typeof value[method] !== "function") {
|
|
262
|
+
_throw(errorClass, code, (label || "opt") + " " +
|
|
263
|
+
(description || ("must expose " + method + "() method")),
|
|
264
|
+
"validate-opts/bad-shaped-handle");
|
|
265
|
+
}
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
268
|
+
|
|
222
269
|
// makeAuditEmitter — closure factory parallel to safeAsync.makeDropCallback.
|
|
223
270
|
// Replaces the per-file `function _emit(action, info) { if (!audit) return;
|
|
224
271
|
// try { audit.safeEmit(Object.assign({ action: action }, info || {})); }
|
|
@@ -263,6 +310,8 @@ module.exports.optionalFiniteNonNegative = optionalFiniteNonNegative;
|
|
|
263
310
|
module.exports.optionalPositiveFinite = optionalPositiveFinite;
|
|
264
311
|
module.exports.optionalFunction = optionalFunction;
|
|
265
312
|
module.exports.optionalNonEmptyString = optionalNonEmptyString;
|
|
313
|
+
module.exports.optionalNonEmptyStringArray = optionalNonEmptyStringArray;
|
|
314
|
+
module.exports.optionalObjectWithMethod = optionalObjectWithMethod;
|
|
266
315
|
module.exports.requireNonEmptyString = requireNonEmptyString;
|
|
267
316
|
module.exports.observabilityShape = observabilityShape;
|
|
268
317
|
module.exports.requireObject = requireObject;
|
package/lib/webhook.js
CHANGED
|
@@ -408,12 +408,9 @@ function _validateVerifierOpts(opts) {
|
|
|
408
408
|
validateOpts.optionalFiniteNonNegative(opts.toleranceMs, "webhook.verifier: toleranceMs", WebhookError);
|
|
409
409
|
validateOpts.optionalFiniteNonNegative(opts.clockSkewMs, "webhook.verifier: clockSkewMs", WebhookError);
|
|
410
410
|
validateOpts.optionalNonEmptyString(opts.signatureHeader, "webhook.verifier: signatureHeader", WebhookError);
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
throw _err("BAD_OPT", "webhook.verifier: nonceStore must implement checkAndInsert(nonce, expireAt)");
|
|
415
|
-
}
|
|
416
|
-
}
|
|
411
|
+
validateOpts.optionalObjectWithMethod(opts.nonceStore, "checkAndInsert",
|
|
412
|
+
"webhook.verifier: nonceStore", WebhookError, "BAD_OPT",
|
|
413
|
+
"must implement checkAndInsert(nonce, expireAt)");
|
|
417
414
|
validateOpts.optionalFunction(opts.now, "webhook.verifier: now", WebhookError);
|
|
418
415
|
validateOpts.auditShape(opts.audit, "webhook.verifier", WebhookError);
|
|
419
416
|
validateOpts.optionalBoolean(opts.auditFailures, "webhook.verifier: auditFailures", WebhookError);
|
package/lib/websocket.js
CHANGED
|
@@ -94,9 +94,20 @@ var { boot } = require("./log");
|
|
|
94
94
|
var HTTP = requestHelpers.HTTP_STATUS;
|
|
95
95
|
var log = boot("websocket");
|
|
96
96
|
|
|
97
|
-
// RFC 6455 §1.3
|
|
97
|
+
// RFC 6455 §1.3 — the standard handshake GUID. Operators running
|
|
98
|
+
// closed-ecosystem clients with a custom magic string pass their own
|
|
99
|
+
// via opts.handshakeGuid on the route; the framework's default stays
|
|
100
|
+
// the RFC value so RFC-compliant clients work out of the box.
|
|
98
101
|
var GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
99
102
|
|
|
103
|
+
// UUID-shape (8-4-4-4-12 hex) for opts.handshakeGuid validation. The
|
|
104
|
+
// SHA-1 used in the handshake is NOT a security primitive (RFC 6455
|
|
105
|
+
// requires it as a protocol marker), so the GUID itself doesn't need
|
|
106
|
+
// to be cryptographically random — but it must match the client's
|
|
107
|
+
// expected value byte-for-byte. Length + format check at config time
|
|
108
|
+
// catches the typo class.
|
|
109
|
+
var GUID_RE = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/;
|
|
110
|
+
|
|
100
111
|
var OPCODE_CONTINUATION = 0x0;
|
|
101
112
|
var OPCODE_TEXT = 0x1;
|
|
102
113
|
var OPCODE_BINARY = 0x2;
|
|
@@ -165,11 +176,13 @@ class WebSocketError extends FrameworkError {
|
|
|
165
176
|
|
|
166
177
|
// ---- Handshake helpers ----
|
|
167
178
|
|
|
168
|
-
function computeAcceptKey(secWebSocketKey) {
|
|
179
|
+
function computeAcceptKey(secWebSocketKey, handshakeGuid) {
|
|
169
180
|
// SHA-1 required by RFC 6455 §1.3 — see file-level note 2 above.
|
|
170
181
|
// This is a protocol marker, not a security primitive.
|
|
182
|
+
// handshakeGuid defaults to the RFC value; operators with custom
|
|
183
|
+
// closed-ecosystem clients override per-route via opts.handshakeGuid.
|
|
171
184
|
var hash = nodeCrypto.createHash("sha1");
|
|
172
|
-
hash.update(String(secWebSocketKey) + GUID);
|
|
185
|
+
hash.update(String(secWebSocketKey) + (handshakeGuid || GUID));
|
|
173
186
|
return hash.digest("base64");
|
|
174
187
|
}
|
|
175
188
|
|
|
@@ -224,12 +237,12 @@ function isOriginAllowed(req, origins) {
|
|
|
224
237
|
return false;
|
|
225
238
|
}
|
|
226
239
|
|
|
227
|
-
function buildUpgradeResponse(secWebSocketKey, subprotocol, extensionHeader) {
|
|
240
|
+
function buildUpgradeResponse(secWebSocketKey, subprotocol, extensionHeader, handshakeGuid) {
|
|
228
241
|
var lines = [
|
|
229
242
|
"HTTP/1.1 101 Switching Protocols",
|
|
230
243
|
"Upgrade: websocket",
|
|
231
244
|
"Connection: Upgrade",
|
|
232
|
-
"Sec-WebSocket-Accept: " + computeAcceptKey(secWebSocketKey),
|
|
245
|
+
"Sec-WebSocket-Accept: " + computeAcceptKey(secWebSocketKey, handshakeGuid),
|
|
233
246
|
];
|
|
234
247
|
if (subprotocol) lines.push("Sec-WebSocket-Protocol: " + subprotocol);
|
|
235
248
|
if (extensionHeader) lines.push("Sec-WebSocket-Extensions: " + extensionHeader);
|
|
@@ -834,6 +847,25 @@ class WebSocketConnection extends EventEmitter {
|
|
|
834
847
|
function handleUpgrade(req, socket, head, opts) {
|
|
835
848
|
opts = opts || {};
|
|
836
849
|
|
|
850
|
+
// Throw-at-config-time on the optional GUID override. A typo here
|
|
851
|
+
// would produce a Sec-WebSocket-Accept the client can't match,
|
|
852
|
+
// breaking the upgrade in a way that's hard to diagnose; the format
|
|
853
|
+
// check at the top of handleUpgrade catches it loudly. Empty /
|
|
854
|
+
// undefined falls through to the RFC default in computeAcceptKey.
|
|
855
|
+
var GUID_MAX_LENGTH = C.BYTES.bytes(64); // allow:raw-byte-literal — UUID is 36 chars; 64 is a tolerant upper bound for the regex engine.
|
|
856
|
+
if (opts.handshakeGuid !== undefined && opts.handshakeGuid !== null) {
|
|
857
|
+
// Length cap before the regex test — UUIDs are exactly 36 chars so
|
|
858
|
+
// a > GUID_MAX_LENGTH input never matches the format and shouldn't
|
|
859
|
+
// reach the regex engine. Bounds the engine on hostile input
|
|
860
|
+
// regardless of the GUID_RE shape.
|
|
861
|
+
if (typeof opts.handshakeGuid !== "string" ||
|
|
862
|
+
opts.handshakeGuid.length > GUID_MAX_LENGTH ||
|
|
863
|
+
!GUID_RE.test(opts.handshakeGuid)) {
|
|
864
|
+
throw new Error("websocket.handleUpgrade: handshakeGuid must be a UUID-shaped string (8-4-4-4-12 hex with dashes), got " +
|
|
865
|
+
JSON.stringify(opts.handshakeGuid));
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
837
869
|
// Validate handshake first — refusing here writes a plain HTTP/1.1
|
|
838
870
|
// response and closes the socket, matching what the upgrade-event
|
|
839
871
|
// consumer would expect for a malformed request.
|
|
@@ -865,7 +897,7 @@ function handleUpgrade(req, socket, head, opts) {
|
|
|
865
897
|
try {
|
|
866
898
|
socket.write(buildUpgradeResponse(
|
|
867
899
|
req.headers["sec-websocket-key"], subprotocol,
|
|
868
|
-
pmd ? pmd.responseHeader : null));
|
|
900
|
+
pmd ? pmd.responseHeader : null, opts.handshakeGuid));
|
|
869
901
|
} catch (err) {
|
|
870
902
|
log.error("failed to write upgrade response: " + err.message);
|
|
871
903
|
try { socket.destroy(); } catch (_e) { /* socket already destroyed */ }
|
package/package.json
CHANGED
package/sbom.cyclonedx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:ddc45497-46e6-4b4b-94ee-8cb0849c82cd",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-04T16:54:01.026Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.7.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.7.4",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.7.
|
|
25
|
+
"version": "0.7.4",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.7.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.7.4",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.7.
|
|
57
|
+
"ref": "@blamejs/core@0.7.4",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|