openvibers/openvibe.network Commits
Chrome page-view beacon: POST /api/chrome/hit answers Cross-Origin-Resource-Policy: cross-origin. navigator.sendBeacon is a no-cors request, and helmet's same-origin policy made every page of every site log the (empty) answer as blocked; counting was never affected
Changes
@@@ -182,7 +182,9 @@ function createChromeService(db, config, analytics, { privateKey = null, issuer// browser-set Origin header (never the body) and must be one of ours or a registered tool domain.const recent = new Map(); // ip → [count, windowStart]: 40/min is plenty for a person, useless for stuffingrouter.post('/hit', (req, res) => {-- res.set({ 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-store' });++ // sendBeacon is a no-cors request: without CORP cross-origin the browser logs every beacon's++ // (empty) answer as blocked on every page of every site (the count itself was never affected).++ res.set({ 'Access-Control-Allow-Origin': '*', 'Cross-Origin-Resource-Policy': 'cross-origin', 'Cache-Control': 'no-store' });try {const ua = String(req.headers['user-agent'] || '');let host = ''; try { host = new URL(String(req.headers.origin || '')).hostname.toLowerCase(); } catch { /* */ }@@@ -76,5 +76,17 @@ for (let i = 0; i < 6; i++) ins.run(1, 'tools', 'dns');await createChromeService(new Database(':memory:'), cfg, null).refreshCopy();assert.equal(liveHits, 0, 'Live is never asked for copy');live.close(); ai.close();++ // The page-view beacon (navigator.sendBeacon, a no-cors request) is readable cross-origin, so no++ // site's console reports it as blocked.++ {++ const express = require('express');++ const app = express(); app.use('/api/chrome', svc.router);++ const srv = await new Promise(r => { const s = app.listen(0, '127.0.0.1', () => r(s)); });++ const r = await fetch(`http://127.0.0.1:${srv.address().port}/api/chrome/hit`, { method: 'POST', headers: { origin: 'https://case.openvibe.tools', 'content-type': 'text/plain' }, body: '' });++ assert.equal(r.status, 204);++ assert.equal(r.headers.get('cross-origin-resource-policy'), 'cross-origin');++ assert.equal(r.headers.get('access-control-allow-origin'), '*');++ srv.close();++ }console.log('chrome service: all checks passed');})().catch(e => { console.error(e); process.exit(1); });
@@ -182,7 +182,9 @@ function createChromeService(db, config, analytics, { privateKey = null, issuer
@@ -182,7 +182,9 @@ function createChromeService(db, config, analytics, { privateKey = null, issuer
// browser-set Origin header (never the body) and must be one of ours or a registered tool domain.
// browser-set Origin header (never the body) and must be one of ours or a registered tool domain.
const recent = new Map(); // ip → [count, windowStart]: 40/min is plenty for a person, useless for stuffing
const recent = new Map(); // ip → [count, windowStart]: 40/min is plenty for a person, useless for stuffing
router.post('/hit', (req, res) => {
router.post('/hit', (req, res) => {
- res.set({ 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-store' });
+ // sendBeacon is a no-cors request: without CORP cross-origin the browser logs every beacon's
+ // (empty) answer as blocked on every page of every site (the count itself was never affected).
+ res.set({ 'Access-Control-Allow-Origin': '*', 'Cross-Origin-Resource-Policy': 'cross-origin', 'Cache-Control': 'no-store' });
try {
try {
const ua = String(req.headers['user-agent'] || '');
const ua = String(req.headers['user-agent'] || '');
let host = ''; try { host = new URL(String(req.headers.origin || '')).hostname.toLowerCase(); } catch { /* */ }
let host = ''; try { host = new URL(String(req.headers.origin || '')).hostname.toLowerCase(); } catch { /* */ }
@@ -76,5 +76,17 @@ for (let i = 0; i < 6; i++) ins.run(1, 'tools', 'dns');
@@ -76,5 +76,17 @@ for (let i = 0; i < 6; i++) ins.run(1, 'tools', 'dns');
await createChromeService(new Database(':memory:'), cfg, null).refreshCopy();
await createChromeService(new Database(':memory:'), cfg, null).refreshCopy();
assert.equal(liveHits, 0, 'Live is never asked for copy');
assert.equal(liveHits, 0, 'Live is never asked for copy');
live.close(); ai.close();
live.close(); ai.close();
+ // The page-view beacon (navigator.sendBeacon, a no-cors request) is readable cross-origin, so no
+ // site's console reports it as blocked.
+ {
+ const express = require('express');
+ const app = express(); app.use('/api/chrome', svc.router);
+ const srv = await new Promise(r => { const s = app.listen(0, '127.0.0.1', () => r(s)); });
+ const r = await fetch(`http://127.0.0.1:${srv.address().port}/api/chrome/hit`, { method: 'POST', headers: { origin: 'https://case.openvibe.tools', 'content-type': 'text/plain' }, body: '' });
+ assert.equal(r.status, 204);
+ assert.equal(r.headers.get('cross-origin-resource-policy'), 'cross-origin');
+ assert.equal(r.headers.get('access-control-allow-origin'), '*');
+ srv.close();
+ }
console.log('chrome service: all checks passed');
console.log('chrome service: all checks passed');
})().catch(e => { console.error(e); process.exit(1); });
})().catch(e => { console.error(e); process.exit(1); });