You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Apr 10, 2026. It is now read-only.
asyncverifyTotp(mfaToken: string,code: string): Promise<AuthUser&{keySalt: string}>// POST /api/auth/totp — same response shape as login on success
Update login(): if response status is 202, return { mfaPending: true, mfaToken: string } instead of throwing.
LoginPage.tsx
Add a 'totp' view alongside existing 'login', 'register', 'forgot-password', 'reset-password'.
Critical — password retention across views: The E2EE key is derived from password + keySalt via cryptoService.initKey(). On normal login this happens immediately in api.login(). With 2FA, initKey must be called after api.verifyTotp() succeeds — at which point the server returns keySalt but the user's password is no longer being typed. The password must be retained in component state through the TOTP view. Do not clear the password field or reset password state on transition to 'totp'.
// On login 202 response — retain password, don't clear it:setMfaToken(token);setView('totp');// password state stays untouched// On TOTP success:const{ keySalt, ...user}=awaitapi.verifyTotp(mfaToken,totpCode);awaitcryptoService.initKey(password,keySalt);// password still in stateauthService.setUser(user);
If the user clicks "Back to login" from the TOTP view, clear both mfaToken and password state and return to the login view.
Part of #50.
Current login flow
LoginPage.tsx→api.login()→ 200 → init E2EE key → render app.New flow
api.login()→ 202 → show TOTP input screen →api.verifyTotp()→ 200 → init E2EE key → render app.Changes required
api.tsAdd
verifyTotp(mfaToken: string, code: string):Update
login(): if response status is 202, return{ mfaPending: true, mfaToken: string }instead of throwing.LoginPage.tsxAdd a
'totp'view alongside existing'login','register','forgot-password','reset-password'.Critical — password retention across views: The E2EE key is derived from
password + keySaltviacryptoService.initKey(). On normal login this happens immediately inapi.login(). With 2FA,initKeymust be called afterapi.verifyTotp()succeeds — at which point the server returnskeySaltbut the user's password is no longer being typed. The password must be retained in component state through the TOTP view. Do not clear the password field or reset password state on transition to'totp'.If the user clicks "Back to login" from the TOTP view, clear both
mfaTokenandpasswordstate and return to the login view.State to add:
TOTP view UI:
inputMode="numeric",maxLength={8}(backup codes are longer than 6 digits),autoFocus,autoComplete="one-time-code"UX notes