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
Changes
@@@ -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 = () => Datr.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 } });@@@ -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));
@@ -9,7 +9,7 @@ const { staff: staffMap } = require('openvibe-contracts');
@@ -9,7 +9,7 @@ const { staff: staffMap } = require('openvibe-contracts');
* (?silent=1 adds prompt=none; ?next= a same-site path)
* (?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/callback → state check, server-side code exchange (client secret + code_verifier)
* GET /auth/logout → clear cookies, revoke the refresh token (best effort)
* 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
* 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
* 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
@@ -243,6 +243,11 @@ function createSso({ config, keys, fetchImpl = globalThis.fetch, now = () => Dat
r.get('/me', (req, res) => {
r.get('/me', (req, res) => {
res.set('Cache-Control', 'private, no-store');
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;
const v = req.viewer;
if (!v || v.kind !== 'user') return res.status(401).json({ error: 'Not signed in' });
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 } });
res.json({ user: { username: v.username, display_name: v.displayName, subject_id: v.subject, role: v.role } });
@@ -38,6 +38,16 @@ const { signJwt } = require('./helpers/mocks');
@@ -38,6 +38,16 @@ const { signJwt } = require('./helpers/mocks');
assert.strictEqual(me.status, 401);
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 () => {
await check('a real Network session token still signs the staff member in', async () => {
const page = await s.get('/staff', { as: staff });
const page = await s.get('/staff', { as: staff });
assert.strictEqual(page.status, 200, page.text.slice(0, 200));
assert.strictEqual(page.status, 200, page.text.slice(0, 200));