openvibers/openvibe.connect Commits

Open Connect messages in Dock Chats (#77)

OP OpenVibers committed 4 days ago · ce9bad1927 · 5 files, +24 −1

* Give Connect navbar a verified Dock token

* Keep Dock token reads outside sign-in rate limit

Changes

ddiff --git a/deploy/nginx/connect.openvibe.website.conf b/deploy/nginx/connect.openvibe.website.conf
iindex ce2b699..792b2f0 100644
---- a/deploy/nginx/connect.openvibe.website.conf
++++ b/deploy/nginx/connect.openvibe.website.conf
@@@ -92,6 +92,13 @@ server {
proxy_pass http://127.0.0.1:5240;
}
++ # The Dock token is fetched on signed-in page views; give it the session probe's budget.
++ location = /auth/navbar-token {
++ limit_req zone=connect_api burst=30 nodelay;
++ limit_req_status 429;
++ proxy_pass http://127.0.0.1:5240;
++ }
++
location /auth/ {
limit_req zone=connect_auth burst=10 nodelay;
limit_req_status 429;
ddiff --git a/server/app.js b/server/app.js
iindex 73d62d2..634efd3 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -241,7 +241,7 @@ async function createApp(opts = {}) {
app.use(sso.middleware());
// ── Sign-in (OAuth2 + PKCE client of OpenVibe.Network) ──
-- app.use('/auth/', rateLimit({ windowMs: 15 * 60_000, limit: 60, 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 }));
app.use('/auth', sso.routes());
{ const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'connect', service: 'connect', host: 'connect.openvibe.link', name: 'OpenVibe Connect', profile: 'ugc' })); }
ddiff --git a/server/auth/sso.js b/server/auth/sso.js
iindex 7b8ad94..4b7f818 100644
---- a/server/auth/sso.js
++++ b/server/auth/sso.js
@@@ -183,6 +183,16 @@ function createSso({ config, keys, fetchImpl = globalThis.fetch, now = () => Dat
return res.json({ user: meOf(claims) });
});
++ // The navbar needs a Network access token for the Dock. Keep /auth/me's
++ // profile response token-free; this same-origin answer is never cached.
++ r.get('/navbar-token', async (req, res) => {
++ res.set('Cache-Control', 'private, no-store');
++ const token = sso.extractToken(req);
++ const claims = await sso.verify(token);
++ if (!claims) return res.status(401).json({ error: 'Not signed in' });
++ return res.json({ token, expires_at: claims.exp ? claims.exp * 1000 : null });
++ });
++
// Sign-out stays a same-site action: another site cannot sign a person out.
const sameOriginRequest = (req) => {
const site = String(req.get('sec-fetch-site') || '');
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 4dc638f..8542793 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -61,6 +61,7 @@ function layout(o) {
],
silentLogin: `${String(config.baseUrl).replace(/\/+$/, '')}/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/sso.test.js b/test/sso.test.js
iindex 295352e..8aee8b0 100644
---- a/test/sso.test.js
++++ b/test/sso.test.js
@@@ -44,6 +44,11 @@ const cookieJar = (res) => res.headers.getSetCookie().map((c) => c.split(';')[0]
const me = await t.get('/auth/me', { cookie: `ov_token=${at}` });
assert.strictEqual(me.json().user.username, 'kim');
assert.ok(!me.text.includes(at), '/auth/me never returns the token');
++ const nav = await t.get('/auth/navbar-token', { cookie: `ov_token=${at}` });
++ assert.strictEqual(nav.status, 200);
++ assert.strictEqual(nav.json().token, at, 'the Dock receives the verified Network token');
++ assert.strictEqual(nav.headers.get('cache-control'), 'private, no-store');
++ assert.strictEqual((await t.get('/auth/navbar-token')).status, 401, 'a guest receives no token');
});
await check('a wrong state, a verifier that does not match, or no flow cookie starts the sign-in once more', async () => {