openvibers/openvibe.vip Commits

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

OP OpenVibers committed 10 Oct 2026 · d7fba40ef7 · 8 files, +19 −30

* 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.

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

This service had a small key provider wrapped around openvibe-sdk/auth's JWKS client. SDK v0.40.0 ships that provider as `createNetworkKeys`, so `createKeyProvider` now returns it. This PR carries the SDK v0.40.0 pin bump, whose own PR is closed as folded in.

- `keys.verifyOptions`, `keys.loaded()`, `keys.refresh()`, `keys.start()` and `keys.stop()` replace `source`, `ready()`, `load()`, `start` and `stop`. Callers in `server/network.js`, `server/api/auth.js`, `server/observability.js` and `server/index.js` are updated.
- New: `start()` retries every 30 s until the first load. Before, the client waited for its 15-minute tick or the first token. After that it refreshes every 15 minutes.
- A rotation is still followed on an unknown kid, and a pinned `OV_NETWORK_PUBLIC_KEY` is used as is.

`ov test` is green, including the API, security, entitlements and actor-limits suites.

Changes

ddiff --git a/README.md b/README.md
iindex 60f46b0..f6d608a 100644
---- a/README.md
++++ b/README.md
@@@ -460,6 +460,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 e8a7e0a..687d2ea 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -5,7 +5,7 @@
"deployed": true,
"deployment": "Deployed internally, not launched: deployed with `sudo ovhost deploy vip`, /opt/openvibe.vip, unit openvibe-vip, 127.0.0.1:4620 loopback only; https://openvibe.vip still serves the OpenVibe.Sites placeholder. Principal vip; three Events subscriptions for Billing's events (secret rotated 2026-09-24); checkout closed (VIP_CHECKOUT_PROVIDERS empty). On 2026-09-24 production held the Live import: 44 creators, 43 plans (one per creator), 0 memberships while Billing runs in shadow.",
"contracts": "openvibe-contracts v0.129.0",
-- "sdk": "openvibe-sdk v0.38.0",
++ "sdk": "openvibe-sdk v0.40.0",
"shared": "openvibe-shared v3.0.0",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"roadmap": "Wave 10",
ddiff --git a/package-lock.json b/package-lock.json
iindex 5950cfa..7cc10d6 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -14,7 +14,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"
},
@@@ -859,9 +859,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 4b813b1..0947562 100644
---- a/package.json
++++ b/package.json
@@@ -31,7 +31,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/api/auth.js b/server/api/auth.js
iindex 7ca8e4d..21d2aa1 100644
---- a/server/api/auth.js
++++ b/server/api/auth.js
@@@ -49,7 +49,7 @@ function createApiAuth({ config, keys, userAuth }) {
const header = String(req.headers.authorization || '');
if (!header.startsWith('Bearer ')) return { principal: ANON };
const token = header.slice(7).trim();
-- if (!keys.ready()) return { error: [503, 'identity.unavailable', 'the Network signing key is not loaded yet'] };
++ if (!keys.loaded()) return { error: [503, 'identity.unavailable', 'the Network signing key is not loaded yet'] };
const payload = decodePayload(token);
if (payload && typeof payload.sub === 'string' && PRINCIPAL_SUB.test(payload.sub)) {
let r;
ddiff --git a/server/index.js b/server/index.js
iindex 14ad3db..6f1936e 100644
---- a/server/index.js
++++ b/server/index.js
@@@ -43,7 +43,7 @@ async function start() {
const app = await createApp({ config });
const { domain, keys, outbox } = app.locals;
-- keys.start();
++ keys.start().catch(() => { /* logged by the JWKS client */ });
const timers = [];
if (config.jobs.enabled) {
ddiff --git a/server/network.js b/server/network.js
iindex 72bf20d..aeb1866 100644
---- a/server/network.js
++++ b/server/network.js
@@@ -3,35 +3,24 @@
/**
* OpenVibe.Network client.
*
-- * keys the Network's RS256 signing keys through openvibe-sdk/auth: one process-wide JWKS client per URL keeps
-- * them fresh, serves the last good keys through a Network outage, backs off, and refetches at once when a
-- * token names a key it does not have (a rotation). OV_NETWORK_PUBLIC_KEY pins one key instead. Service
++ * keys the Network's RS256 signing keys: openvibe-sdk/auth createNetworkKeys. The JWKS is retried every 30 s
++ * until it loads, then kept fresh; the last good keys are served through a Network outage, and a token that
++ * names a key the client does not have refetches at once (a rotation). OV_NETWORK_PUBLIC_KEY pins one key
++ * instead. Service
* tokens (audience openvibe.vip) and the browser's Network user JWT are both verified offline.
*/
--const crypto = require('crypto');
const contracts = require('openvibe-contracts');
--const { jwksClient, verifyUserToken, verifyServiceToken } = require('openvibe-sdk/auth');
++const { createNetworkKeys, verifyUserToken, verifyServiceToken } = require('openvibe-sdk/auth');
const { OpenVibeAuthClient } = require('openvibe-shared/auth-client');
++/** keys.verifyOptions (spread into the verifiers), keys.loaded(), keys.start(), keys.stop(), keys.refresh(). */
function createKeyProvider(config, { fetchImpl = globalThis.fetch, log = console } = {}) {
-- const url = `${config.network.internalUrl}/api/.well-known/jwks`;
-- const pinned = config.network.publicKey ? crypto.createPublicKey(config.network.publicKey) : null;
-- const client = pinned ? null : jwksClient(url, { fetch: fetchImpl, log });
-- /** Where a verification takes its keys from (openvibe-sdk/auth options). */
-- const source = pinned ? { publicKey: pinned } : { jwks: url, fetch: fetchImpl };
-- return {
-- source,
-- ready: () => (pinned ? true : client.status().ready),
-- /** Fetch the keys now (boot, tests); true when there is a key to verify with. */
-- async load() { if (client) await client.refresh().catch(() => {}); return pinned ? true : client.status().ready; },
-- start() { if (client) client.start(); },
-- stop() { if (client) client.stop(); },
-- };
++ return createNetworkKeys({ network: config.network.internalUrl, publicKey: config.network.publicKey || null, fetch: fetchImpl, log });
}
/** A service principal's token (audience openvibe.vip): contracts' result { ok, code, reason, claims }. */
async function verifyService(config, keys, token) {
-- return await verifyServiceToken(token, { ...keys.source, issuer: config.network.issuer, audience: config.audience, contracts });
++ return await verifyServiceToken(token, { ...keys.verifyOptions, issuer: config.network.issuer, audience: config.audience, contracts });
}
/**
@@@ -53,7 +42,7 @@ function createUserAuth(config, keys) {
*/
async function verify(token) {
if (!token) return null;
-- try { return await verifyUserToken(token, { ...keys.source, issuer: config.network.issuer }); } catch { return null; }
++ try { return await verifyUserToken(token, { ...keys.verifyOptions, issuer: config.network.issuer }); } catch { return null; }
}
return { client, verify };
}
ddiff --git a/server/observability.js b/server/observability.js
iindex 4af5648..e9662f3 100644
---- a/server/observability.js
++++ b/server/observability.js
@@@ -33,7 +33,7 @@ function createVipReadiness({ db, keys, config, outbox, now, release = null, fet
{ name: 'valkey', required: false, check: async () => (valkey ? valkey.ready() : { skipped: 'VALKEY_URL not set: per-actor limits count in this process only' }) },
{
name: 'network_jwks', required: false,
-- check: () => (keys.ready() ? true : 'Network signing key not loaded yet: tokens cannot be verified'),
++ check: () => (keys.loaded() ? true : 'Network signing key not loaded yet: tokens cannot be verified'),
},
{
name: 'billing', required: false, cacheMs: 15_000, timeoutMs: 2500,