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
+6 -25
View File
@@ -277,20 +277,9 @@ async function pinSetupFlow() {
// Setting/changing a PIN creates a new unlock path → treat as a
// sensitive operation. An unattended unlocked vault must not be
// possible for a passerby to pin-backdoor.
const masterPwd = await askReauth(
const masterPwd = await reauthLoop(
'Confirm your master password to set or change the PIN.');
if (!masterPwd) return;
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 }),
});
} catch (err) {
return toast('Wrong master password', 'error');
}
let lastError = '';
let attempts = 0;
@@ -419,20 +408,9 @@ async function enableQuickUnlock() {
if (!Bridge.active) return toast('Quick unlock requires the Delphi app', 'warning');
if (!state.cryptoKey) return toast('Unlock the vault first', 'warning');
const masterPwd = await askReauth(
const masterPwd = await reauthLoop(
'Confirm your master password to enable Quick unlock on this device.');
if (!masterPwd) return;
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 }),
});
} catch (err) {
return toast('Wrong master password', 'error');
}
// Export the raw key + identity. We don't store the session token —
// tryQuickUnlock re-logs in with a verifier derived from the key,
@@ -674,7 +652,10 @@ async function unwrapAesKeyFromRecovery(wrappedKeyB64, wrappedIvB64, recoveryCod
async function doGenerateRecoveryKey() {
if (!state.cryptoKey) { return toast('Vault locked', 'warning'); }
const masterPwd = await askReauth(
// Pre-verify via reauthLoop (inline retry) so a wrong master pw re-prompts
// instead of failing the setup POST and closing the modal. The setup call
// below still carries the verifier — the server re-checks it.
const masterPwd = await reauthLoop(
'Confirm your master password to generate a recovery key.');
if (!masterPwd) return;