openvibers/openvibe.space Commits

Continue Network-signed-in Website visitors on Space (#293)

OP OpenVibers committed 2 days ago · 54a28ff6cf · 4 files, +72 −1

Changes

ddiff --git a/public/js/website-arrival.js b/public/js/website-arrival.js
nnew file mode 100644
iindex 0000000..16ae2ca
---- /dev/null
++++ b/public/js/website-arrival.js
@@@ -0,0 +1,24 @@
++/* The Website front door has no cookies or account actions. A Network account continues on Space. */
++(function () {
++ 'use strict';
++ if (window.top !== window) return;
++ try { if (localStorage.getItem('ov_sso_hint') === 'guest') return; } catch { /* no stored hint */ }
++ const frame = document.createElement('iframe');
++ frame.setAttribute('aria-hidden', 'true');
++ frame.setAttribute('tabindex', '-1');
++ frame.style.cssText = 'position:absolute;width:0;height:0;border:0;opacity:0;pointer-events:none';
++ const finish = (signedIn) => {
++ clearTimeout(timer);
++ window.removeEventListener('message', onMessage);
++ frame.remove();
++ if (signedIn) location.replace('https://openvibe.space/spaces/mine');
++ };
++ const onMessage = (event) => {
++ if (event.origin !== 'https://openvibe.network' || event.source !== frame.contentWindow || event.data?.type !== 'ov-sso') return;
++ finish(event.data.signedIn === true);
++ };
++ window.addEventListener('message', onMessage);
++ const timer = setTimeout(() => finish(false), 4000);
++ frame.src = `https://openvibe.network/sso/check?origin=${encodeURIComponent(location.origin)}`;
++ document.body.appendChild(frame);
++})();
ddiff --git a/server/render/website.js b/server/render/website.js
iindex 2dbe77f..ee3b5c7 100644
---- a/server/render/website.js
++++ b/server/render/website.js
@@@ -119,7 +119,7 @@ function page({ base, title, canonicalPath, body, jsonLd, robots }) {
name: NAME, service: 'space', lang: 'en', title, siteName: NAME, description: DESCRIPTION,
canonical: `${base}${canonicalPath}`, robots: robots || 'index,follow', type: 'website', summary: DESCRIPTION, url: `${base}${canonicalPath}`,
jsonLd,
-- // Nobody is ever signed in on this host (server/website.js): the navbar's sign-in goes to openvibe.space.
++ // The cookie-free front door sends a Network-signed-in person to Space's own account session.
navbar: { service: 'space', apiBase: 'https://openvibe.network', links, sessionUrl: '/auth/me', loginUrl: `${SPACE}/auth/login?next=/spaces/mine` },
home: '/', navLinks: links.map(({ label, href }) => ({ label, href })),
footer: { service: 'space', variant: 'auto', path: canonicalPath },
@@@ -129,6 +129,7 @@ function page({ base, title, canonicalPath, body, jsonLd, robots }) {
`<link rel="stylesheet" href="${ovServe.url('room.css')}">`,
`<link rel="stylesheet" href="${asset('css/space.css')}">`,
`<link rel="stylesheet" href="${asset('css/website.css')}">`,
++ '<script src="/js/website-arrival.js" defer></script>',
].join('\n'),
bodyAttributes: { 'data-page': 'website' },
body: `<div id="navbar-mount"></div>\n<main id="main" class="page">\n${body}\n</main>`,
ddiff --git a/test/website-arrival.test.js b/test/website-arrival.test.js
nnew file mode 100644
iindex 0000000..9c32138
---- /dev/null
++++ b/test/website-arrival.test.js
@@@ -0,0 +1,44 @@
++'use strict';
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++const vm = require('vm');
++
++const script = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'website-arrival.js'), 'utf8');
++
++function visit(hint) {
++ const calls = { navigations: [], frames: [], message: null };
++ const frame = { contentWindow: {}, setAttribute() {}, style: {}, remove() { calls.removed = true; } };
++ const window = {
++ addEventListener(_type, fn) { calls.message = fn; },
++ removeEventListener() { calls.message = null; },
++ };
++ window.top = window;
++ vm.runInNewContext(script, {
++ window, URL, encodeURIComponent,
++ localStorage: { getItem() { return hint || null; } },
++ document: { createElement() { return frame; }, body: { appendChild(el) { calls.frames.push(el); } } },
++ location: { origin: 'https://openvibe.website', replace(url) { calls.navigations.push(url); } },
++ setTimeout() { return 1; }, clearTimeout() {},
++ });
++ return { calls, frame };
++}
++
++const guest = visit('guest');
++assert.strictEqual(guest.calls.frames.length, 0, 'an explicit guest does not check Network');
++
++const checked = visit();
++assert.strictEqual(checked.calls.frames.length, 1);
++assert.match(checked.frame.src, /^https:\/\/openvibe\.network\/sso\/check\?origin=/);
++checked.calls.message({ origin: 'https://other.example', source: checked.frame.contentWindow, data: { type: 'ov-sso', signedIn: true } });
++checked.calls.message({ origin: 'https://openvibe.network', source: {}, data: { type: 'ov-sso', signedIn: true } });
++assert.deepStrictEqual(checked.calls.navigations, [], 'an unrelated sender cannot move the browser');
++checked.calls.message({ origin: 'https://openvibe.network', source: checked.frame.contentWindow, data: { type: 'ov-sso', signedIn: true } });
++assert.deepStrictEqual(checked.calls.navigations, ['https://openvibe.space/spaces/mine']);
++assert.strictEqual(checked.calls.removed, true);
++
++const negative = visit();
++negative.calls.message({ origin: 'https://openvibe.network', source: negative.frame.contentWindow, data: { type: 'ov-sso', signedIn: false } });
++assert.deepStrictEqual(negative.calls.navigations, [], 'a Network guest stays on the public front door');
++
++console.log('website arrival: guest, verified account, spoofed sender, and Network guest passed');
ddiff --git a/test/website-home.test.js b/test/website-home.test.js
iindex 6fa493e..e9d93b9 100644
---- a/test/website-home.test.js
++++ b/test/website-home.test.js
@@@ -40,11 +40,13 @@ assert.match(hostingSrc, /target\.kind === 'dashboard' \|\| target\.kind === 'we
assert.strictEqual((r.text.match(/<h1[\s>]/g) || []).length, 1, 'one h1');
assert.ok(r.text.includes('"@type":"FAQPage"'), 'its questions are structured data');
assert.ok(r.text.includes('href="https://connect.openvibe.website/"'), 'the other half of openvibe.website: your own computer, through Connect');
++ assert.ok(r.text.includes('src="/js/website-arrival.js"'), 'a Network account continues on Space without a Website cookie');
});
await check('no cookie, ever: none set, none read, no sign-in or API on this host', async () => {
const home = await t.get('openvibe.website', '/', { headers: { cookie: 'ov_token=x; sp_sess=y' } });
assert.strictEqual(home.headers['set-cookie'], undefined, 'the home sets no cookie');
++ assert.strictEqual((await t.get('openvibe.website', '/js/website-arrival.js')).status, 200);
for (const p of ['/auth/login', '/api/v1/projects', '/dashboard', '/spaces/new', '/updates']) {
const r = await t.get('openvibe.website', p);
assert.strictEqual(r.status, 404, `${p} is not served here`);