/** * "Sign out everywhere" — DELETE /api/v1/sessions. A security control (revoke every device after a * lost/stolen phone) that had ZERO coverage. Sessions are validated per-request against the DB * (auth middleware resolves the token hash to a live session row), so revocation is observable: a * revoked token must stop authenticating. */ import { test, expect } from '../../fixtures/test'; import { BASE } from '../../helpers/env'; const ctx = (jwt: string) => fetch(`${BASE}/api/v1/me/context`, { headers: { Authorization: `Bearer ${jwt}` } }); test.describe('Auth — sign out everywhere', () => { test("DELETE /sessions revokes ALL of the caller's sessions, not just the current one", async ({ api, guest, db, }) => { // Two devices for one guest: the join session, plus a second session from a recover login. const g = await guest('MultiDevice'); const second = await api.recover(g.displayName, g.pin); const secondJwt = second.body.jwt; // Both tokens work, and there really are two distinct sessions. expect((await ctx(g.jwt)).status).toBe(200); expect((await ctx(secondJwt)).status).toBe(200); expect(await db.countSessionsForUser(g.userId)).toBeGreaterThanOrEqual(2); // Sign out everywhere, authenticated as device one. const res = await fetch(`${BASE}/api/v1/sessions`, { method: 'DELETE', headers: { Authorization: `Bearer ${g.jwt}` }, }); expect(res.status).toBe(204); // BOTH devices are now logged out — the whole point of the control. If it only killed the // caller's own session, device two would still be live and a stolen phone would keep access. expect((await ctx(g.jwt)).status, 'the calling device is signed out').toBe(401); expect((await ctx(secondJwt)).status, 'the OTHER device is signed out too').toBe(401); expect(await db.countSessionsForUser(g.userId)).toBe(0); }); test("one user signing out everywhere does not touch another user's sessions", async ({ guest, }) => { const a = await guest('SignsOut'); const b = await guest('StaysIn'); await fetch(`${BASE}/api/v1/sessions`, { method: 'DELETE', headers: { Authorization: `Bearer ${a.jwt}` }, }); expect((await ctx(a.jwt)).status).toBe(401); expect((await ctx(b.jwt)).status, "another user's session must be untouched").toBe(200); }); });