openvibers/openvibe.network Commits
Avatar import sends the account's subject (#127)
Changes
@@@ -80,7 +80,8 @@ function createAvatarService({ db, config, requireAuth, selfToken = () => null,if (!Object.keys(auth).length) return { ok: false, status: 503, error: 'Picture import is not configured on this server' };try {const r = await fetch(`${media.replace(/\/$/, '')}/internal/avatar-ingest`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...auth },-- body: JSON.stringify({ url, user_id: user.id, username: user.username }), signal: AbortSignal.timeout(20000) });++ // subject: the picture's owner on Media's object, in the network-wide id space (user_id is Network's own).++ body: JSON.stringify({ url, user_id: user.id, username: user.username, subject: user.subject_id || null }), signal: AbortSignal.timeout(20000) });const j = await r.json().catch(() => ({}));if (!r.ok || !j.ok) return { ok: false, status: r.status === 404 ? 503 : 422, error: j.error || 'That picture could not be imported' };const back = normalizeAvatar(j.url); // trust nothing: the answer must itself be an openvibe.media address@@@ -240,7 +240,7 @@ function inventory(root) {const ingested = [];const media = http.createServer((req, res) => {let b = ''; req.on('data', (d) => { b += d; });-- req.on('end', () => { try { ingested.push({ path: req.url, url: JSON.parse(b).url }); } catch { /* */ } res.writeHead(422, { 'content-type': 'application/json' }).end('{"ok":false,"error":"refused"}'); });++ req.on('end', () => { try { const j = JSON.parse(b); ingested.push({ path: req.url, url: j.url, subject: j.subject }); } catch { /* */ } res.writeHead(422, { 'content-type': 'application/json' }).end('{"ok":false,"error":"refused"}'); });});await new Promise((r) => media.listen(0, '127.0.0.1', r));const w = await buildWorld({ label: 'ssrf', env: { OV_MEDIA_INTERNAL_URL: `http://127.0.0.1:${media.address().port}` } });@@@ -270,6 +270,7 @@ function inventory(root) {assert.match(gh.text, /no egress to api\.github\.com|could not be reached/, 'the GitHub integration test goes to api.github.com only (the egress guard refuses the socket; undici surfaces it as a fetch failure)');assert.ok(ingested.length >= 10 && ingested.every((x) => x.path === '/internal/avatar-ingest'), `avatar imports went to Media's ingest instead (${ingested.length})`);assert.ok(ingested.some((x) => x.url === `https://127.0.0.1:${T}/a.png`), 'Media was asked to import the URL; Network did not fetch it');++ assert.ok(ingested.every((x) => x.subject === w.users.alice.subject), 'the import names its owner by subject, so Media records it on the object');} finally {await target.close();await new Promise((r) => media.close(r));
@@ -80,7 +80,8 @@ function createAvatarService({ db, config, requireAuth, selfToken = () => null,
@@ -80,7 +80,8 @@ function createAvatarService({ db, config, requireAuth, selfToken = () => null,
if (!Object.keys(auth).length) return { ok: false, status: 503, error: 'Picture import is not configured on this server' };
if (!Object.keys(auth).length) return { ok: false, status: 503, error: 'Picture import is not configured on this server' };
try {
try {
const r = await fetch(`${media.replace(/\/$/, '')}/internal/avatar-ingest`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...auth },
const r = await fetch(`${media.replace(/\/$/, '')}/internal/avatar-ingest`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...auth },
- body: JSON.stringify({ url, user_id: user.id, username: user.username }), signal: AbortSignal.timeout(20000) });
+ // subject: the picture's owner on Media's object, in the network-wide id space (user_id is Network's own).
+ body: JSON.stringify({ url, user_id: user.id, username: user.username, subject: user.subject_id || null }), signal: AbortSignal.timeout(20000) });
const j = await r.json().catch(() => ({}));
const j = await r.json().catch(() => ({}));
if (!r.ok || !j.ok) return { ok: false, status: r.status === 404 ? 503 : 422, error: j.error || 'That picture could not be imported' };
if (!r.ok || !j.ok) return { ok: false, status: r.status === 404 ? 503 : 422, error: j.error || 'That picture could not be imported' };
const back = normalizeAvatar(j.url); // trust nothing: the answer must itself be an openvibe.media address
const back = normalizeAvatar(j.url); // trust nothing: the answer must itself be an openvibe.media address
@@ -240,7 +240,7 @@ function inventory(root) {
@@ -240,7 +240,7 @@ function inventory(root) {
const ingested = [];
const ingested = [];
const media = http.createServer((req, res) => {
const media = http.createServer((req, res) => {
let b = ''; req.on('data', (d) => { b += d; });
let b = ''; req.on('data', (d) => { b += d; });
- req.on('end', () => { try { ingested.push({ path: req.url, url: JSON.parse(b).url }); } catch { /* */ } res.writeHead(422, { 'content-type': 'application/json' }).end('{"ok":false,"error":"refused"}'); });
+ req.on('end', () => { try { const j = JSON.parse(b); ingested.push({ path: req.url, url: j.url, subject: j.subject }); } catch { /* */ } res.writeHead(422, { 'content-type': 'application/json' }).end('{"ok":false,"error":"refused"}'); });
});
});
await new Promise((r) => media.listen(0, '127.0.0.1', r));
await new Promise((r) => media.listen(0, '127.0.0.1', r));
const w = await buildWorld({ label: 'ssrf', env: { OV_MEDIA_INTERNAL_URL: `http://127.0.0.1:${media.address().port}` } });
const w = await buildWorld({ label: 'ssrf', env: { OV_MEDIA_INTERNAL_URL: `http://127.0.0.1:${media.address().port}` } });
@@ -270,6 +270,7 @@ function inventory(root) {
@@ -270,6 +270,7 @@ function inventory(root) {
assert.match(gh.text, /no egress to api\.github\.com|could not be reached/, 'the GitHub integration test goes to api.github.com only (the egress guard refuses the socket; undici surfaces it as a fetch failure)');
assert.match(gh.text, /no egress to api\.github\.com|could not be reached/, 'the GitHub integration test goes to api.github.com only (the egress guard refuses the socket; undici surfaces it as a fetch failure)');
assert.ok(ingested.length >= 10 && ingested.every((x) => x.path === '/internal/avatar-ingest'), `avatar imports went to Media's ingest instead (${ingested.length})`);
assert.ok(ingested.length >= 10 && ingested.every((x) => x.path === '/internal/avatar-ingest'), `avatar imports went to Media's ingest instead (${ingested.length})`);
assert.ok(ingested.some((x) => x.url === `https://127.0.0.1:${T}/a.png`), 'Media was asked to import the URL; Network did not fetch it');
assert.ok(ingested.some((x) => x.url === `https://127.0.0.1:${T}/a.png`), 'Media was asked to import the URL; Network did not fetch it');
+ assert.ok(ingested.every((x) => x.subject === w.users.alice.subject), 'the import names its owner by subject, so Media records it on the object');
} finally {
} finally {
await target.close();
await target.close();
await new Promise((r) => media.close(r));
await new Promise((r) => media.close(r));