fix(reauth): wrong master pw re-prompts inline instead of closing

Setting a PIN, enabling Quick Unlock, and generating a recovery key all
closed the reauth modal and showed a toast on the first wrong master
password — no retry. Export already looped inline; the others didn't.

Extracted that loop into reauthLoop(message): re-prompts with an inline
error + attempt count (5 tries), honours the 429 lockout, returns the
verified password or null. All four sensitive-action flows now share it, so
a mistyped master password keeps the modal up to try again.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-07-24 23:52:05 +01:00
parent b542106c76
commit 9b7d04ee46
3 changed files with 49 additions and 58 deletions
+4 -33
View File
@@ -791,39 +791,10 @@ async function doExport() {
// Step 1: reauth — verifies the human in front of the screen is the
// vault owner before we hand them every plaintext password. Defense
// against a stranger reaching the open laptop and exfiltrating data.
// Up to 5 retries with inline error in the modal — beyond that the
// server-side rate limiter takes over (429 with retry_after).
let lastError = null;
let attempts = 0;
const MAX_ATTEMPTS = 5;
let reauthed = false;
while (!reauthed) {
const masterPwd = await askReauth(
'Enter your master password to start an encrypted export.',
{ error: lastError });
if (!masterPwd) return; // user cancelled
try {
const verifier = await computeVerifier(
masterPwd, state.salt, state.kdfIterations || 100000, state.hashAlgo, state.argon2Params);
await api('/reauth', {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify({ verifier: verifier }),
});
reauthed = true;
} catch (err) {
if (err.status === 429 && err.body && err.body.retry_after) {
return toast('Account locked. Try again in ' +
Math.ceil(err.body.retry_after / 60) + ' min', 'warning');
}
attempts++;
if (attempts >= MAX_ATTEMPTS) {
return toast('Too many wrong attempts — try again later', 'error');
}
lastError = 'Wrong master password. Attempt ' +
attempts + ' / ' + MAX_ATTEMPTS + '.';
}
}
// reauthLoop re-prompts inline (up to 5 tries) then defers to the
// server rate limiter; null = cancelled or capped (already toasted).
if (!(await reauthLoop('Enter your master password to start an encrypted export.')))
return;
// Step 2: ask for an INDEPENDENT export password. Decoupled from the
// master pw so a master-pw change later doesn't invalidate the backup,