openvibers/openvibe.inventory Commits

Workshop gifts to an @name work (Network's subject is a subject-ref) (#186)

OP OpenVibers committed 7 days ago · aa109fe193 · 2 files, +44 −2

people.subjectOf read body.subject_id, which Network never sends (identity.subject-projection@1 has subject as
{ type, id }), so every @name answered "nobody by that name". It also refused names with . or - that Network allows.
Found while fixing the same misreading in Bot (#304). workshop.test.js stubs this module, so a new test checks it
against the contract's shape.

Changes

ddiff --git a/server/workshop/people.js b/server/workshop/people.js
iindex c5b194a..79694a7 100644
---- a/server/workshop/people.js
++++ b/server/workshop/people.js
@@@ -9,7 +9,8 @@
*/
const { serviceAuth } = require('openvibe-contracts');
--const NAME_RE = /^[A-Za-z0-9_]{3,24}$/;
++// Network's own rule for a name (server/identity/subjects.js resolveUsername): 1-40 of letters, digits, _ . and -.
++const NAME_RE = /^[A-Za-z0-9_.-]{1,40}$/;
const SUBJECT_RE = /^usr_[0-9A-HJKMNP-TV-Z]{26}$/;
function createPeople({ config, fetchImpl = globalThis.fetch }) {
@@@ -33,7 +34,11 @@ function createPeople({ config, fetchImpl = globalThis.fetch }) {
if (res.status === 404) return null;
if (!res.ok) throw new Error(`OpenVibe.Network answered ${res.status}`);
const body = await res.json().catch(() => ({}));
-- const subject = body.subject_id || (body.projection && body.projection.subject_id) || null;
++ // identity.subject-projection@1: subject is a subject-ref { type, id }; only a person (type user) can be given an item.
++ // (2026-11-06: this read subject_id, which Network never sends, so every @name answered "nobody by that name".)
++ const ref = body && body.subject;
++ const subject = ref && typeof ref === 'object' && ref.type === 'user' ? ref.id : null;
++ if (body && (body.banned || body.deleted)) return null;
return SUBJECT_RE.test(String(subject || '')) ? subject : null;
}
ddiff --git a/test/workshop-people.test.js b/test/workshop-people.test.js
nnew file mode 100644
iindex 0000000..597ee4a
---- /dev/null
++++ b/test/workshop-people.test.js
@@@ -0,0 +1,37 @@
++'use strict';
++// The Workshop's "@name" → subject lookup against OpenVibe.Network's real answer (identity.subject-projection@1, whose
++// subject is a subject-ref { type, id }). Until 2026-11-06 it read subject_id, which Network never sends, so every gift
++// to an @name answered "nobody by that name". workshop.test.js stubs this module, so the shape is tested here.
++const assert = require('assert');
++const { createPeople } = require('../server/workshop/people');
++
++const SUBJECT = 'usr_01M1PZ8C7RJGH2J1ATMPQ5B71F';
++function fakeNetwork(answers) {
++ const asked = [];
++ const fetchImpl = async (url) => {
++ const u = new URL(url);
++ if (u.pathname === '/oauth/token') return new Response(JSON.stringify({ access_token: 'svc', token_type: 'Bearer', expires_in: 3600 }), { status: 200, headers: { 'content-type': 'application/json' } });
++ const name = u.searchParams.get('username');
++ asked.push(name);
++ const a = answers[name.toLowerCase()];
++ if (!a) return new Response(JSON.stringify({ code: 'identity.subject_not_found' }), { status: 404, headers: { 'content-type': 'application/json' } });
++ return new Response(JSON.stringify(a), { status: 200, headers: { 'content-type': 'application/json' } });
++ };
++ return { fetchImpl, asked };
++}
++const config = { networkInternalUrl: 'http://network.test', oauth: { clientId: 'inventory', clientSecret: 'x' } };
++const person = (over = {}) => ({ subject: { type: 'user', id: SUBJECT }, username: 'Quinn', display_name: 'Quinn', avatar_url: null, banned: false, ...over });
++
++(async () => {
++ const net = fakeNetwork({ quinn: person(), 'a.b-c': person({ username: 'a.b-c' }), gone: person({ deleted: true }), banned: person({ banned: true }) });
++ const people = createPeople({ config, fetchImpl: net.fetchImpl });
++
++ assert.strictEqual(await people.subjectOf('@Quinn'), SUBJECT, 'the subject-ref id, any case, with or without the @');
++ assert.strictEqual(await people.subjectOf('a.b-c'), SUBJECT, "Network's own name rule: dots and dashes are names too");
++ assert.strictEqual(await people.subjectOf('nobody'), null, 'Network does not know the name');
++ assert.strictEqual(await people.subjectOf('gone'), null, 'a deleted account is nobody to give to');
++ assert.strictEqual(await people.subjectOf('banned'), null, 'nor a banned one');
++ assert.strictEqual(await people.subjectOf('bad name!'), null, 'a name Network would refuse is not asked');
++ assert.ok(!net.asked.includes('bad name!'));
++ console.log('workshop people: all checks passed');
++})().catch((err) => { console.error(err); process.exit(1); });