import { expect, test } from '@playwright/test'; import { authConfig, errorCodes } from '../../support/auth-config'; import { expireConfirmationLink, expireRecoveryLink } from '../../support/db'; import { GoTrueClient, randomEmail, randomPassword, waitMailInterval } from '../../support/gotrue'; import { expectMailCount, extractTokenHash, waitForMail } from '../../support/mailpit'; const gotrue = new GoTrueClient(); async function confirmedUser() { const email = randomEmail(); const password = randomPassword(); const created = await gotrue.createConfirmedUser(email, password); expect(created.status).toBe(200); return { email, password }; } test.describe('KTO-01 Kriterium 8: Passwort-Reset-Link (api)', () => { test('KTO-01 Kriterium 8: Reset anfordern -> Mail mit type=recovery -> einloesen ok -> zweites Einloesen 403 otp_expired', async () => { const { email } = await confirmedUser(); expect((await gotrue.recover(email)).status).toBe(200); const { tokenHash, type } = extractTokenHash(await waitForMail(email)); expect(type).toBe('recovery'); const first = await gotrue.verify(tokenHash, 'recovery'); expect(first.status).toBe(200); expect(first.body.access_token).toBeTruthy(); const second = await gotrue.verify(tokenHash, 'recovery'); expect(second.status).toBe(403); expect(second.body.error_code).toBe(errorCodes.otpExpired); }); test('KTO-01 Kriterium 8: neue Reset-Anforderung ersetzt den alten Link', async () => { const { email } = await confirmedUser(); await gotrue.recover(email); const oldLink = extractTokenHash(await waitForMail(email)); await waitMailInterval(); await gotrue.recover(email); const newLink = extractTokenHash(await waitForMail(email, 2)); expect(newLink.tokenHash).not.toBe(oldLink.tokenHash); const old = await gotrue.verify(oldLink.tokenHash, 'recovery'); expect(old.status).toBe(403); expect(old.body.error_code).toBe(errorCodes.otpExpired); expect((await gotrue.verify(newLink.tokenHash, 'recovery')).status).toBe(200); }); test('KTO-01 Kriterium 8: AUTH-CFG-07 Reset-Link ist nach 1 h + 1 s abgelaufen (recovery_sent_at zurueckgesetzt)', async () => { const { email } = await confirmedUser(); await gotrue.recover(email); const { tokenHash } = extractTokenHash(await waitForMail(email)); await expireRecoveryLink(email); const res = await gotrue.verify(tokenHash, 'recovery'); expect(res.status).toBe(403); expect(res.body.error_code).toBe(errorCodes.otpExpired); }); test('KTO-01 Kriterium 8: AUTH-CFG-07 Grenze: Reset-Link kurz vor Ablauf ist noch gueltig', async () => { const { email } = await confirmedUser(); await gotrue.recover(email); const { tokenHash } = extractTokenHash(await waitForMail(email)); await expireRecoveryLink(email, authConfig.otpExpirySeconds - 60); expect((await gotrue.verify(tokenHash, 'recovery')).status).toBe(200); }); test('KTO-01 Kriterium 8: AUTH-CFG-07 Bestaetigungslink ist nach 1 h + 1 s abgelaufen (confirmation_sent_at zurueckgesetzt)', async () => { const email = randomEmail(); const password = randomPassword(); await gotrue.signUp(email, password); const { tokenHash } = extractTokenHash(await waitForMail(email)); await expireConfirmationLink(email); const res = await gotrue.verify(tokenHash, 'email'); expect(res.status).toBe(403); expect(res.body.error_code).toBe(errorCodes.otpExpired); // Konto bleibt unbestaetigt. expect((await gotrue.signIn(email, password)).body.error_code).toBe(errorCodes.emailNotConfirmed); }); test('KTO-01 Kriterium 8: Bestaetigungslink ist einmalig', async () => { const email = randomEmail(); await gotrue.signUp(email, randomPassword()); const { tokenHash } = extractTokenHash(await waitForMail(email)); expect((await gotrue.verify(tokenHash, 'email')).status).toBe(200); const again = await gotrue.verify(tokenHash, 'email'); expect(again.status).toBe(403); expect(again.body.error_code).toBe(errorCodes.otpExpired); }); test('KTO-01 Kriterium 8: Reset fuer unbekannte Adresse -> 200, keine Mail', async () => { const unknown = randomEmail('unknown'); expect((await gotrue.recover(unknown)).status).toBe(200); await expectMailCount(unknown, 0); }); test('KTO-01 Kriterium 8: nach dem Setzen des neuen Passworts ist der Reset-Link verbraucht', async () => { const { email } = await confirmedUser(); await gotrue.recover(email); const { tokenHash } = extractTokenHash(await waitForMail(email)); const session = await gotrue.verify(tokenHash, 'recovery'); const update = await gotrue.updateUser(session.body.access_token, { password: randomPassword() }); expect(update.status).toBe(200); expect((await gotrue.verify(tokenHash, 'recovery')).body.error_code).toBe(errorCodes.otpExpired); }); });