openvibers/openvibe.rent Commits

Open Rent messages in Dock Chats (#249)

OP OpenVibers committed 4 days ago · f1f3258886 · 5 files, +25 −1

* Give Rent navbar a verified Dock token

* Keep Dock token reads outside sign-in rate limit

Changes

ddiff --git a/deploy/nginx/openvibe.rent.conf b/deploy/nginx/openvibe.rent.conf
iindex dc0b477..639b370 100644
---- a/deploy/nginx/openvibe.rent.conf
++++ b/deploy/nginx/openvibe.rent.conf
@@@ -77,6 +77,13 @@ server {
proxy_pass http://127.0.0.1:5010;
}
++ # Give the Dock token the session probe's rate-limit budget.
++ location = /auth/navbar-token {
++ limit_req zone=rent_api burst=30 nodelay;
++ limit_req_status 429;
++ proxy_pass http://127.0.0.1:5010;
++ }
++
location /auth/ {
limit_req zone=rent_auth burst=10 nodelay;
limit_req_status 429;
ddiff --git a/server/app.js b/server/app.js
iindex 354f223..fa74dea 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -171,7 +171,7 @@ async function createApp(opts = {}) {
// ── Sign-in (OAuth2 + PKCE client of OpenVibe.Network) ──
// /auth/me is the navbar's who-is-signed-in read on every page, so it is not counted with sign-ins: 60 page views in 15 minutes made a signed-in visitor look signed out.
-- app.use('/auth/', rateLimit({ windowMs: 15 * 60_000, limit: 60, skip: (req) => req.path === '/me', standardHeaders: true, legacyHeaders: false }));
++ app.use('/auth/', rateLimit({ windowMs: 15 * 60_000, limit: 60, skip: (req) => req.path === '/me' || req.path === '/navbar-token', standardHeaders: true, legacyHeaders: false }));
const rentAuth = sso.routes(), homesAuth = homesSso.routes();
app.use('/auth', (req, res, next) => (req.hostname === homesAuthHost ? homesAuth : rentAuth)(req, res, next));
{ const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'rent', service: 'rent', host: 'openvibe.rent', name: 'OpenVibe.Rent', profile: 'ugc' })); }
ddiff --git a/server/auth/sso.js b/server/auth/sso.js
iindex 15c6787..0632ff4 100644
---- a/server/auth/sso.js
++++ b/server/auth/sso.js
@@@ -195,6 +195,14 @@ function createSso({ config, keys, fetchImpl = globalThis.fetch, now = () => Dat
return res.json({ user: { username: v.username, display_name: v.displayName, subject_id: v.subject, role: v.role, avatar_url: v.avatarUrl || null, profile_color: v.profileColor || null } });
});
++ // The shared Dock needs a verified Network access token from this HTTP-only session.
++ r.get('/navbar-token', (req, res) => {
++ res.set('Cache-Control', cache.htmlHeaders({ private: true }));
++ const viewer = req.viewer;
++ if (!viewer || viewer.kind !== 'user' || !viewer.token) return res.status(401).json({ error: 'Not signed in' });
++ return res.json({ token: viewer.token });
++ });
++
r.get('/logout', (req, res, next) => {
if (sameOriginRequest(req)) return next();
res.set('Cache-Control', cache.htmlHeaders({ private: true }));
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex c19a3a5..50c2b0f 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -93,6 +93,7 @@ function renderPage(o) {
history: { type: 'page', title: o.title || SITE_NAME },
silentLogin: '/auth/login?silent=1&next={url}',
sessionUrl: '/auth/me',
++ tokenUrl: '/auth/navbar-token',
loginUrl: '/auth/login?next={path}',
logoutUrl: '/auth/logout?next={path}',
notificationsRealtime: true,
ddiff --git a/test/security-session.test.js b/test/security-session.test.js
iindex b8fa180..1a197f9 100644
---- a/test/security-session.test.js
++++ b/test/security-session.test.js
@@@ -56,6 +56,14 @@ const { signJwt } = require('./helpers/mocks');
assert.strictEqual(JSON.parse(me.text).user.username, staff.username);
});
++ await check('the navbar receives a verified, uncached Network token only for a signed-in person', async () => {
++ const nav = await s.get('/auth/navbar-token', { as: staff });
++ assert.strictEqual(nav.status, 200);
++ assert.match(JSON.parse(nav.text).token, /^[^.]+\.[^.]+\.[^.]+$/);
++ assert.strictEqual(nav.headers.get('cache-control'), 'private, no-store');
++ assert.strictEqual((await s.get('/auth/navbar-token')).status, 401);
++ });
++
await check('another site cannot sign a person out; this site can', async () => {
const cross = await s.get('/auth/logout', { as: staff, headers: { 'sec-fetch-site': 'cross-site' } });
assert.strictEqual(cross.status, 200, 'a confirm page, not a sign-out');