diff --git a/e2e/mfa-step-up-journey.spec.ts b/e2e/mfa-step-up-journey.spec.ts index bc9ed52db..7a42acb95 100644 --- a/e2e/mfa-step-up-journey.spec.ts +++ b/e2e/mfa-step-up-journey.spec.ts @@ -6,6 +6,12 @@ import type { } from "@playwright/test"; import * as OTPAuth from "otpauth"; +import { + TOTP_ALGORITHM, + TOTP_DIGITS, + TOTP_PERIOD_SECONDS, +} from "@/lib/auth/mfa/totp"; + import { expect, test } from "./setup/test"; import { clearAuthRateLimits, @@ -77,7 +83,6 @@ import { * current step has too little left — instead of hoping the wall clock is kind. */ -const TOTP_PERIOD_SECONDS = 30; const RECOVERY_CODE_COUNT = 10; const REGENERATE_ENDPOINT = "/api/auth/me/mfa/recovery-codes/regenerate"; /** `/api/auth/login`, per IP per 15 minutes (`src/lib/rate-limit.ts`). */ @@ -96,8 +101,8 @@ function totpCodeAt(secretBase32: string, atMs: number): string { return new OTPAuth.TOTP({ issuer: "HealthLog", label: "HealthLog", - algorithm: "SHA1", - digits: 6, + algorithm: TOTP_ALGORITHM, + digits: TOTP_DIGITS, period: TOTP_PERIOD_SECONDS, secret: OTPAuth.Secret.fromBase32(secretBase32), }).generate({ timestamp: atMs }); @@ -141,8 +146,8 @@ async function submitPassword( await clearAuthRateLimits(); await page.goto("/auth/login"); await page.getByTestId("login-use-password").click(); - await page.locator("#email").fill(account.username); - await page.locator("#password").fill(account.password); + await page.getByTestId("login-email").fill(account.username); + await page.getByTestId("login-password").fill(account.password); const answer = page.waitForResponse( (res) => res.url().includes("/api/auth/login") && @@ -394,7 +399,7 @@ test.describe("second factor", () => { }); await test.step("spends a recovery code once", async () => { - await page.getByTestId("mfa-toggle-recovery").click(); + await page.getByTestId("mfa-recovery-toggle").click(); const answer = await submitFactor(page, liveRecoveryCodes[0]); expect(answer.status()).toBe(200); await page.waitForURL("/"); @@ -406,7 +411,7 @@ test.describe("second factor", () => { await submitPassword(page, account); await expect(page.getByTestId("mfa-login-step")).toBeVisible(); - await page.getByTestId("mfa-toggle-recovery").click(); + await page.getByTestId("mfa-recovery-toggle").click(); const answer = await submitFactor(page, liveRecoveryCodes[0]); expect(answer.status()).toBe(401); await expect(page.getByTestId("mfa-error")).toBeVisible(); diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 778fe26b2..2ece6eabc 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -438,6 +438,7 @@ export default function LoginPage() { setEmail(e.target.value)} @@ -459,6 +460,7 @@ export default function LoginPage() { setPassword(e.target.value)} diff --git a/src/components/auth/mfa-login-step.tsx b/src/components/auth/mfa-login-step.tsx index 1b0900fe6..2e6f893e3 100644 --- a/src/components/auth/mfa-login-step.tsx +++ b/src/components/auth/mfa-login-step.tsx @@ -198,7 +198,7 @@ export function MfaLoginStep({ {hasTotp && (