openvibers/openvibe.codes Commits

A guest's session probe answers 200, not 401 (browser check, WS-Q task 3): GET /auth/me with no codes_at or codes_rt 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 session cookie that is present but does not resolve still answers 401. Test: security-session.test.js

OP OpenVibers committed 26 Sept 2026 · a2127a9985 · 2 files, +16 −1


Changes

ddiff --git a/server/auth/sso.js b/server/auth/sso.js
iindex 5aa50a6..1af1071 100644
---- a/server/auth/sso.js
++++ b/server/auth/sso.js
@@@ -9,7 +9,7 @@ const { staff: staffMap } = require('openvibe-contracts');
* (?silent=1 adds prompt=none; ?next= a same-site path)
* GET /auth/callback → state check, server-side code exchange (client secret + code_verifier)
* GET /auth/logout → clear cookies, revoke the refresh token (best effort)
-- * GET /auth/me → { user } for the shared navbar (it cannot read the httpOnly cookie)
++ * GET /auth/me → { user } for the shared navbar (it cannot read the httpOnly cookie); { user: null } for a guest
*
* Codes calls the Network's /api/v1/projects API with the person's access token, so it keeps that
* token — in an httpOnly cookie (codes_at) no script can read, unlike the navbar-readable ov_token
@@@ -243,6 +243,11 @@ function createSso({ config, keys, fetchImpl = globalThis.fetch, now = () => Dat
r.get('/me', (req, res) => {
res.set('Cache-Control', 'private, no-store');
++ // No session at all (a guest: no access or refresh cookie) 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 session cookie that is present but invalid or expired still answers 401.
++ const c = req.cookies || {};
++ if (!c[ACCESS_COOKIE] && !c[REFRESH_COOKIE] && !req.get('authorization')) return res.json({ user: null });
const v = req.viewer;
if (!v || v.kind !== 'user') return res.status(401).json({ error: 'Not signed in' });
res.json({ user: { username: v.username, display_name: v.displayName, subject_id: v.subject, role: v.role } });
ddiff --git a/test/security-session.test.js b/test/security-session.test.js
iindex b46c466..f3b6fe3 100644
---- a/test/security-session.test.js
++++ b/test/security-session.test.js
@@@ -38,6 +38,16 @@ const { signJwt } = require('./helpers/mocks');
assert.strictEqual(me.status, 401);
});
++ await check('/auth/me: a guest is signed out (200 { user: null }); a session cookie that fails is 401', async () => {
++ const guest = await s.get('/auth/me');
++ assert.strictEqual(guest.status, 200, 'no session cookie 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 s.get('/auth/me', { cookie: 'codes_at=garbage' })).status, 401, 'a present but invalid access cookie');
++ assert.strictEqual((await s.get('/auth/me', { cookie: 'codes_rt=stale' })).status, 401, 'a refresh cookie alone is a session that did not resolve');
++ assert.strictEqual((await s.get('/auth/me', { as: staff })).status, 200);
++ });
++
await check('a real Network session token still signs the staff member in', async () => {
const page = await s.get('/staff', { as: staff });
assert.strictEqual(page.status, 200, page.text.slice(0, 200));