openvibers/openvibe.chat Commits

A guest's session probe answers 200, not 401 (browser check, WS-Q task 3): GET /auth/me with no ov_token cookie and no Authorization header answers { user: null }, which the shared navbar reads as signed out. It asks on every page view, and the 401 logged a console error on each. A credential that is present but invalid or expired still answers 401. Test: web.test.js

OP OpenVibers committed 26 Sept 2026 · cf3807aa7f · 2 files, +19 −2


Changes

ddiff --git a/server/web/session.js b/server/web/session.js
iindex d43ada6..19518c9 100644
---- a/server/web/session.js
++++ b/server/web/session.js
@@@ -7,7 +7,7 @@
* GET /auth/callback → server-side code exchange, sets the cookies
* POST /auth/fedcm → the shared navbar's FedCM assertion (nonce checked here, signature by the Network)
* GET /auth/logout → clears the cookies (best-effort refresh revoke)
-- * GET /auth/me → the signed-in profile (offline JWT verification)
++ * GET /auth/me → the signed-in profile (offline JWT verification); a guest gets 200 { user: null }
* POST /auth/refresh → rotates tokens with the refresh token
*
* Cookies (host-only): ov_token (access JWT, JS-readable for the navbar), ov_refresh (httpOnly,
@@@ -175,9 +175,14 @@ function createSessionRoutes({ config, viewers, fetchImpl = globalThis.fetch, lo
});
router.get('/me', async (req, res) => {
++ res.set('Cache-Control', 'private, no-store');
++ // No credential at all (a guest) is signed out, not an error: the shared navbar asks this on every
++ // page view, and a 401 logged a console error on each (browser check, OpenVibe.Host). A credential
++ // that is present but invalid or expired still answers 401.
++ if (!(req.cookies && req.cookies[ACCESS_COOKIE]) && !req.get('authorization')) return res.json({ user: null });
const actor = await viewers.resolve(req, { services: false }).catch(() => null);
if (!actor || actor.kind !== 'user') return res.status(401).json({ error: 'Not authenticated' });
-- res.set('Cache-Control', 'private, no-store').json({ user: actor.user });
++ res.json({ user: actor.user });
});
router.post('/refresh', async (req, res) => {
ddiff --git a/test/web.test.js b/test/web.test.js
iindex 23a876a..3f17267 100644
---- a/test/web.test.js
++++ b/test/web.test.js
@@@ -132,6 +132,18 @@ t('rooms: start one, post and join without JavaScript, private rooms stay privat
assert.ok(sitemap.includes('/r/coffee-talk') && !sitemap.includes('inner-circle'), 'public rooms only');
});
++t('the session probe: a guest is signed out (200 { user: null }), a bad credential is 401', async () => {
++ const guest = await req('GET', '/auth/me');
++ assert.strictEqual(guest.status, 200, 'no cookie or token at all: not an error');
++ assert.deepStrictEqual(JSON.parse(guest.text), { user: null });
++ assert.strictEqual(guest.headers.get('cache-control'), 'private, no-store');
++ assert.strictEqual((await req('GET', '/auth/me', { headers: { cookie: 'ov_token=expired.or.forged' } })).status, 401, 'a present but invalid cookie');
++ assert.strictEqual((await req('GET', '/auth/me', { headers: { authorization: 'Bearer nope' } })).status, 401, 'a present but invalid bearer token');
++ const me = await req('GET', '/auth/me', { headers: cookie(alice) });
++ assert.strictEqual(me.status, 200);
++ assert.strictEqual(JSON.parse(me.text).user.username, 'alice');
++});
++
t('sign-in, the Frame, robots, sitemap and /updates', async () => {
const r = await req('GET', '/auth/login?next=%2Fmessages');
assert.strictEqual(r.status, 302);