@blamejs/core 0.8.43 → 0.8.49
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 +92 -0
- package/README.md +10 -10
- package/index.js +52 -0
- package/lib/a2a.js +159 -34
- package/lib/acme.js +762 -0
- package/lib/ai-pref.js +166 -43
- package/lib/api-key.js +108 -47
- package/lib/api-snapshot.js +157 -40
- package/lib/app-shutdown.js +113 -77
- package/lib/archive.js +337 -40
- package/lib/arg-parser.js +697 -0
- package/lib/asyncapi.js +99 -55
- package/lib/atomic-file.js +465 -104
- package/lib/audit-chain.js +123 -34
- package/lib/audit-daily-review.js +389 -0
- package/lib/audit-sign.js +302 -56
- package/lib/audit-tools.js +412 -63
- package/lib/audit.js +656 -35
- package/lib/auth/jwt-external.js +17 -0
- package/lib/auth/oauth.js +7 -0
- package/lib/auth-bot-challenge.js +505 -0
- package/lib/auth-header.js +92 -25
- package/lib/backup/bundle.js +26 -0
- package/lib/backup/index.js +512 -89
- package/lib/backup/manifest.js +168 -7
- package/lib/break-glass.js +415 -39
- package/lib/budr.js +103 -30
- package/lib/bundler.js +86 -66
- package/lib/cache.js +192 -72
- package/lib/chain-writer.js +65 -40
- package/lib/circuit-breaker.js +56 -33
- package/lib/cli-helpers.js +106 -75
- package/lib/cli.js +6 -30
- package/lib/cloud-events.js +99 -32
- package/lib/cluster-storage.js +162 -37
- package/lib/cluster.js +340 -49
- package/lib/codepoint-class.js +66 -0
- package/lib/compliance.js +424 -24
- package/lib/config-drift.js +111 -46
- package/lib/config.js +94 -40
- package/lib/consent.js +165 -18
- package/lib/constants.js +1 -0
- package/lib/content-credentials.js +153 -48
- package/lib/cookies.js +154 -62
- package/lib/credential-hash.js +133 -61
- package/lib/crypto-field.js +702 -18
- package/lib/crypto-hpke.js +256 -0
- package/lib/crypto.js +744 -22
- package/lib/csv.js +178 -35
- package/lib/daemon.js +456 -0
- package/lib/dark-patterns.js +186 -55
- package/lib/db-query.js +79 -2
- package/lib/db.js +1431 -60
- package/lib/ddl-change-control.js +523 -0
- package/lib/deprecate.js +195 -40
- package/lib/dev.js +82 -39
- package/lib/dora.js +67 -48
- package/lib/dr-runbook.js +368 -0
- package/lib/dsr.js +142 -11
- package/lib/dual-control.js +91 -56
- package/lib/events.js +120 -41
- package/lib/external-db-migrate.js +192 -2
- package/lib/external-db.js +795 -50
- package/lib/fapi2.js +122 -1
- package/lib/fda-21cfr11.js +395 -0
- package/lib/fdx.js +132 -2
- package/lib/file-type.js +87 -0
- package/lib/file-upload.js +93 -0
- package/lib/flag.js +82 -20
- package/lib/forms.js +132 -29
- package/lib/framework-error.js +169 -0
- package/lib/framework-schema.js +163 -35
- package/lib/gate-contract.js +849 -175
- package/lib/graphql-federation.js +68 -7
- package/lib/guard-all.js +172 -55
- package/lib/guard-archive.js +286 -124
- package/lib/guard-auth.js +194 -21
- package/lib/guard-cidr.js +190 -28
- package/lib/guard-csv.js +397 -51
- package/lib/guard-domain.js +213 -91
- package/lib/guard-email.js +236 -29
- package/lib/guard-filename.js +307 -75
- package/lib/guard-graphql.js +263 -30
- package/lib/guard-html.js +310 -116
- package/lib/guard-image.js +243 -30
- package/lib/guard-json.js +260 -54
- package/lib/guard-jsonpath.js +235 -23
- package/lib/guard-jwt.js +284 -30
- package/lib/guard-markdown.js +204 -22
- package/lib/guard-mime.js +190 -26
- package/lib/guard-oauth.js +277 -28
- package/lib/guard-pdf.js +251 -27
- package/lib/guard-regex.js +226 -18
- package/lib/guard-shell.js +229 -26
- package/lib/guard-svg.js +177 -10
- package/lib/guard-template.js +232 -21
- package/lib/guard-time.js +195 -29
- package/lib/guard-uuid.js +189 -30
- package/lib/guard-xml.js +259 -36
- package/lib/guard-yaml.js +241 -44
- package/lib/honeytoken.js +63 -27
- package/lib/html-balance.js +83 -0
- package/lib/http-client.js +486 -59
- package/lib/http-message-signature.js +582 -0
- package/lib/i18n.js +102 -49
- package/lib/iab-mspa.js +112 -32
- package/lib/iab-tcf.js +107 -2
- package/lib/inbox.js +90 -52
- package/lib/keychain.js +865 -0
- package/lib/legal-hold.js +374 -0
- package/lib/local-db-thin.js +320 -0
- package/lib/log-stream.js +281 -51
- package/lib/log.js +184 -86
- package/lib/mail-bounce.js +107 -62
- package/lib/mail.js +295 -58
- package/lib/mcp.js +108 -27
- package/lib/metrics.js +98 -89
- package/lib/middleware/age-gate.js +36 -0
- package/lib/middleware/ai-act-disclosure.js +37 -0
- package/lib/middleware/api-encrypt.js +45 -0
- package/lib/middleware/assetlinks.js +40 -0
- package/lib/middleware/asyncapi-serve.js +35 -0
- package/lib/middleware/attach-user.js +40 -0
- package/lib/middleware/bearer-auth.js +40 -0
- package/lib/middleware/body-parser.js +230 -0
- package/lib/middleware/bot-disclose.js +34 -0
- package/lib/middleware/bot-guard.js +39 -0
- package/lib/middleware/compression.js +37 -0
- package/lib/middleware/cookies.js +32 -0
- package/lib/middleware/cors.js +40 -0
- package/lib/middleware/csp-nonce.js +40 -0
- package/lib/middleware/csp-report.js +34 -0
- package/lib/middleware/csrf-protect.js +43 -0
- package/lib/middleware/daily-byte-quota.js +53 -85
- package/lib/middleware/db-role-for.js +40 -0
- package/lib/middleware/dpop.js +40 -0
- package/lib/middleware/error-handler.js +37 -14
- package/lib/middleware/fetch-metadata.js +39 -0
- package/lib/middleware/flag-context.js +34 -0
- package/lib/middleware/gpc.js +33 -0
- package/lib/middleware/headers.js +35 -0
- package/lib/middleware/health.js +46 -0
- package/lib/middleware/host-allowlist.js +30 -0
- package/lib/middleware/network-allowlist.js +38 -0
- package/lib/middleware/openapi-serve.js +34 -0
- package/lib/middleware/rate-limit.js +160 -18
- package/lib/middleware/request-id.js +36 -18
- package/lib/middleware/request-log.js +37 -0
- package/lib/middleware/require-aal.js +29 -0
- package/lib/middleware/require-auth.js +32 -0
- package/lib/middleware/require-bound-key.js +41 -0
- package/lib/middleware/require-content-type.js +32 -0
- package/lib/middleware/require-methods.js +27 -0
- package/lib/middleware/require-mtls.js +33 -0
- package/lib/middleware/require-step-up.js +37 -0
- package/lib/middleware/security-headers.js +44 -0
- package/lib/middleware/security-txt.js +38 -0
- package/lib/middleware/span-http-server.js +37 -0
- package/lib/middleware/sse.js +36 -0
- package/lib/middleware/trace-log-correlation.js +33 -0
- package/lib/middleware/trace-propagate.js +32 -0
- package/lib/middleware/tus-upload.js +90 -0
- package/lib/middleware/web-app-manifest.js +53 -0
- package/lib/mtls-ca.js +100 -70
- package/lib/network-byte-quota.js +308 -0
- package/lib/network-heartbeat.js +135 -0
- package/lib/network-tls.js +534 -4
- package/lib/network.js +103 -0
- package/lib/notify.js +114 -43
- package/lib/ntp-check.js +192 -51
- package/lib/observability.js +145 -47
- package/lib/openapi.js +90 -44
- package/lib/outbox.js +99 -1
- package/lib/pagination.js +168 -86
- package/lib/parsers/index.js +16 -5
- package/lib/permissions.js +93 -40
- package/lib/pqc-agent.js +84 -8
- package/lib/pqc-software.js +94 -60
- package/lib/process-spawn.js +95 -21
- package/lib/pubsub.js +96 -66
- package/lib/queue.js +375 -54
- package/lib/redact.js +793 -21
- package/lib/render.js +139 -47
- package/lib/request-helpers.js +485 -121
- package/lib/restore-bundle.js +142 -39
- package/lib/restore-rollback.js +136 -45
- package/lib/retention.js +178 -50
- package/lib/retry.js +116 -33
- package/lib/router.js +475 -23
- package/lib/safe-async.js +543 -94
- package/lib/safe-buffer.js +337 -41
- package/lib/safe-json.js +467 -62
- package/lib/safe-jsonpath.js +285 -0
- package/lib/safe-schema.js +631 -87
- package/lib/safe-sql.js +221 -59
- package/lib/safe-url.js +278 -46
- package/lib/sandbox-worker.js +135 -0
- package/lib/sandbox.js +358 -0
- package/lib/scheduler.js +135 -70
- package/lib/self-update.js +647 -0
- package/lib/session-device-binding.js +431 -0
- package/lib/session.js +259 -49
- package/lib/slug.js +138 -26
- package/lib/ssrf-guard.js +316 -56
- package/lib/storage.js +433 -70
- package/lib/subject.js +405 -23
- package/lib/template.js +148 -8
- package/lib/tenant-quota.js +545 -0
- package/lib/testing.js +440 -53
- package/lib/time.js +291 -23
- package/lib/tls-exporter.js +239 -0
- package/lib/tracing.js +90 -74
- package/lib/uuid.js +97 -22
- package/lib/vault/index.js +284 -22
- package/lib/vault/seal-pem-file.js +66 -0
- package/lib/watcher.js +368 -0
- package/lib/webhook.js +196 -63
- package/lib/websocket.js +393 -68
- package/lib/wiki-concepts.js +338 -0
- package/lib/worker-pool.js +464 -0
- package/package.json +3 -3
- package/sbom.cyclonedx.json +7 -7
package/lib/metrics.js
CHANGED
|
@@ -1,99 +1,40 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.metrics
|
|
4
|
+
* @nav Observability
|
|
5
|
+
* @title Metrics
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* auto-instrumentation already wired into audit / vault / queue
|
|
10
|
-
* hot paths so operators get the framework's vital signs for free.
|
|
7
|
+
* @intro
|
|
8
|
+
* Counter / gauge / histogram primitives in Prometheus 0.0.4 text
|
|
9
|
+
* format with OTLP-friendly labels, plus framework auto-instrumentation
|
|
10
|
+
* wired into audit / vault / queue hot paths.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* `b.metrics.create()` returns a registry — call `counter(name)` /
|
|
13
|
+
* `gauge(name)` / `histogram(name)` to register typed metrics, then
|
|
14
|
+
* `requestMiddleware()` for per-request counter+latency, and
|
|
15
|
+
* `expositionHandler()` for the `/metrics` scrape route. Every metric
|
|
16
|
+
* carries a per-instance `labelCardinalityCap` (default 10,000) — when
|
|
17
|
+
* the next label combination would push past the cap the increment
|
|
18
|
+
* drops and a single warning logs, so a runaway label (request-id,
|
|
19
|
+
* raw URL with query string, per-user dimension) can't OOM the
|
|
20
|
+
* process.
|
|
13
21
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
22
|
+
* Framework modules call `metrics.tap("audit.record", value, labels)`
|
|
23
|
+
* at hot paths. Until a registry is active the call is a zero-cost
|
|
24
|
+
* no-op; once `create()` runs, taps flow into pre-registered
|
|
25
|
+
* counters / gauges (`framework_audit_events_total`,
|
|
26
|
+
* `framework_vault_seal_total`, `framework_queue_depth`,
|
|
27
|
+
* `framework_jobs_inflight`, `framework_errors_total`,
|
|
28
|
+
* `framework_http_requests_total`,
|
|
29
|
+
* `framework_http_request_duration_seconds`).
|
|
19
30
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* requests.inc({ method: "GET", route: "/users", status: "200" });
|
|
25
|
-
* requests.inc({ method: "GET", route: "/users", status: "200" }, 5);
|
|
31
|
+
* Best-practice route labels are the route TEMPLATE
|
|
32
|
+
* (`/users/:id`), not the actual path — `requestMiddleware` reads
|
|
33
|
+
* `req.routePattern` when the matcher set one and falls back to the
|
|
34
|
+
* query-stripped URL otherwise.
|
|
26
35
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* labelNames: ["method", "route"],
|
|
30
|
-
* buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
|
|
31
|
-
* });
|
|
32
|
-
* latency.observe({ method: "GET", route: "/users" }, 0.123);
|
|
33
|
-
*
|
|
34
|
-
* var queueDepth = m.gauge("queue_depth", { labelNames: ["queueName"] });
|
|
35
|
-
* queueDepth.set({ queueName: "default" }, 42);
|
|
36
|
-
* queueDepth.inc({ queueName: "default" });
|
|
37
|
-
* queueDepth.dec({ queueName: "default" });
|
|
38
|
-
*
|
|
39
|
-
* router.use(m.requestMiddleware()); // auto-times every request
|
|
40
|
-
* router.get("/metrics", m.expositionHandler());
|
|
41
|
-
*
|
|
42
|
-
* Framework auto-instrumentation:
|
|
43
|
-
* When metrics.create() runs, framework hot paths (audit.record,
|
|
44
|
-
* vault.seal, vault.unseal, queue ops) call metrics.tap() — a
|
|
45
|
-
* global no-op stub that the active registry replaces with real
|
|
46
|
-
* counters. Modules don't import the registry directly; the tap
|
|
47
|
-
* pattern keeps them decoupled and lets operators with no metrics
|
|
48
|
-
* pay zero cost.
|
|
49
|
-
*
|
|
50
|
-
* Built-in metrics surfaced:
|
|
51
|
-
* framework_audit_events_total{action, outcome} counter
|
|
52
|
-
* framework_vault_seal_total counter
|
|
53
|
-
* framework_vault_unseal_total counter
|
|
54
|
-
* framework_queue_enqueue_total{queueName} counter
|
|
55
|
-
* framework_queue_complete_total{queueName} counter
|
|
56
|
-
* framework_queue_fail_total{queueName} counter
|
|
57
|
-
* framework_queue_depth{queueName} gauge
|
|
58
|
-
* framework_jobs_inflight{queueName} gauge
|
|
59
|
-
* framework_errors_total{class} counter
|
|
60
|
-
* framework_http_requests_total{method,route,status} counter
|
|
61
|
-
* framework_http_request_duration_seconds{method,route} histogram
|
|
62
|
-
*
|
|
63
|
-
* Cardinality control:
|
|
64
|
-
* Every metric has a per-instance ceiling on distinct label
|
|
65
|
-
* combinations (default 10,000). When a request's label set would
|
|
66
|
-
* create the 10,001st unique combination, the increment is dropped
|
|
67
|
-
* and a warning is logged ONCE per metric. The bound is high enough
|
|
68
|
-
* that legitimate apps don't hit it; low enough that runaway
|
|
69
|
-
* labels (a label per request id, per user id, per full URL with
|
|
70
|
-
* query string) can't OOM the process. Operators size up via
|
|
71
|
-
* labelCardinalityCap when they have a legitimate need.
|
|
72
|
-
*
|
|
73
|
-
* Best practice: route labels are the route TEMPLATE (`/users/:id`),
|
|
74
|
-
* not the actual path (`/users/123`). The framework's
|
|
75
|
-
* requestMiddleware uses req.routePattern when set; otherwise falls
|
|
76
|
-
* back to req.url stripped of query string.
|
|
77
|
-
*
|
|
78
|
-
* Exposition format:
|
|
79
|
-
* The text/plain exposition follows the Prometheus 0.0.4 text format:
|
|
80
|
-
* `# HELP <name> <description>` and `# TYPE <name> <counter|gauge|
|
|
81
|
-
* histogram>` headers, one sample per line with serialized labels
|
|
82
|
-
* in `{key="value",key2="value2"}` form. Buckets and _sum / _count
|
|
83
|
-
* for histograms.
|
|
84
|
-
*
|
|
85
|
-
* Out of scope (with structural reasons):
|
|
86
|
-
* - Summary type (client-side quantiles): generally inferior to
|
|
87
|
-
* histogram for aggregation across instances. Prometheus team
|
|
88
|
-
* recommends histogram. Add later if a real demand emerges.
|
|
89
|
-
* - Push gateway: pull-only monitoring is the simpler architecture.
|
|
90
|
-
* Operators with batch jobs that want push wire it themselves.
|
|
91
|
-
* - Native histogram (Prometheus 2.40+): not yet broadly supported
|
|
92
|
-
* by tooling; classic histogram is universal.
|
|
93
|
-
* - Per-process labels at scrape time (instance, hostname): operators
|
|
94
|
-
* pass via defaultLabels. The framework doesn't auto-inject —
|
|
95
|
-
* deploy environments differ on what to use (k8s pod name,
|
|
96
|
-
* hostname, container id) and the operator knows best.
|
|
36
|
+
* @card
|
|
37
|
+
* Counter / gauge / histogram primitives in Prometheus 0.0.4 text format with OTLP-friendly labels, plus framework auto-instrumentation wired into audit / vault / queue hot paths.
|
|
97
38
|
*/
|
|
98
39
|
|
|
99
40
|
var C = require("./constants");
|
|
@@ -137,6 +78,28 @@ var LABEL_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
|
137
78
|
|
|
138
79
|
var _activeTap = null;
|
|
139
80
|
|
|
81
|
+
/**
|
|
82
|
+
* @primitive b.metrics.tap
|
|
83
|
+
* @signature b.metrics.tap(name, value, labels)
|
|
84
|
+
* @since 0.4.0
|
|
85
|
+
* @related b.metrics.create, b.observability.event
|
|
86
|
+
*
|
|
87
|
+
* Framework hot-path tap. Modules call `tap("audit.record", 1,
|
|
88
|
+
* { action, outcome })` without importing a registry. Until
|
|
89
|
+
* `b.metrics.create()` runs the call is a zero-cost no-op; afterwards
|
|
90
|
+
* the active registry routes the tap into pre-registered counters and
|
|
91
|
+
* gauges. Drop-silent on internal throws so a misconfigured metric
|
|
92
|
+
* cannot crash the request that triggered the tap.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* // Module-level — no registry yet, no-op:
|
|
96
|
+
* b.metrics.tap("audit.record", 1, { action: "auth.login", outcome: "success" });
|
|
97
|
+
*
|
|
98
|
+
* // After registry creation, the same tap call increments
|
|
99
|
+
* // framework_audit_events_total{action="auth.login", outcome="success"}.
|
|
100
|
+
* var registry = b.metrics.create({ namespace: "myapp" });
|
|
101
|
+
* b.metrics.tap("audit.record", 1, { action: "auth.login", outcome: "success" });
|
|
102
|
+
*/
|
|
140
103
|
function tap(name, value, labels) {
|
|
141
104
|
if (_activeTap === null) return;
|
|
142
105
|
try { _activeTap(name, value, labels); }
|
|
@@ -260,6 +223,52 @@ function _resolveLabels(defaultLabels, declaredNames, callLabels) {
|
|
|
260
223
|
|
|
261
224
|
// ---- registry factory ----
|
|
262
225
|
|
|
226
|
+
/**
|
|
227
|
+
* @primitive b.metrics.create
|
|
228
|
+
* @signature b.metrics.create(opts)
|
|
229
|
+
* @since 0.4.0
|
|
230
|
+
* @status stable
|
|
231
|
+
* @related b.metrics.tap, b.observability.event, b.tracing.create
|
|
232
|
+
*
|
|
233
|
+
* Build a Prometheus-format metrics registry. The returned registry
|
|
234
|
+
* exposes `counter` / `gauge` / `histogram` factories,
|
|
235
|
+
* `requestMiddleware()` for per-route auto-instrumentation,
|
|
236
|
+
* `expositionHandler()` for the `/metrics` scrape route, and
|
|
237
|
+
* `exposition()` for direct rendering. Activates the framework
|
|
238
|
+
* auto-tap so audit / vault / queue / error events feed
|
|
239
|
+
* pre-registered framework counters.
|
|
240
|
+
*
|
|
241
|
+
* @opts
|
|
242
|
+
* namespace: string, // prepended to every metric name
|
|
243
|
+
* defaultLabels: object, // attached to every sample
|
|
244
|
+
* labelCardinalityCap: number, // per-metric distinct-label-set cap; default 10000
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* var m = b.metrics.create({
|
|
248
|
+
* namespace: "myapp",
|
|
249
|
+
* defaultLabels: { service: "api", version: "1.2.3" },
|
|
250
|
+
* });
|
|
251
|
+
*
|
|
252
|
+
* var requests = m.counter("http_requests_total", {
|
|
253
|
+
* help: "Total HTTP requests",
|
|
254
|
+
* labelNames: ["method", "route", "status"],
|
|
255
|
+
* });
|
|
256
|
+
* requests.inc({ method: "GET", route: "/users", status: "200" });
|
|
257
|
+
*
|
|
258
|
+
* var latency = m.histogram("http_request_duration_seconds", {
|
|
259
|
+
* help: "HTTP request latency",
|
|
260
|
+
* labelNames: ["method", "route"],
|
|
261
|
+
* buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
|
|
262
|
+
* });
|
|
263
|
+
* latency.observe({ method: "GET", route: "/users" }, 0.123);
|
|
264
|
+
*
|
|
265
|
+
* var depth = m.gauge("queue_depth", { labelNames: ["queueName"] });
|
|
266
|
+
* depth.set({ queueName: "default" }, 42);
|
|
267
|
+
*
|
|
268
|
+
* // Wire into an HTTP server.
|
|
269
|
+
* router.use(m.requestMiddleware());
|
|
270
|
+
* router.get("/metrics", m.expositionHandler());
|
|
271
|
+
*/
|
|
263
272
|
function create(opts) {
|
|
264
273
|
opts = opts || {};
|
|
265
274
|
validateOpts(opts, [
|
|
@@ -43,6 +43,42 @@ var audit = lazyRequire(function () { return require("../audit"); });
|
|
|
43
43
|
|
|
44
44
|
var AgeGateError = defineClass("AgeGateError", { alwaysPermanent: true });
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* @primitive b.middleware.ageGate
|
|
48
|
+
* @signature b.middleware.ageGate(opts)
|
|
49
|
+
* @since 0.1.0
|
|
50
|
+
* @compliance gdpr, ferpa, ccpa
|
|
51
|
+
* @related b.middleware.gpc
|
|
52
|
+
*
|
|
53
|
+
* Classifies the request against an operator-supplied age predicate
|
|
54
|
+
* and applies COPPA / UK Children's Code / California AADC defaults
|
|
55
|
+
* (no-store cache, no-referrer, X-Privacy-Posture header) for
|
|
56
|
+
* below-threshold + unknown-age requests. When `requireAge` is set
|
|
57
|
+
* and the request is below threshold without a parental-consent
|
|
58
|
+
* record, refuses with HTTP 451. Every classification decision is
|
|
59
|
+
* audited.
|
|
60
|
+
*
|
|
61
|
+
* @opts
|
|
62
|
+
* {
|
|
63
|
+
* getAge: function(req): number|null, // required
|
|
64
|
+
* requireAge: number|null, // 451 below this
|
|
65
|
+
* consentRequired: number|null, // require consent below
|
|
66
|
+
* hasParentalConsent: function(req): boolean,
|
|
67
|
+
* skipPaths: string[],
|
|
68
|
+
* errorMessage: string,
|
|
69
|
+
* audit: boolean, // default true
|
|
70
|
+
* }
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* var b = require("@blamejs/core");
|
|
74
|
+
* var app = b.router.create();
|
|
75
|
+
* app.use(b.middleware.ageGate({
|
|
76
|
+
* getAge: function (req) { return (req.user && req.user.age) || null; },
|
|
77
|
+
* requireAge: null,
|
|
78
|
+
* consentRequired: 13,
|
|
79
|
+
* hasParentalConsent: function (req) { return req.user && req.user.parentalConsent === true; },
|
|
80
|
+
* }));
|
|
81
|
+
*/
|
|
46
82
|
function create(opts) {
|
|
47
83
|
opts = opts || {};
|
|
48
84
|
validateOpts(opts, [
|
|
@@ -42,6 +42,43 @@ var requestHelpers = require("../request-helpers");
|
|
|
42
42
|
var aiActMod = lazyRequire(function () { return require("../compliance-ai-act"); });
|
|
43
43
|
var audit = lazyRequire(function () { return require("../audit"); });
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* @primitive b.middleware.aiActDisclosure
|
|
47
|
+
* @signature b.middleware.aiActDisclosure(opts)
|
|
48
|
+
* @since 0.1.0
|
|
49
|
+
* @compliance eu-ai-act
|
|
50
|
+
* @related b.middleware.botDisclose
|
|
51
|
+
*
|
|
52
|
+
* Injects EU AI Act Article 50 transparency disclosures into outgoing
|
|
53
|
+
* responses. In `mode: "header"` (default) it sets `AI-Act-Notice` and
|
|
54
|
+
* `AI-Act-Article` response headers — cheapest, works for both JSON
|
|
55
|
+
* and HTML. In `mode: "html"` it additionally inserts a status banner
|
|
56
|
+
* after `<body>` and a `<meta>` inside `<head>` for HTML responses.
|
|
57
|
+
* Skips error pages, redirects, requests bearing the configured
|
|
58
|
+
* skip-header, and responses opted out via `res.locals.aiActSkip`.
|
|
59
|
+
* Emits `compliance.aiact.disclosed` audits on success.
|
|
60
|
+
*
|
|
61
|
+
* @opts
|
|
62
|
+
* {
|
|
63
|
+
* kind: "ai-interaction"|"deepfake"|"emotion-recognition"|"biometric-categorisation"|"synthetic-content",
|
|
64
|
+
* deployerName: string,
|
|
65
|
+
* policyUri: string,
|
|
66
|
+
* mode: "header"|"html", // default "header"
|
|
67
|
+
* lang: string, // default "en"
|
|
68
|
+
* skipHeader: string, // default "x-skip-ai-act"
|
|
69
|
+
* audit: boolean, // default true
|
|
70
|
+
* }
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* var b = require("@blamejs/core");
|
|
74
|
+
* var app = b.router.create();
|
|
75
|
+
* app.use(b.middleware.aiActDisclosure({
|
|
76
|
+
* kind: "ai-interaction",
|
|
77
|
+
* deployerName: "myco",
|
|
78
|
+
* policyUri: "https://myco.example.com/ai-policy",
|
|
79
|
+
* mode: "html",
|
|
80
|
+
* }));
|
|
81
|
+
*/
|
|
45
82
|
function create(opts) {
|
|
46
83
|
opts = opts || {};
|
|
47
84
|
validateOpts(opts, [
|
|
@@ -232,6 +232,51 @@ function _writeRejection(res, code, body) {
|
|
|
232
232
|
|
|
233
233
|
// ---- Server-side middleware ----
|
|
234
234
|
|
|
235
|
+
/**
|
|
236
|
+
* @primitive b.middleware.apiEncrypt
|
|
237
|
+
* @signature b.middleware.apiEncrypt(opts)
|
|
238
|
+
* @since 0.1.0
|
|
239
|
+
* @related b.middleware.csrfProtect
|
|
240
|
+
*
|
|
241
|
+
* End-to-end PQC payload encryption for operator-controlled clients.
|
|
242
|
+
* TLS protects browser to load-balancer; this middleware protects the
|
|
243
|
+
* request and response bodies through every intermediate hop (LB,
|
|
244
|
+
* sidecars, queues, log aggregators, APM tooling). A tampered byte
|
|
245
|
+
* anywhere downstream of the encrypted boundary fails the AEAD tag
|
|
246
|
+
* before the route handler runs. Defends against stripped TLS,
|
|
247
|
+
* body capture in observability tooling, and replay (timestamp +
|
|
248
|
+
* nonce window). Mount with a server keypair set; the configured
|
|
249
|
+
* client SDK encrypts to the keypair's public half.
|
|
250
|
+
*
|
|
251
|
+
* @opts
|
|
252
|
+
* {
|
|
253
|
+
* keypair: { publicKey, secretKey, ecPublicKey, ecSecretKey },
|
|
254
|
+
* keypairs: [...] // multi-key rotation set; first = active
|
|
255
|
+
* replayWindowMs: number,
|
|
256
|
+
* pruneIntervalMs: number,
|
|
257
|
+
* nonceStore: { has, add, prune },
|
|
258
|
+
* exemptPaths: string[],
|
|
259
|
+
* contentTypes: string[], // default ["application/json"]
|
|
260
|
+
* audit: boolean,
|
|
261
|
+
* maxDecryptedBytes: number,
|
|
262
|
+
* trustProxy: boolean|number,
|
|
263
|
+
* keying: "per-request"|"per-session",
|
|
264
|
+
* sessionStore: object,
|
|
265
|
+
* sessionTtlMs: number,
|
|
266
|
+
* sessionMaxResponses: number,
|
|
267
|
+
* observability: object,
|
|
268
|
+
* }
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* var b = require("@blamejs/core");
|
|
272
|
+
* var app = b.router.create();
|
|
273
|
+
* var kp = b.crypto.keypair();
|
|
274
|
+
* app.use(b.middleware.apiEncrypt({
|
|
275
|
+
* keypair: kp,
|
|
276
|
+
* replayWindowMs: 30000,
|
|
277
|
+
* contentTypes: ["application/json"],
|
|
278
|
+
* }));
|
|
279
|
+
*/
|
|
235
280
|
function create(opts) {
|
|
236
281
|
opts = opts || {};
|
|
237
282
|
validateOpts(opts, [
|
|
@@ -34,6 +34,46 @@ var AssetlinksError = defineClass("AssetlinksError", { alwaysPermanent: true });
|
|
|
34
34
|
|
|
35
35
|
var observability = lazyRequire(function () { return require("../observability"); });
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @primitive b.middleware.assetlinks
|
|
39
|
+
* @signature b.middleware.assetlinks(opts)
|
|
40
|
+
* @since 0.1.0
|
|
41
|
+
* @related b.middleware.webAppManifest
|
|
42
|
+
*
|
|
43
|
+
* Serves Digital Asset Links at `/.well-known/assetlinks.json` per
|
|
44
|
+
* Google's spec (Trusted Web Activity, Android App Links, Smart Lock,
|
|
45
|
+
* WebAuthn-for-Android). The statements array is JSON-serialized once
|
|
46
|
+
* at create-time and emitted with `Content-Type: application/json`.
|
|
47
|
+
* Multi-app deployments include multiple statement entries.
|
|
48
|
+
*
|
|
49
|
+
* @opts
|
|
50
|
+
* {
|
|
51
|
+
* statements: Array<{
|
|
52
|
+
* relation: string[],
|
|
53
|
+
* target: {
|
|
54
|
+
* namespace: string,
|
|
55
|
+
* package_name?: string,
|
|
56
|
+
* sha256_cert_fingerprints?: string[],
|
|
57
|
+
* site?: string,
|
|
58
|
+
* },
|
|
59
|
+
* }>,
|
|
60
|
+
* audit: boolean, // default true
|
|
61
|
+
* }
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* var b = require("@blamejs/core");
|
|
65
|
+
* var app = b.router.create();
|
|
66
|
+
* app.use(b.middleware.assetlinks({
|
|
67
|
+
* statements: [{
|
|
68
|
+
* relation: ["delegate_permission/common.handle_all_urls"],
|
|
69
|
+
* target: {
|
|
70
|
+
* namespace: "android_app",
|
|
71
|
+
* package_name: "com.example.app",
|
|
72
|
+
* sha256_cert_fingerprints: ["AB:CD:EF:01:23:45:67:89"],
|
|
73
|
+
* },
|
|
74
|
+
* }],
|
|
75
|
+
* }));
|
|
76
|
+
*/
|
|
37
77
|
function create(opts) {
|
|
38
78
|
validateOpts.requireObject(opts, "middleware.assetlinks", AssetlinksError);
|
|
39
79
|
validateOpts(opts, ["statements", "audit"], "middleware.assetlinks");
|
|
@@ -28,6 +28,41 @@ var AsyncApiError = defineClass("AsyncApiError", { alwaysPermanent: true });
|
|
|
28
28
|
var openapiYaml = lazyRequire(function () { return require("../openapi-yaml"); });
|
|
29
29
|
var audit = lazyRequire(function () { return require("../audit"); });
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* @primitive b.middleware.asyncapiServe
|
|
33
|
+
* @signature b.middleware.asyncapiServe(opts)
|
|
34
|
+
* @since 0.1.0
|
|
35
|
+
* @related b.middleware.openapiServe, b.asyncapi.create
|
|
36
|
+
*
|
|
37
|
+
* Serves an AsyncAPI 3.0 document built via `b.asyncapi.create` at
|
|
38
|
+
* configurable JSON + YAML mount points. Matches `openapiServe`
|
|
39
|
+
* behaviour: GET/HEAD only, SHA3-512 ETag with conditional 304,
|
|
40
|
+
* operator-controlled CORS gate, falls through on unmatched paths
|
|
41
|
+
* or methods. Use to publish channel + operation + message + schema
|
|
42
|
+
* specs for event-driven APIs (Kafka, AMQP, MQTT, WebSocket).
|
|
43
|
+
*
|
|
44
|
+
* @opts
|
|
45
|
+
* {
|
|
46
|
+
* document: object, // builder from b.asyncapi.create()
|
|
47
|
+
* pathJson: string, // default "/asyncapi.json"
|
|
48
|
+
* pathYaml: string, // default "/asyncapi.yaml"
|
|
49
|
+
* pretty: boolean, // default false → minified
|
|
50
|
+
* cacheControl: string, // default "public, max-age=300"
|
|
51
|
+
* accessControl: "public"|"same-origin"|{ allowOrigin: string },
|
|
52
|
+
* audit: boolean, // default true
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* var b = require("@blamejs/core");
|
|
57
|
+
* var app = b.router.create();
|
|
58
|
+
* var aapi = b.asyncapi.create({ title: "events", version: "1.0.0" });
|
|
59
|
+
* app.use(b.middleware.asyncapiServe({
|
|
60
|
+
* document: aapi,
|
|
61
|
+
* pathJson: "/asyncapi.json",
|
|
62
|
+
* pathYaml: "/asyncapi.yaml",
|
|
63
|
+
* accessControl: "public",
|
|
64
|
+
* }));
|
|
65
|
+
*/
|
|
31
66
|
function create(opts) {
|
|
32
67
|
opts = opts || {};
|
|
33
68
|
validateOpts(opts, [
|
|
@@ -63,6 +63,46 @@ function _readBearer(authHeader) {
|
|
|
63
63
|
return m ? m[1].trim() : null;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* @primitive b.middleware.attachUser
|
|
68
|
+
* @signature b.middleware.attachUser(req, res, next)
|
|
69
|
+
* @since 0.1.0
|
|
70
|
+
* @related b.middleware.requireAuth, b.middleware.bearerAuth, b.session.verify
|
|
71
|
+
*
|
|
72
|
+
* Populates `req.user` and `req.session` from a verified session
|
|
73
|
+
* token. Constructed via `b.middleware.attachUser(opts)`; the
|
|
74
|
+
* resulting middleware has the `(req, res, next)` shape shown
|
|
75
|
+
* above. Tries the configured cookie first, then `Authorization:
|
|
76
|
+
* Bearer <token>`. Sealed cookies (vault-unwrapped) are supported so
|
|
77
|
+
* the cookie isn't reachable via curl-with-arbitrary-cookies. The
|
|
78
|
+
* framework can't know the operator's user schema; `userLoader`
|
|
79
|
+
* receives the verified session and returns the user record. Always
|
|
80
|
+
* calls `next()` — gating decisions live in
|
|
81
|
+
* `b.middleware.requireAuth`. Optional fingerprint-drift / IP-UA pin
|
|
82
|
+
* / anomaly-score enforcement threads through `session.verify`.
|
|
83
|
+
*
|
|
84
|
+
* @opts
|
|
85
|
+
* {
|
|
86
|
+
* userLoader: async function(session): user|null, // required
|
|
87
|
+
* cookieName: string, // default "blamejs_session"
|
|
88
|
+
* tokenFrom: "both"|"cookie"|"header", // default "both"
|
|
89
|
+
* sealed: boolean,
|
|
90
|
+
* vault: object, // required when sealed
|
|
91
|
+
* requireFingerprintMatch: boolean,
|
|
92
|
+
* maxAnomalyScore: number,
|
|
93
|
+
* scorer: function,
|
|
94
|
+
* audit: boolean, // default true
|
|
95
|
+
* }
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* var b = require("@blamejs/core");
|
|
99
|
+
* var app = b.router.create();
|
|
100
|
+
* app.use(b.middleware.attachUser({
|
|
101
|
+
* userLoader: async function (session) {
|
|
102
|
+
* return { id: session.userId, name: "alice" };
|
|
103
|
+
* },
|
|
104
|
+
* }));
|
|
105
|
+
*/
|
|
66
106
|
function create(opts) {
|
|
67
107
|
opts = opts || {};
|
|
68
108
|
validateOpts(opts, [
|
|
@@ -78,6 +78,46 @@ function _extractToken(req, scheme) {
|
|
|
78
78
|
return { state: "ok", token: token };
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* @primitive b.middleware.bearerAuth
|
|
83
|
+
* @signature b.middleware.bearerAuth(req, res, next)
|
|
84
|
+
* @since 0.1.0
|
|
85
|
+
* @related b.middleware.attachUser, b.middleware.requireAuth, b.auth.jwt
|
|
86
|
+
*
|
|
87
|
+
* Extracts `Authorization: Bearer <token>`, calls an operator-supplied
|
|
88
|
+
* verifier, attaches the result to `req.user`. Constructed via
|
|
89
|
+
* `b.middleware.bearerAuth(opts)`; the resulting middleware has
|
|
90
|
+
* the `(req, res, next)` shape shown above. Distinct from
|
|
91
|
+
* `attachUser` (cookie sessions) — this is the API-token / JWT /
|
|
92
|
+
* OAuth-access-token path. When the header is absent the middleware
|
|
93
|
+
* defers to downstream auth; when it IS present but invalid it
|
|
94
|
+
* rejects with HTTP 401 + `WWW-Authenticate` immediately. Verifier
|
|
95
|
+
* returns the user object on success, null/false on rejection, or
|
|
96
|
+
* throws an Error with `code === "auth-bearer/expired"` to surface
|
|
97
|
+
* a token-expired challenge. Emits `auth.bearer.success` /
|
|
98
|
+
* `auth.bearer.failure` audit events with actor context.
|
|
99
|
+
*
|
|
100
|
+
* @opts
|
|
101
|
+
* {
|
|
102
|
+
* verify: async function(token): user|null, // required
|
|
103
|
+
* scheme: string, // default "Bearer"; some ops use "Token"
|
|
104
|
+
* realm: string,
|
|
105
|
+
* errorMessage: string,
|
|
106
|
+
* tokenAttachKey: string,
|
|
107
|
+
* userAttachKey: string,
|
|
108
|
+
* audit: boolean, // default true
|
|
109
|
+
* }
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* var b = require("@blamejs/core");
|
|
113
|
+
* var app = b.router.create();
|
|
114
|
+
* app.use("/api", b.middleware.bearerAuth({
|
|
115
|
+
* verify: async function (token) {
|
|
116
|
+
* if (token === "valid-token") return { id: "user-1" };
|
|
117
|
+
* return null;
|
|
118
|
+
* },
|
|
119
|
+
* }));
|
|
120
|
+
*/
|
|
81
121
|
function create(opts) {
|
|
82
122
|
opts = opts || {};
|
|
83
123
|
validateOpts(opts, [
|