openvibers/openvibe.sdk Commits
SDK 0.25.1: verifyServiceToken({ jwks }) also takes a JWKS client the service already holds (the one it built for openvibe-sdk/sso, or a test's), so service tokens are checked with the key their kid names instead of a service taking the first key itself. Test: kid choice, bad signature and an unreachable client (token.unavailable, fixed reason) through a held client.
Changes
@@@ -8,6 +8,12 @@ release may change an API and says so here.++## 0.25.1 (2026-09-29)++++- `verifyServiceToken({ jwks })` also takes a JWKS client the service already holds (`jwksClient(url)`, or one a test++ injects), so a service that built one for `openvibe-sdk/sso` verifies service tokens with the same keys, chosen by++ `kid`, instead of taking the first key itself.++## 0.25.0 (2026-09-29)- `openvibe-sdk/sso` (new, server): `createSsoClient({ site, baseUrl, clientId, clientSecret, networkUrl, networkInternalUrl,@@@ -1,4 +1,4 @@--// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.25.0, MIT). Do not edit.++// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.25.1, MIT). Do not edit.// One self-contained ES module: core at the top level plus the browser-safe subpaths as namespaces// (auth, registry, modules, realtime, media, community, search, chat, ai, jobs, tools, projects, geo). No server-only code: no service tokens, JWT verification, events, identity./* eslint-disable */@@@ -41,7 +41,7 @@ const { paginate, offsetPager } = __load(7);const semver = __load(6);/** The package version (test/version.test.js keeps it equal to package.json). */--const SDK_VERSION = '0.25.0';++const SDK_VERSION = '0.25.1';module.exports = {createClient,@@@ -1,12 +1,12 @@{"name": "openvibe-sdk",-- "version": "0.25.0",++ "version": "0.25.1","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-sdk",-- "version": "0.25.0",++ "version": "0.25.1","license": "MIT","devDependencies": {"@electric-sql/pglite": "^0.5.8",@@@ -1,6 +1,6 @@{"name": "openvibe-sdk",-- "version": "0.25.0",++ "version": "0.25.1","description": "Supported browser and server clients for the OpenVibe platform: auth (incl. developer apps), registry discovery, identity, user modules, events, realtime, media, community, search, chat, AI runs, Tools (registry, run API and jobs), VIP memberships and developer projects, the async PostgreSQL data layer (db) and the Valkey cache, limits store, queue and pub/sub, with deadlines, safe retries, problem errors and trace propagation.","main": "index.js","types": "types/index.d.ts",@@@ -1,6 +1,6 @@'use strict';/**-- * verifyServiceToken(token, { jwks (a URL or a JWKS document) | publicKey, issuer, audience, contracts, acceptSandbox, log })++ * verifyServiceToken(token, { jwks (a URL, a jwksClient or a JWKS document) | publicKey, issuer, audience, contracts, acceptSandbox, log })* → { ok: true, claims } | { ok: false, code, reason }** A Network service or app token (identity.service-token-claims@1), for a service that receives them. The SDK@@@ -31,7 +31,9 @@ async function verifyServiceToken(token, { jwks = null, publicKey = null, issuerconst kid = headerKid(token);let keys;try {-- keys = typeof jwks === 'string' ? await jwksClient(jwks, { log, ...(fetchImpl ? { fetch: fetchImpl } : {}) }).keysForKid(kid) : keysFromJwks(jwks);++ keys = typeof jwks === 'string' ? await jwksClient(jwks, { log, ...(fetchImpl ? { fetch: fetchImpl } : {}) }).keysForKid(kid)++ : typeof jwks.keysForKid === 'function' ? await jwks.keysForKid(kid) // a client the service already holds++ : keysFromJwks(jwks);} catch (err) {try { (log && (log.warn || log.error) || (() => {})).call(log, `[openvibe-sdk] service token not verified: ${(err && err.message) || err}`); } catch { /* */ }return { ok: false, code: 'token.unavailable', reason: 'signing key not loaded yet' };@@@ -8,7 +8,7 @@ const { paginate, offsetPager } = require('./paginate');const semver = require('./semver');/** The package version (test/version.test.js keeps it equal to package.json). */--const SDK_VERSION = '0.25.0';++const SDK_VERSION = '0.25.1';module.exports = {createClient,@@@ -52,6 +52,15 @@ run([assert.equal(hits, 1);} finally { srv.close(); }}],++ ['a JWKS client the service already holds: the key its kid names', async () => {++ const asked = [];++ const client = { keysForKid: async (kid) => { asked.push(kid); return [{ kid: 'old', key: a.publicKey }, { kid: 'new', key: b.publicKey }]; } };++ assert.equal((await verifyServiceToken(sign(svc(), b.privateKey, 'new'), opts({ jwks: client }))).ok, true);++ assert.equal((await verifyServiceToken(sign(svc(), stranger.privateKey, 'new'), opts({ jwks: client }))).code, 'token.bad_signature');++ assert.deepEqual(asked, ['new', 'new']);++ const down = { keysForKid: async () => { throw new Error('http://10.0.0.1/jwks unreachable'); } };++ assert.deepEqual(Object.values(await verifyServiceToken(sign(svc(), b.privateKey, 'new'), opts({ jwks: down }))), [false, 'token.unavailable', 'signing key not loaded yet']);++ }],['misuse is a TypeError: no contracts module, no audience, no key source', async () => {await assert.rejects(verifyServiceToken('x.y.z', { jwks: doc, audience: 'openvibe.media' }), TypeError);await assert.rejects(verifyServiceToken('x.y.z', { jwks: doc, contracts }), TypeError);@@@ -143,8 +143,8 @@ export interface ContractsForServiceTokens {}export type ServiceTokenResult = { ok: true; claims: Record<string, unknown> } | { ok: false; code: string; reason: string };export interface VerifyServiceTokenOptions {-- /** A JWKS URL (the process-wide client: last good keys, rotation, backoff) or a JWKS document. */-- jwks?: string | { keys?: object[]; public_key?: string } | null;++ /** A JWKS URL (the process-wide client: last good keys, rotation, backoff), a JWKS client the service already holds, or a JWKS document. */++ jwks?: string | JwksClient | { keys?: object[]; public_key?: string } | null;/** A pinned PEM instead of a JWKS. */publicKey?: string | object | null;issuer?: string;
@@ -8,6 +8,12 @@ release may change an API and says so here.
@@ -8,6 +8,12 @@ release may change an API and says so here.
+## 0.25.1 (2026-09-29)
+
+- `verifyServiceToken({ jwks })` also takes a JWKS client the service already holds (`jwksClient(url)`, or one a test
+ injects), so a service that built one for `openvibe-sdk/sso` verifies service tokens with the same keys, chosen by
+ `kid`, instead of taking the first key itself.
+
## 0.25.0 (2026-09-29)
## 0.25.0 (2026-09-29)
- `openvibe-sdk/sso` (new, server): `createSsoClient({ site, baseUrl, clientId, clientSecret, networkUrl, networkInternalUrl,
- `openvibe-sdk/sso` (new, server): `createSsoClient({ site, baseUrl, clientId, clientSecret, networkUrl, networkInternalUrl,
@@ -1,4 +1,4 @@
@@ -1,4 +1,4 @@
-// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.25.0, MIT). Do not edit.
+// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.25.1, MIT). Do not edit.
// One self-contained ES module: core at the top level plus the browser-safe subpaths as namespaces
// One self-contained ES module: core at the top level plus the browser-safe subpaths as namespaces
// (auth, registry, modules, realtime, media, community, search, chat, ai, jobs, tools, projects, geo). No server-only code: no service tokens, JWT verification, events, identity.
// (auth, registry, modules, realtime, media, community, search, chat, ai, jobs, tools, projects, geo). No server-only code: no service tokens, JWT verification, events, identity.
/* eslint-disable */
/* eslint-disable */
@@ -41,7 +41,7 @@ const { paginate, offsetPager } = __load(7);
@@ -41,7 +41,7 @@ const { paginate, offsetPager } = __load(7);
const semver = __load(6);
const semver = __load(6);
/** The package version (test/version.test.js keeps it equal to package.json). */
/** The package version (test/version.test.js keeps it equal to package.json). */
-const SDK_VERSION = '0.25.0';
+const SDK_VERSION = '0.25.1';
module.exports = {
module.exports = {
createClient,
createClient,
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.25.0",
+ "version": "0.25.1",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.25.0",
+ "version": "0.25.1",
"license": "MIT",
"license": "MIT",
"devDependencies": {
"devDependencies": {
"@electric-sql/pglite": "^0.5.8",
"@electric-sql/pglite": "^0.5.8",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.25.0",
+ "version": "0.25.1",
"description": "Supported browser and server clients for the OpenVibe platform: auth (incl. developer apps), registry discovery, identity, user modules, events, realtime, media, community, search, chat, AI runs, Tools (registry, run API and jobs), VIP memberships and developer projects, the async PostgreSQL data layer (db) and the Valkey cache, limits store, queue and pub/sub, with deadlines, safe retries, problem errors and trace propagation.",
"description": "Supported browser and server clients for the OpenVibe platform: auth (incl. developer apps), registry discovery, identity, user modules, events, realtime, media, community, search, chat, AI runs, Tools (registry, run API and jobs), VIP memberships and developer projects, the async PostgreSQL data layer (db) and the Valkey cache, limits store, queue and pub/sub, with deadlines, safe retries, problem errors and trace propagation.",
"main": "index.js",
"main": "index.js",
"types": "types/index.d.ts",
"types": "types/index.d.ts",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
'use strict';
'use strict';
/**
/**
- * verifyServiceToken(token, { jwks (a URL or a JWKS document) | publicKey, issuer, audience, contracts, acceptSandbox, log })
+ * verifyServiceToken(token, { jwks (a URL, a jwksClient or a JWKS document) | publicKey, issuer, audience, contracts, acceptSandbox, log })
* → { ok: true, claims } | { ok: false, code, reason }
* → { ok: true, claims } | { ok: false, code, reason }
*
*
* A Network service or app token (identity.service-token-claims@1), for a service that receives them. The SDK
* A Network service or app token (identity.service-token-claims@1), for a service that receives them. The SDK
@@ -31,7 +31,9 @@ async function verifyServiceToken(token, { jwks = null, publicKey = null, issuer
@@ -31,7 +31,9 @@ async function verifyServiceToken(token, { jwks = null, publicKey = null, issuer
const kid = headerKid(token);
const kid = headerKid(token);
let keys;
let keys;
try {
try {
- keys = typeof jwks === 'string' ? await jwksClient(jwks, { log, ...(fetchImpl ? { fetch: fetchImpl } : {}) }).keysForKid(kid) : keysFromJwks(jwks);
+ keys = typeof jwks === 'string' ? await jwksClient(jwks, { log, ...(fetchImpl ? { fetch: fetchImpl } : {}) }).keysForKid(kid)
+ : typeof jwks.keysForKid === 'function' ? await jwks.keysForKid(kid) // a client the service already holds
+ : keysFromJwks(jwks);
} catch (err) {
} catch (err) {
try { (log && (log.warn || log.error) || (() => {})).call(log, `[openvibe-sdk] service token not verified: ${(err && err.message) || err}`); } catch { /* */ }
try { (log && (log.warn || log.error) || (() => {})).call(log, `[openvibe-sdk] service token not verified: ${(err && err.message) || err}`); } catch { /* */ }
return { ok: false, code: 'token.unavailable', reason: 'signing key not loaded yet' };
return { ok: false, code: 'token.unavailable', reason: 'signing key not loaded yet' };
@@ -8,7 +8,7 @@ const { paginate, offsetPager } = require('./paginate');
@@ -8,7 +8,7 @@ const { paginate, offsetPager } = require('./paginate');
const semver = require('./semver');
const semver = require('./semver');
/** The package version (test/version.test.js keeps it equal to package.json). */
/** The package version (test/version.test.js keeps it equal to package.json). */
-const SDK_VERSION = '0.25.0';
+const SDK_VERSION = '0.25.1';
module.exports = {
module.exports = {
createClient,
createClient,
@@ -52,6 +52,15 @@ run([
@@ -52,6 +52,15 @@ run([
assert.equal(hits, 1);
assert.equal(hits, 1);
} finally { srv.close(); }
} finally { srv.close(); }
}],
}],
+ ['a JWKS client the service already holds: the key its kid names', async () => {
+ const asked = [];
+ const client = { keysForKid: async (kid) => { asked.push(kid); return [{ kid: 'old', key: a.publicKey }, { kid: 'new', key: b.publicKey }]; } };
+ assert.equal((await verifyServiceToken(sign(svc(), b.privateKey, 'new'), opts({ jwks: client }))).ok, true);
+ assert.equal((await verifyServiceToken(sign(svc(), stranger.privateKey, 'new'), opts({ jwks: client }))).code, 'token.bad_signature');
+ assert.deepEqual(asked, ['new', 'new']);
+ const down = { keysForKid: async () => { throw new Error('http://10.0.0.1/jwks unreachable'); } };
+ assert.deepEqual(Object.values(await verifyServiceToken(sign(svc(), b.privateKey, 'new'), opts({ jwks: down }))), [false, 'token.unavailable', 'signing key not loaded yet']);
+ }],
['misuse is a TypeError: no contracts module, no audience, no key source', async () => {
['misuse is a TypeError: no contracts module, no audience, no key source', async () => {
await assert.rejects(verifyServiceToken('x.y.z', { jwks: doc, audience: 'openvibe.media' }), TypeError);
await assert.rejects(verifyServiceToken('x.y.z', { jwks: doc, audience: 'openvibe.media' }), TypeError);
await assert.rejects(verifyServiceToken('x.y.z', { jwks: doc, contracts }), TypeError);
await assert.rejects(verifyServiceToken('x.y.z', { jwks: doc, contracts }), TypeError);
@@ -143,8 +143,8 @@ export interface ContractsForServiceTokens {
@@ -143,8 +143,8 @@ export interface ContractsForServiceTokens {
}
}
export type ServiceTokenResult = { ok: true; claims: Record<string, unknown> } | { ok: false; code: string; reason: string };
export type ServiceTokenResult = { ok: true; claims: Record<string, unknown> } | { ok: false; code: string; reason: string };
export interface VerifyServiceTokenOptions {
export interface VerifyServiceTokenOptions {
- /** A JWKS URL (the process-wide client: last good keys, rotation, backoff) or a JWKS document. */
- jwks?: string | { keys?: object[]; public_key?: string } | null;
+ /** A JWKS URL (the process-wide client: last good keys, rotation, backoff), a JWKS client the service already holds, or a JWKS document. */
+ jwks?: string | JwksClient | { keys?: object[]; public_key?: string } | null;
/** A pinned PEM instead of a JWKS. */
/** A pinned PEM instead of a JWKS. */
publicKey?: string | object | null;
publicKey?: string | object | null;
issuer?: string;
issuer?: string;