openvibers/openvibe.network Commits
openvibe.my: the deletion banner asks Network directly, and avatars load (#648)
Changes
@@@ -16,9 +16,18 @@const REAUTH_WINDOW_S = 10 * 60;const BANNER_ID = 'ov-deletion-banner';++ // The account hub on its own origin (openvibe.my, 2026-11-07) asks Network's API directly: a relative call there is++ // redirected to openvibe.network, and a browser drops the Authorization header on a cross-origin redirect (401).++ function onMyHost() {++ const h = root && root.location && root.location.hostname;++ return h === 'openvibe.my' || h === 'www.openvibe.my';++ }function base() {++ if (onMyHost()) return 'https://openvibe.network';return (root && root.location && root.location.origin) || '';}++ // No cookie crosses to openvibe.network from openvibe.my: the Bearer token is the whole credential there.++ function credentials() { return onMyHost() ? 'omit' : 'include'; }function cookieToken() {try {@@@ -70,7 +79,7 @@if (!t) return null;const res = await fetch(base() + '/api/v1/account/deletion', {headers: { Authorization: 'Bearer ' + t },-- credentials: 'include',++ credentials: credentials(),});if (!res.ok) return null;const body = await res.json().catch(() => ({}));@@@ -83,7 +92,7 @@const res = await fetch(base() + '/api/v1/account/deletion', {method: 'DELETE',headers: { Authorization: 'Bearer ' + t },-- credentials: 'include',++ credentials: credentials(),});let body = {};try { body = await res.json(); } catch { /* no body */ }@@@ -125,7 +125,8 @@ app.use(helmet({fontSrc: ["'self'", "fonts.gstatic.com"],// Avatars and media live on openvibe.media; the home page's live-stream thumbnails come from openvibe.live and// its "Right now" screenshot pastes from openvibe.community (blocked until 2026-11-06: the card showed nothing).-- imgSrc: ["'self'", "data:", "blob:", "https://openvibe.media", "https://openvibe.live", "https://openvibe.community"],++ // openvibe.network itself too: the account hub on openvibe.my draws avatars from Network's /avatar (2026-11-07).++ imgSrc: ["'self'", "data:", "blob:", "https://openvibe.network", "https://openvibe.media", "https://openvibe.live", "https://openvibe.community"],connectSrc: ["'self'","https://openvibe.network",@@@ -34,20 +34,20 @@ const read = (p) => fs.readFileSync(path.join(ROOT, p), 'utf8');// ── The browser helpers: one re-authentication URL, and the banner ───────────const safety = read('public/js/account-safety.js');--function safetyWindow() {++function safetyWindow(location = { origin: 'https://openvibe.network', hostname: 'openvibe.network' }) {const { window: win } = parseHTML('<!doctype html><html><body></body></html>');const store = new Map();const calls = [];const root = {document: win.document,-- location: { origin: 'https://openvibe.network' },++ location,localStorage: {getItem: (k) => (store.has(k) ? store.get(k) : null),setItem: (k, v) => store.set(k, String(v)),removeItem: (k) => store.delete(k),},fetch: async (url, opts = {}) => {-- calls.push({ url: String(url), method: opts.method || 'GET', headers: opts.headers || {} });++ calls.push({ url: String(url), method: opts.method || 'GET', headers: opts.headers || {}, credentials: opts.credentials });if ((opts.method || 'GET') === 'DELETE') {return { ok: true, status: 200, json: async () => ({ status: 'cancelled', deletion_id: 'del_x' }) };}@@@ -251,7 +251,22 @@ assert.strictEqual(reauthUrl('https://openvibe.network', '/my#delete'), 'https:/assert.strictEqual(signInUrl({ loginUrl: 'https://openvibe.network', baseUrl: 'https://openvibe.network' }),'https://openvibe.network/login?reauth=1&return=https%3A%2F%2Fopenvibe.network%2Fadmin%23ai');--Promise.all([bannerTest, serverTest()]).then(() => console.log('account deletion reauth: all checks passed')).catch((err) => {++// On openvibe.my (the account hub's own origin) the banner asks Network directly, with the Bearer and no cookie: a++// relative call is redirected there and the browser drops the Authorization header on that redirect (a 401).++const myHostTest = (async () => {++ const { root, calls, api } = safetyWindow({ origin: 'https://openvibe.my', hostname: 'openvibe.my' });++ root.localStorage.setItem('openvibe_accounts', JSON.stringify([{ id: 7, username: 'Me', token: 'tok-saved' }]));++ root.localStorage.setItem('openvibe_active_account', '7');++ calls.length = 0;++ await api.deletionStatus();++ const c = calls.find((x) => x.url.includes('/api/v1/account/deletion'));++ assert.ok(c, 'the status was asked');++ assert.strictEqual(c.url, 'https://openvibe.network/api/v1/account/deletion');++ assert.strictEqual(c.credentials, 'omit', 'no cookie crosses from openvibe.my');++ assert.strictEqual(c.headers.Authorization, 'Bearer tok-saved');++})();++++Promise.all([bannerTest, myHostTest, serverTest()]).then(() => console.log('account deletion reauth: all checks passed')).catch((err) => {console.error(err);process.exitCode = 1;});
@@ -16,9 +16,18 @@
@@ -16,9 +16,18 @@
const REAUTH_WINDOW_S = 10 * 60;
const REAUTH_WINDOW_S = 10 * 60;
const BANNER_ID = 'ov-deletion-banner';
const BANNER_ID = 'ov-deletion-banner';
+ // The account hub on its own origin (openvibe.my, 2026-11-07) asks Network's API directly: a relative call there is
+ // redirected to openvibe.network, and a browser drops the Authorization header on a cross-origin redirect (401).
+ function onMyHost() {
+ const h = root && root.location && root.location.hostname;
+ return h === 'openvibe.my' || h === 'www.openvibe.my';
+ }
function base() {
function base() {
+ if (onMyHost()) return 'https://openvibe.network';
return (root && root.location && root.location.origin) || '';
return (root && root.location && root.location.origin) || '';
}
}
+ // No cookie crosses to openvibe.network from openvibe.my: the Bearer token is the whole credential there.
+ function credentials() { return onMyHost() ? 'omit' : 'include'; }
function cookieToken() {
function cookieToken() {
try {
try {
@@ -70,7 +79,7 @@
@@ -70,7 +79,7 @@
if (!t) return null;
if (!t) return null;
const res = await fetch(base() + '/api/v1/account/deletion', {
const res = await fetch(base() + '/api/v1/account/deletion', {
headers: { Authorization: 'Bearer ' + t },
headers: { Authorization: 'Bearer ' + t },
- credentials: 'include',
+ credentials: credentials(),
});
});
if (!res.ok) return null;
if (!res.ok) return null;
const body = await res.json().catch(() => ({}));
const body = await res.json().catch(() => ({}));
@@ -83,7 +92,7 @@
@@ -83,7 +92,7 @@
const res = await fetch(base() + '/api/v1/account/deletion', {
const res = await fetch(base() + '/api/v1/account/deletion', {
method: 'DELETE',
method: 'DELETE',
headers: { Authorization: 'Bearer ' + t },
headers: { Authorization: 'Bearer ' + t },
- credentials: 'include',
+ credentials: credentials(),
});
});
let body = {};
let body = {};
try { body = await res.json(); } catch { /* no body */ }
try { body = await res.json(); } catch { /* no body */ }
@@ -125,7 +125,8 @@ app.use(helmet({
@@ -125,7 +125,8 @@ app.use(helmet({
fontSrc: ["'self'", "fonts.gstatic.com"],
fontSrc: ["'self'", "fonts.gstatic.com"],
// Avatars and media live on openvibe.media; the home page's live-stream thumbnails come from openvibe.live and
// Avatars and media live on openvibe.media; the home page's live-stream thumbnails come from openvibe.live and
// its "Right now" screenshot pastes from openvibe.community (blocked until 2026-11-06: the card showed nothing).
// its "Right now" screenshot pastes from openvibe.community (blocked until 2026-11-06: the card showed nothing).
- imgSrc: ["'self'", "data:", "blob:", "https://openvibe.media", "https://openvibe.live", "https://openvibe.community"],
+ // openvibe.network itself too: the account hub on openvibe.my draws avatars from Network's /avatar (2026-11-07).
+ imgSrc: ["'self'", "data:", "blob:", "https://openvibe.network", "https://openvibe.media", "https://openvibe.live", "https://openvibe.community"],
connectSrc: [
connectSrc: [
"'self'",
"'self'",
"https://openvibe.network",
"https://openvibe.network",
@@ -34,20 +34,20 @@ const read = (p) => fs.readFileSync(path.join(ROOT, p), 'utf8');
@@ -34,20 +34,20 @@ const read = (p) => fs.readFileSync(path.join(ROOT, p), 'utf8');
// ── The browser helpers: one re-authentication URL, and the banner ───────────
// ── The browser helpers: one re-authentication URL, and the banner ───────────
const safety = read('public/js/account-safety.js');
const safety = read('public/js/account-safety.js');
-function safetyWindow() {
+function safetyWindow(location = { origin: 'https://openvibe.network', hostname: 'openvibe.network' }) {
const { window: win } = parseHTML('<!doctype html><html><body></body></html>');
const { window: win } = parseHTML('<!doctype html><html><body></body></html>');
const store = new Map();
const store = new Map();
const calls = [];
const calls = [];
const root = {
const root = {
document: win.document,
document: win.document,
- location: { origin: 'https://openvibe.network' },
+ location,
localStorage: {
localStorage: {
getItem: (k) => (store.has(k) ? store.get(k) : null),
getItem: (k) => (store.has(k) ? store.get(k) : null),
setItem: (k, v) => store.set(k, String(v)),
setItem: (k, v) => store.set(k, String(v)),
removeItem: (k) => store.delete(k),
removeItem: (k) => store.delete(k),
},
},
fetch: async (url, opts = {}) => {
fetch: async (url, opts = {}) => {
- calls.push({ url: String(url), method: opts.method || 'GET', headers: opts.headers || {} });
+ calls.push({ url: String(url), method: opts.method || 'GET', headers: opts.headers || {}, credentials: opts.credentials });
if ((opts.method || 'GET') === 'DELETE') {
if ((opts.method || 'GET') === 'DELETE') {
return { ok: true, status: 200, json: async () => ({ status: 'cancelled', deletion_id: 'del_x' }) };
return { ok: true, status: 200, json: async () => ({ status: 'cancelled', deletion_id: 'del_x' }) };
}
}
@@ -251,7 +251,22 @@ assert.strictEqual(reauthUrl('https://openvibe.network', '/my#delete'), 'https:/
@@ -251,7 +251,22 @@ assert.strictEqual(reauthUrl('https://openvibe.network', '/my#delete'), 'https:/
assert.strictEqual(signInUrl({ loginUrl: 'https://openvibe.network', baseUrl: 'https://openvibe.network' }),
assert.strictEqual(signInUrl({ loginUrl: 'https://openvibe.network', baseUrl: 'https://openvibe.network' }),
'https://openvibe.network/login?reauth=1&return=https%3A%2F%2Fopenvibe.network%2Fadmin%23ai');
'https://openvibe.network/login?reauth=1&return=https%3A%2F%2Fopenvibe.network%2Fadmin%23ai');
-Promise.all([bannerTest, serverTest()]).then(() => console.log('account deletion reauth: all checks passed')).catch((err) => {
+// On openvibe.my (the account hub's own origin) the banner asks Network directly, with the Bearer and no cookie: a
+// relative call is redirected there and the browser drops the Authorization header on that redirect (a 401).
+const myHostTest = (async () => {
+ const { root, calls, api } = safetyWindow({ origin: 'https://openvibe.my', hostname: 'openvibe.my' });
+ root.localStorage.setItem('openvibe_accounts', JSON.stringify([{ id: 7, username: 'Me', token: 'tok-saved' }]));
+ root.localStorage.setItem('openvibe_active_account', '7');
+ calls.length = 0;
+ await api.deletionStatus();
+ const c = calls.find((x) => x.url.includes('/api/v1/account/deletion'));
+ assert.ok(c, 'the status was asked');
+ assert.strictEqual(c.url, 'https://openvibe.network/api/v1/account/deletion');
+ assert.strictEqual(c.credentials, 'omit', 'no cookie crosses from openvibe.my');
+ assert.strictEqual(c.headers.Authorization, 'Bearer tok-saved');
+})();
+
+Promise.all([bannerTest, myHostTest, serverTest()]).then(() => console.log('account deletion reauth: all checks passed')).catch((err) => {
console.error(err);
console.error(err);
process.exitCode = 1;
process.exitCode = 1;
});
});