import { expect, test } from '@playwright/test'; import { GoTrueClient, randomEmail, randomPassword } from '../../support/gotrue'; import { extractTokenHash, waitForMail } from '../../support/mailpit'; import { enableSemantics, expectSignedIn, expectSignedInViaLoginRedirect, login, readSession, shiftBrowserClock } from '../../support/web'; const gotrue = new GoTrueClient(); test.describe('KTO-01 Kriterium 9 (web): Passwort-Reset beendet andere Sitzungen', () => { test('KTO-01 Kriterium 9: zweiter Browser-Kontext ist nach dem Reset und dem naechsten (erzwungenen) Refresh abgemeldet', async ({ page, browser, }) => { const email = randomEmail(); const oldPassword = randomPassword(); await gotrue.createConfirmedUser(email, oldPassword); // Sitzung B: ein zweites Geraet in eigenem Kontext. await login(page, email, oldPassword); await expectSignedIn(page); // Reset ueber Sitzung A (API): neues Passwort setzen beendet alle anderen Sitzungen. await gotrue.recover(email); const { tokenHash } = extractTokenHash(await waitForMail(email)); const recovery = await gotrue.verify(tokenHash, 'recovery'); const update = await gotrue.updateUser(recovery.body.access_token, { password: randomPassword() }); expect(update.status).toBe(200); // Der Access-Token laeuft erst nach jwt_expiry (AUTH-CFG-14, bis 60 min, F1) ab. Der Test // erzwingt den Refresh, indem er die Browser-Uhr um 2 h vorstellt (Session.expiresAt kommt in // supabase_flutter aus dem JWT-Claim `exp`, ein geaendertes `expires_at` im Storage wirkt nicht). await shiftBrowserClock(page, 2 * 60 * 60 * 1000); await page.reload(); await enableSemantics(page); // Der Refresh scheitert (refresh_token_not_found): supabase_flutter loescht die Sitzung aus dem Storage. await expect.poll(async () => await readSession(page), { timeout: 15_000 }).toBeNull(); // Abgemeldet: /login leitet nicht mehr auf die Startseite (Gegenprobe zu Krit. 5). await page.goto('/login'); await enableSemantics(page); await page.waitForTimeout(1_000); await expect(page).toHaveURL(/\/login$/); await expect(page.getByRole('button', { name: 'Sign in', exact: true })).toBeVisible(); }); test('KTO-01 Kriterium 9: Gegenprobe ohne Reset und mit abgelaufenem Access-Token bleibt die Sitzung bestehen (Refresh klappt)', async ({ page, }) => { const email = randomEmail(); const password = randomPassword(); await gotrue.createConfirmedUser(email, password); await login(page, email, password); await expectSignedIn(page); await shiftBrowserClock(page, 2 * 60 * 60 * 1000); const before = (await readSession(page))?.access_token; expect(before, 'Sitzung vor dem Reload').toBeTruthy(); await page.reload(); await expectSignedInViaLoginRedirect(page); // Der Refresh lief wirklich: neues Access-Token im Storage. await expect .poll(async () => (await readSession(page))?.access_token ?? before, { timeout: 15_000 }) .not.toBe(before); }); });