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:
+6
-25
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user