/** * Regression guard — privilege escalation via mixed auth storage. * * The admin JWT lives in sessionStorage; guest JWTs in localStorage; reads are * sessionStorage-first. So a guest login MUST displace any resident admin token — otherwise * a leftover admin session on a shared "event laptop" would shadow the guest's own token and * silently hand the next person who joins full admin rights. This exercises the real UI flow. */ import { test, expect } from '../../fixtures/test'; import { JoinPage } from '../../page-objects'; function roleOf(page: import('@playwright/test').Page) { return page.evaluate(() => { const t = sessionStorage.getItem('eventsnap_jwt') ?? localStorage.getItem('eventsnap_jwt'); if (!t) return null; try { return JSON.parse(atob(t.split('.')[1])).role; } catch { return null; } }); } test.describe('AuthZ — guest join displaces a resident admin session', () => { test('joining as a guest with an admin token resident in sessionStorage yields GUEST rights', async ({ page, api, }) => { // Simulate an admin who logged in on a shared device and walked away without closing the // tab — their token sits in sessionStorage. const adminJwt = await api.adminLogin(); await page.goto('/'); await page.evaluate((jwt) => sessionStorage.setItem('eventsnap_jwt', jwt), adminJwt); // A different person now joins as a guest in that same tab. const join = new JoinPage(page); await join.goto(); await join.joinAs('DisplacementGuest'); await join.continueToFeed(); // The effective identity must be the GUEST — not the leftover admin. expect(await roleOf(page)).toBe('guest'); // The admin token must have been cleared from sessionStorage by the guest login. expect(await page.evaluate(() => sessionStorage.getItem('eventsnap_jwt'))).toBeNull(); // And the guest must NOT be able to reach the admin dashboard. await page.goto('/admin'); await page.waitForURL(/admin\/login|join|feed/, { timeout: 5_000 }); expect(page.url()).not.toMatch(/\/admin(\/)?$/); }); });