openvibers/openvibe.network Commits

Principals: media holds identity.subject.resolve (openvibe.network), so Media can record each object's owner as a canonical subject. Its owner_subject backfill and reconcile job resolve the Live user ids Media is given (X-OV-User-Id) through POST /internal/identity/resolve-batch with a service token instead of the shared X-Internal-Key. The principals test now uses openre as the client with no Network grants, and checks Media's token resolves Live ids (an unknown id answers null) and moves no coins

OP OpenVibers committed 23 Sept 2026 · c3b7cf380d · 2 files, +16 −1


Changes

ddiff --git a/server/identity/principals.js b/server/identity/principals.js
iindex e5c808bb..c9503299 100644
---- a/server/identity/principals.js
++++ b/server/identity/principals.js
@@@ -124,6 +124,9 @@ const DEFAULT_GRANTS = [
['billing', 'identity.subject.resolve', SELF_AUDIENCE, []],
['chat', 'identity.subject.resolve', SELF_AUDIENCE, []],
['live', 'identity.subject.resolve', SELF_AUDIENCE, []],
++ // Media records each object's owner as a canonical subject (roadmap D01/D20): it resolves the app-local
++ // owner ids it is given (X-OV-User-Id) through resolve-batch, in its backfill and its reconcile job.
++ ['media', 'identity.subject.resolve', SELF_AUDIENCE, []],
['live', 'community.paste.create', 'openvibe.community', []],
['live', 'community.paste.write', 'openvibe.community', []],
['live', 'community.paste.moderate', 'openvibe.community', []],
ddiff --git a/test/principals.test.js b/test/principals.test.js
iindex bdb432b7..854d4a88 100644
---- a/test/principals.test.js
++++ b/test/principals.test.js
@@@ -64,8 +64,13 @@ const server = http.createServer(app);
const creditOnly = t.body.access_token;
t = await token({ client_id: 'live', client_secret: 'live-secret', audience: 'openvibe.network', scope: 'network.coins.credit network.coins.mint' });
assert.strictEqual(t.status, 400); assert.strictEqual(t.body.error, 'invalid_scope', 'asking for an ungranted capability fails');
-- t = await token({ client_id: 'media', client_secret: 'media-secret', audience: 'openvibe.network' });
++ db.prepare("UPDATE oauth_clients SET client_secret = 'openre-secret' WHERE client_id = 'openre'").run();
++ t = await token({ client_id: 'openre', client_secret: 'openre-secret', audience: 'openvibe.network' });
assert.strictEqual(t.status, 400); assert.strictEqual(t.body.error, 'invalid_scope', 'a client with no grants gets no token');
++ t = await token({ client_id: 'media', client_secret: 'media-secret', audience: 'openvibe.network' });
++ assert.strictEqual(t.status, 200, JSON.stringify(t.body));
++ assert.strictEqual(t.body.scope, 'identity.subject.resolve', 'Media resolves object owners to subjects, and nothing else here');
++ const mediaResolve = t.body.access_token;
t = await token({ client_id: 'live', client_secret: 'live-secret', audience: 'openvibe.media' });
assert.strictEqual(t.status, 400, 'no grants for that audience');
@@@ -108,6 +113,13 @@ const server = http.createServer(app);
assert.strictEqual(r.status, 200); assert.strictEqual(r.body.results['7'].username, 'payee');
r = await post('/internal/identity/resolve-batch', { system: 'network', ids: ['7'] }, { authorization: `Bearer ${creditOnly}` });
assert.strictEqual(r.status, 403, 'a token narrowed to coins cannot resolve identities');
++ // Media's owner_subject backfill: Live user ids -> subjects, an unknown id answers null.
++ db.prepare("INSERT OR IGNORE INTO identity_legacy_map (source_system, source_type, source_id, subject_id) SELECT 'live', 'user', '42', subject_id FROM users WHERE id = 7").run();
++ r = await post('/internal/identity/resolve-batch', { system: 'live', type: 'user', ids: ['42', '43'] }, { authorization: `Bearer ${mediaResolve}` });
++ assert.strictEqual(r.status, 200, JSON.stringify(r.body));
++ assert.match(r.body.results['42'].subject.id, /^usr_/); assert.strictEqual(r.body.results['43'], null);
++ r = await post('/internal/coins/credit', credit(), { authorization: `Bearer ${mediaResolve}` });
++ assert.strictEqual(r.status, 403, 'Media holds no coin capability');
const media = await token({ client_id: 'community', client_secret: 'community-secret', audience: 'openvibe.media' });
assert.strictEqual(media.body.scope, 'media.object.upload', 'community may upload screenshot bytes to Media');
const cm = await token({ client_id: 'live', client_secret: 'live-secret', audience: 'openvibe.community' });