openvibers/openvibe.events Commits

Events: the key provider is openvibe-sdk/auth createNetworkKeys (SDK 0.40.0) (#53)

OP OpenVibers committed 10 Oct 2026 · c3e0d2f053 · 6 files, +15 −72

* openvibe-sdk v0.40.0 (was v0.38.0)

Opened by scripts/pin-bump.js in OpenVibers/OpenVibe.Shared after the v0.40.0 release.

* Events: the key provider is openvibe-sdk/auth createNetworkKeys (SDK 0.40.0)

Events#51 moved Events' token checks to openvibe-sdk/auth but kept a local key provider (`createKeys`). The SDK now has that provider as `createNetworkKeys` (v0.40.0), so the local copy goes. This PR carries the pin bump from #52.

- `server/index.js` builds the keys with `createNetworkKeys({ network, publicKey, fetch, log })`. Behaviour is the same:
  - a pinned PEM, or Network's JWKS;
  - a retry every 30 s until the first load, then a refresh every 15 minutes;
  - a rotation honoured on an unknown kid.
- `server/auth.js` no longer has a key section. The guards, `realtimeViewer` and the realtime ticket check read `keys.verifyOptions` and `keys.keysFor(kid)` as before.
- README: `network_jwks` names `createNetworkKeys`.

The key-loader, rotation, realtime, ticket, apps and security tests are green.

Changes

ddiff --git a/README.md b/README.md
iindex 44c3355..cf62ef6 100644
---- a/README.md
++++ b/README.md
@@@ -22,7 +22,7 @@ npm test # every test/*.test.js, temp databases, no network needed
Node 22 in production (`fnm exec --using=22.22.1 npm test`). Production: `/opt/openvibe.events`, env `/etc/openvibe/events.env`, unit [deploy/systemd/openvibe-events.service](deploy/systemd/openvibe-events.service), store PostgreSQL (`ov_events` on the host's data role), nginx [deploy/nginx/openvibe.events.conf](deploy/nginx/openvibe.events.conf) (the public vhost, on the API and product origin: it exposes the home and its discovery files, `/realtime/stream`, health, and the token-guarded `/api/v1/events`, `/api/v1/subscriptions` and `/api/v1/checkpoints` for developer apps; `/api/v1/deliveries` stays host-local; services on the host call `127.0.0.1:4300`).
--`GET /api/health` is liveness. `GET /api/ready` (openvibe-shared/ready) is 200 only when every required check passes — `db` (a real query), `network_jwks` (the Network signing key has loaded: openvibe-sdk/auth's JWKS client, retried every 30 s while Network boots, refreshed every 15 minutes, refetched at once for a token naming an unknown kid, and the last good keys kept through a Network outage) and `delivery_worker` (when `EVENTS_WORKER` is on) — and 503 otherwise, with `failed: [...]`. The optional `dlq` check fails once more than `EVENTS_DLQ_DEGRADED_AT` (default 100) deliveries are dead: the service stays ready (200) and reports `status: "degraded"`, `degraded: ["dlq"]`. Each check carries `status`, `required`, `latency_ms`, `checked_at` and, for `dlq`, `detail: { depth, threshold }`; `latest_seq`, `deliveries`, `worker` and `realtime_connections` are still in the body. **Shape change (Track O):** `checks` used to be booleans (`{ db, worker, key }`); they are now objects keyed `db`, `network_jwks`, `delivery_worker`, `dlq`.
++`GET /api/health` is liveness. `GET /api/ready` (openvibe-shared/ready) is 200 only when every required check passes — `db` (a real query), `network_jwks` (the Network signing key has loaded: openvibe-sdk/auth `createNetworkKeys`, retried every 30 s while Network boots, refreshed every 15 minutes, refetched at once for a token naming an unknown kid, and the last good keys kept through a Network outage) and `delivery_worker` (when `EVENTS_WORKER` is on) — and 503 otherwise, with `failed: [...]`. The optional `dlq` check fails once more than `EVENTS_DLQ_DEGRADED_AT` (default 100) deliveries are dead: the service stays ready (200) and reports `status: "degraded"`, `degraded: ["dlq"]`. Each check carries `status`, `required`, `latency_ms`, `checked_at` and, for `dlq`, `detail: { depth, threshold }`; `latest_seq`, `deliveries`, `worker` and `realtime_connections` are still in the body. **Shape change (Track O):** `checks` used to be booleans (`{ db, worker, key }`); they are now objects keyed `db`, `network_jwks`, `delivery_worker`, `dlq`.
`GET /metrics` (Prometheus text, direct loopback callers only — a request carrying `X-Forwarded-For` gets 404, and nginx refuses the path): HTTP golden signals by route template (`http_requests_total`, `http_request_duration_seconds`, `http_requests_in_flight`; SSE sessions excluded), process metrics, `release_info`, and `events_deliveries{status}`, `events_dlq_depth`, `events_latest_seq`, `events_realtime_connections`, `events_delivery_latency_seconds{priority}` (acceptance → successful delivery, retries included), `events_delivery_attempts_total{outcome}`. `GET /release.json` is the deployed release (ADR-016, `release.mount` from openvibe-shared 1.5.0, with the manifest 1.1.0 fields under openvibe-contracts 0.32.0); its `POST /release-metrics` would count open tabs' update reports as `release_client_updates_total`, but Events has no pages and nginx does not proxy that path. `GET /limits.json` lists the developer limits enforced here (per project and environment), read from the running config; [openvibe.services/docs/limits](https://openvibe.services/docs/limits) renders it.
@@@ -388,6 +388,6 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
<!-- versions:start -->
- openvibe-contracts: v0.129.0
--- openvibe-sdk: v0.38.0
++- openvibe-sdk: v0.40.0
- openvibe-shared: v3.0.0
<!-- versions:end -->
ddiff --git a/STATUS.json b/STATUS.json
iindex 2af0493..f5dba30 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -8,7 +8,7 @@
"port": 4300,
"domain": "openvibe.events",
"contracts": "openvibe-contracts v0.129.0",
-- "sdk": "openvibe-sdk v0.38.0",
++ "sdk": "openvibe-sdk v0.40.0",
"shared": "openvibe-shared v3.0.0",
"capabilities": [
"events.event.publish",
ddiff --git a/package-lock.json b/package-lock.json
iindex 6e949e7..9c40a92 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -13,7 +13,7 @@
"express": "^4.21.2",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.129.0",
-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.38.0",
++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.40.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.0",
"pg": "^8.23.0"
},
@@@ -844,9 +844,9 @@
}
},
"node_modules/openvibe-sdk": {
-- "version": "0.38.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.38.0",
-- "integrity": "sha512-/Z+1kB5l4Hff0E1O1Zo+DcXaZX93wrmISOPP75sB7NqVUZVeKuJgjYu6vBwG8ufJjlk0vD3ZJ0ntBLI/3IKdpg==",
++ "version": "0.40.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.40.0",
++ "integrity": "sha512-fYX0zijJZDdwjXJGwroLDlb/wniY6NXVtgdPVJJHNgcdRmaAydpr6SKuxcLffmkTquyZQptwChsCvo+D6Wx+IA==",
"license": "MIT",
"engines": {
"node": ">=20"
ddiff --git a/package.json b/package.json
iindex 225ddf5..8d3b9dc 100644
---- a/package.json
++++ b/package.json
@@@ -25,7 +25,7 @@
"express": "^4.21.2",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.129.0",
-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.38.0",
++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.40.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.0",
"pg": "^8.23.0"
},
ddiff --git a/server/auth.js b/server/auth.js
iindex 629f7cb..61cbbb2 100644
---- a/server/auth.js
++++ b/server/auth.js
@@@ -18,8 +18,9 @@
* (appOrService guard). Only there is a sandbox token (env=sandbox) accepted; every other route
* refuses it with token.sandbox_refused.
*
-- * The key: Network's JWKS through openvibe-sdk/auth's process-wide client (the last good keys through a
-- * Network outage, a rotation honoured on an unknown kid), or a pinned PEM (OV_NETWORK_PUBLIC_KEY).
++ * The keys: openvibe-sdk/auth createNetworkKeys (server/index.js): Network's JWKS (the last good keys through a
++ * Network outage, a rotation honoured on an unknown kid, retried every 30 s until the first load), or a pinned
++ * PEM (OV_NETWORK_PUBLIC_KEY).
*
* The service capabilities are in openvibe-contracts since v0.7; events.app.* arrive in v0.28.
* capabilities.check() answers capability.unknown for an id the installed release does not know;
@@@ -47,65 +48,6 @@ const CAPS = Object.freeze({
resourceRead: 'events.resource.read',
});
--// ── Network public key ─────────────────────────────────────
--
--const JWKS_RETRY_MS = 30 * 1000;
--const JWKS_REFRESH_MS = 15 * 60 * 1000;
--
--/**
-- * Network's signing keys. With a PEM in config (OV_NETWORK_PUBLIC_KEY) that one key, never fetched. Otherwise
-- * <networkInternalUrl>/api/.well-known/jwks through openvibe-sdk/auth's process-wide JWKS client (the verifiers read
-- * the same client): start() retries every 30 s until the first load (Network may still be booting), then refreshes
-- * every 15 minutes; a token naming an unknown kid refetches at once (a rotation).
-- *
-- * keys.verifyOptions what the SDK verifiers take: { publicKey } or { jwks: url, fetch, log }
-- * keys.keysFor(kid) [{ kid, key }] to verify a realtime ticket with; throws when none has loaded
-- */
--function createKeys({ url = null, pem = null, fetchImpl = globalThis.fetch, log = console } = {}) {
-- const pinned = pem ? crypto.createPublicKey(pem) : null;
-- const jwksUrl = pinned || !url ? null : `${url.replace(/\/+$/, '')}/api/.well-known/jwks`;
-- const client = jwksUrl ? sdkAuth.jwksClient(jwksUrl, { fetch: fetchImpl, log }) : null;
-- let retryTimer = null;
--
-- const loaded = () => Boolean(pinned) || Boolean(client && client.status().ready);
--
-- /** One fetch now; the keys, or null (the client logged why). */
-- async function refresh() {
-- if (!client) return null;
-- try { return await client.refresh(); } catch { return null; }
-- }
--
-- async function start() {
-- if (!client) return pinned;
-- client.start({ intervalMs: JWKS_REFRESH_MS });
-- const attempt = async () => {
-- retryTimer = null;
-- const got = await refresh();
-- if (!loaded()) {
-- retryTimer = setTimeout(attempt, JWKS_RETRY_MS);
-- retryTimer.unref?.();
-- }
-- return got;
-- };
-- return await attempt();
-- }
--
-- function stop() {
-- clearTimeout(retryTimer);
-- retryTimer = null;
-- if (client) client.stop();
-- }
--
-- async function keysFor(kid) {
-- if (pinned) return [{ kid: null, key: pinned }];
-- if (!client) return [];
-- return await client.keysForKid(kid || null);
-- }
--
-- const verifyOptions = pinned ? { publicKey: pinned } : { jwks: jwksUrl, fetch: fetchImpl, log };
-- return { url: jwksUrl, loaded, start, stop, refresh, keysFor, verifyOptions };
--}
--
// ── Capabilities ───────────────────────────────────────────
/** Exact id or `family.*` grant (the rule openvibe-contracts applies to its known capabilities). */
@@@ -328,4 +270,4 @@ function createAuth({ config, keys, store = null }) {
return { verifyService, verifyUser, requireCap, appOrService, realtimeViewer };
}
--module.exports = { CAPS, createKeys, createAuth, hasCap, allows, verifyRealtimeTicket, serviceSlug, bearer, cookie };
++module.exports = { CAPS, createAuth, hasCap, allows, verifyRealtimeTicket, serviceSlug, bearer, cookie };
ddiff --git a/server/index.js b/server/index.js
iindex a9206cf..35a6073 100644
---- a/server/index.js
++++ b/server/index.js
@@@ -12,7 +12,8 @@ const { openDb, createStore } = require('./store');
const { createPolicyLibrary } = require('./fabric/policy');
const { createCarriers } = require('./fabric/carriers');
const { createPlanner } = require('./fabric/planner');
--const { createKeys, createAuth } = require('./auth');
++const { createNetworkKeys } = require('openvibe-sdk/auth');
++const { createAuth } = require('./auth');
const { createWorker } = require('./worker');
const { createRealtime } = require('./realtime');
const { createApp } = require('./app');
@@@ -38,7 +39,7 @@ async function start({
const carriers = createCarriers({ config, clock, log, valkey });
const planner = createPlanner({ registry: carriers, db, clock, log });
const store = createStore(db, { clock, maxHops: config.maxHops, usage: config.usage, policies, planner });
-- const keys = createKeys({ url: config.networkInternalUrl, pem: config.networkPublicKey, fetchImpl, log });
++ const keys = createNetworkKeys({ network: config.networkInternalUrl, publicKey: config.networkPublicKey, fetch: fetchImpl, log });
const auth = createAuth({ config, keys, store });
const metrics = createMetrics({ store });
// appPost / dnsLookup: developer-app delivery and the subscribe-time DNS check (server/egress.js);