openvibers/openvibe.events Commits

The live feed answers every first-party site (manifest domains; contracts 0.280.0) (#225)

OP OpenVibers committed 10 days ago · 3008179382 · 6 files, +35 −9

* The live feed answers every first-party site: realtime CORS takes the manifests' domains; contracts 0.280.0

The realtime stream allowed https://*.openvibe.* and REALTIME_CORS_ORIGINS only, so open.shoes's footer feed
(host.release.published) was refused by the browser (no Access-Control-Allow-Origin; found by a sweep of every
site, 11-02). Now every exact https domain the service manifests declare is allowed too, as Network's
first-party-origins.js does (never a wildcard). The contracts pin moves 0.222.0 → 0.280.0, whose manifests name
the newer sites (shoes among them).

* Vision is a source; the envelope test follows contracts 0.280.0 (two-segment types are valid)

The 0.280.0 manifests name Vision as a producer (vision.result.ready, vision.alert), which Events refused as
events.unknown_source; it joins the default sources. 0.280.0's envelope allows two-segment event types, so the
test's invalid case is now a one-segment 'live'.

Changes

ddiff --git a/package-lock.json b/package-lock.json
iindex 433affb..4b50d61 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -12,7 +12,7 @@
"dotenv": "^16.4.7",
"express": "^4.21.2",
"iovalkey": "^0.4.0",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.280.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.106.2",
"pg": "^8.23.0"
@@@ -1103,9 +1103,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.222.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0",
-- "integrity": "sha512-wdUQCilq9glRMwfxEswbIj3l0QQqKDXZAi2+CR5Mlh34rZjI1Q+7GlAoR0UtNwZAgtLwXhPxFyJ/aPk8lcDG1Q==",
++ "version": "0.280.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.280.0",
++ "integrity": "sha512-OOU5z5gwXZkXl+eWiRJHsbZoNC+nDL3LK+kXHo8TcMlgijTkGyvLIUlKzRDqYXthk0KnYK91dS0VuzRNkVLPIw==",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex 20da512..1471974 100644
---- a/package.json
++++ b/package.json
@@@ -24,7 +24,7 @@
"dotenv": "^16.4.7",
"express": "^4.21.2",
"iovalkey": "^0.4.0",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.280.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.106.2",
"pg": "^8.23.0"
ddiff --git a/server/config.js b/server/config.js
iindex f83cc87..755cb29 100644
---- a/server/config.js
++++ b/server/config.js
@@@ -52,7 +52,10 @@ const DEFAULT_SOURCES = ['live', 'media', 'network', 'community', 'chat', 'openr
// Report (openvibe.report): report.index_document.* (contracts 0.275.0), its Rundown topic pages in Search.
'report',
// Design (openvibe.design): design.index_document.* (contracts 0.275.0), its icon and theme pages in Search.
-- 'design'];
++ 'design',
++ // Vision (openvibe.vision, VIS-5): vision.result.ready, vision.alert. Its manifest declared them, but it was missing
++ // here, so its events were refused as events.unknown_source (found 11-02 when the contracts pin moved to 0.280.0).
++ 'vision'];
/**
* Namespaces that belong to the network rather than to one service. `provider.*` carries storage
ddiff --git a/server/realtime.js b/server/realtime.js
iindex 2dc5df4..fa4dda5 100644
---- a/server/realtime.js
++++ b/server/realtime.js
@@@ -38,6 +38,24 @@ const cursor = require('./cursor');
const { rowToEnvelope } = require('./store');
const ORIGIN_RE = /^https:\/\/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*openvibe\.[a-z]{2,63}$/;
++// Every first-party origin the service manifests declare (openvibe-contracts manifests/services/*.json `domains`),
++// exact https origins only: the sites not named openvibe.* (open.shoes, openre.stream, …) get the live release feed
++// too. 11-02: open.shoes's footer was refused ("No 'Access-Control-Allow-Origin'"). As Network's first-party-origins.js.
++const HOST_RE = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/;
++let manifestOriginSet = null;
++function manifestOrigins() {
++ if (manifestOriginSet) return manifestOriginSet;
++ const out = new Set();
++ const manifests = (require('openvibe-contracts').services || {}).manifests || [];
++ for (const m of manifests) {
++ for (const d of m.domains || []) {
++ const host = String(d || '').trim().toLowerCase();
++ if (HOST_RE.test(host)) out.add(`https://${host}`);
++ }
++ }
++ manifestOriginSet = out;
++ return out;
++}
const MAX_BUFFERED = 1024 * 1024; // a client this far behind is dropped; it resumes with Last-Event-ID
function createRealtime({ store, auth, config, clock = { now: () => Date.now() }, log = console }) {
@@@ -46,10 +64,10 @@ function createRealtime({ store, auth, config, clock = { now: () => Date.now() }
let heartbeat = null;
function originAllowed(origin) {
-- return typeof origin === 'string' && (ORIGIN_RE.test(origin) || opts.extraOrigins.includes(origin));
++ return typeof origin === 'string' && (ORIGIN_RE.test(origin) || manifestOrigins().has(origin) || opts.extraOrigins.includes(origin));
}
-- /** CORS for browser EventSource({ withCredentials: true }) from https://*.openvibe.* only. */
++ /** CORS for browser EventSource({ withCredentials: true }) from https://*.openvibe.* and the manifests' domains only. */
function cors(req, res, next) {
const origin = req.headers.origin;
res.setHeader('Vary', 'Origin');
ddiff --git a/test/publish.test.js b/test/publish.test.js
iindex 80eb2bc..2acc2e0 100644
---- a/test/publish.test.js
++++ b/test/publish.test.js
@@@ -49,7 +49,7 @@ t('valid envelope -> 201 with a cursor; defaults filled', async () => {
t('bad envelope -> 422 events.invalid_envelope with errors', async () => {
for (const bad of [
{ ...envelope(), event_id: 'nope' },
-- { ...envelope(), event_type: 'live.started' }, // needs 3+ segments
++ { ...envelope(), event_type: 'live' }, // needs 2+ segments (contracts 0.280.0; it was 3+)
{ ...envelope(), actor: { type: 'user', id: 57 } },
{ ...envelope(), extra: true },
(() => { const e = envelope(); delete e.subject; return e; })(),
ddiff --git a/test/realtime.test.js b/test/realtime.test.js
iindex 6ea3a0f..d3567a6 100644
---- a/test/realtime.test.js
++++ b/test/realtime.test.js
@@@ -158,6 +158,11 @@ t('CORS: credentials only for https://*.openvibe.* origins', async () => {
r = await fetch(`${h.base}/realtime/stream?topics=live.*`, { method: 'OPTIONS', headers: { Origin: origin } });
assert.strictEqual(r.headers.get('access-control-allow-origin'), null, origin);
}
++ // A first-party site not named openvibe.* (a manifest's domain) is allowed; a look-alike is not.
++ r = await fetch(`${h.base}/realtime/stream?topics=live.*`, { method: 'OPTIONS', headers: { Origin: 'https://open.shoes', 'Access-Control-Request-Method': 'GET' } });
++ assert.strictEqual(r.headers.get('access-control-allow-origin'), 'https://open.shoes', 'open.shoes is in the manifests');
++ r = await fetch(`${h.base}/realtime/stream?topics=live.*`, { method: 'OPTIONS', headers: { Origin: 'https://evil.shoes', 'Access-Control-Request-Method': 'GET' } });
++ assert.strictEqual(r.headers.get('access-control-allow-origin'), null, 'a domain no manifest names');
const c = await stream('topics=live.*', { Origin: 'https://live.openvibe.network' });
assert.strictEqual(c.headers['access-control-allow-origin'], 'https://live.openvibe.network');
c.close();