aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-15 17:42:10 -0700
committerAdam Soltys <[email protected]>2026-08-15 17:42:10 -0700
commit79d360e0f4221256675b274127e0abacaf54be28 (patch)
tree95511bc1b89d8d11d73e195af5f98848940a062f /src
parentceab8e8aecc29ac84e184886312a27c835cad8a9 (diff)
downloadcoinosv3-79d360e0f4221256675b274127e0abacaf54be28.tar.gz
coinosv3-79d360e0f4221256675b274127e0abacaf54be28.zip
The padlock refuses to strand a seed: a wallet the vault never met gets saved (password asked) before locking, and unlocking returns you to the wallet you left
Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
Diffstat (limited to 'src')
-rw-r--r--src/app.js28
-rw-r--r--src/i18n.js1
2 files changed, 26 insertions, 3 deletions
diff --git a/src/app.js b/src/app.js
index f5337b6..091e295 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1400,6 +1400,8 @@ function startForget(id) {
function cancelPw() {
// Declining the lock-time offer is an answer, not a postponement.
if (ui.pw && ui.pw.purpose === 'lock') markLockPwAsked();
+ // Backing out of save-before-lock means "don't lock" — go home, keys intact.
+ if (ui.pw && ui.pw.purpose === 'locksave') ui.screen = 'wallet';
ui.pw = null;
render();
}
@@ -1444,10 +1446,12 @@ function submitPw() {
mergeVaultList(list); // bring existing persisted accounts into the session
}
const acc = accounts.find((a) => a.id === p.accId);
- if (acc) acc.persisted = p.purpose === 'save';
+ if (acc) acc.persisted = p.purpose === 'save' || p.purpose === 'locksave';
writeVault();
persistAccounts();
+ const finishLock = p.purpose === 'locksave';
ui.pw = null;
+ if (finishLock) { softLock(); return; }
render();
}
@@ -1514,7 +1518,8 @@ function unlockVault() {
else localStorage.removeItem(UNLOCK_UNTIL_KEY);
} catch {}
armUnlockDeadline();
- if (accounts.length) activateAccount(accounts[0], { fresh: true });
+ const cur = accounts.find((a) => a.id === activeId) || accounts[0];
+ if (cur) activateAccount(cur, { fresh: true });
else lock(); // everything got signed out by the timer
}
function skipVault() {
@@ -1755,6 +1760,23 @@ function softLock() {
// Nothing watchable, or a passwordless vault (locking would mean nothing):
// fall back to the classic flow, which offers to set a password.
if (!watch.length || !hasVault() || blankPw) { lock({ offerPassword: true }); ui.justLocked = true; return; }
+ // A wallet that isn't IN the vault can't come back from a lock — unlocking
+ // decrypts the vault, and this seed isn't there (created while the vault
+ // sat locked, so autoSave couldn't write it). Save it first: with the
+ // session password if we hold it, else ask for the vault password now.
+ const active = activeAccount();
+ if (active && active.type === 'full' && !active.provisional && !active.persisted) {
+ if (vaultPassword != null) {
+ active.persisted = true;
+ writeVault();
+ persistAccounts();
+ } else {
+ ui.pw = { purpose: 'locksave', accId: active.id, mode: 'enter', v1: '', v2: '', error: '' };
+ ui.screen = 'accounts'; // the pw prompt renders on the accounts screen
+ render();
+ return;
+ }
+ }
const keepId = activeId;
for (const f of FEATURES) { try { f.stop && f.stop(); } catch {} }
vaultPassword = null;
@@ -2439,7 +2461,7 @@ function pwPromptCard() {
return h('div', { class: 'card col' },
h('h3', {}, atLock ? t('lockPwTitle') : change ? t('changePassword') : p.mode === 'set' ? t('setPassword') : t('enterPassword')),
h('p', { class: 'small muted', style: 'margin:0' },
- atLock ? t('lockPwDesc') : change ? t('changePasswordDesc') : p.mode === 'set' ? t('setPasswordDesc') : t('enterPasswordDesc')),
+ atLock ? t('lockPwDesc') : change ? t('changePasswordDesc') : p.purpose === 'locksave' ? t('lockSaveDesc') : p.mode === 'set' ? t('setPasswordDesc') : t('enterPasswordDesc')),
change ? h('input', { type: 'password', placeholder: t('currentPassword'), value: p.v0, onInput: (e) => (p.v0 = e.target.value) }) : null,
h('input', { type: 'password', placeholder: newish ? t('passwordOptional') : t('password'), value: p.v1, onInput: (e) => (p.v1 = e.target.value) }),
newish ? h('input', { type: 'password', placeholder: t('confirmPassword'), value: p.v2, onInput: (e) => (p.v2 = e.target.value) }) : null,
diff --git a/src/i18n.js b/src/i18n.js
index 9e5d77f..4538894 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -77,6 +77,7 @@ const STR = {
pwChanged: 'Password changed',
enterPassword: 'Enter your password',
enterPasswordDesc: 'Unlock your saved wallets to continue.',
+ lockSaveDesc: "This wallet isn't saved on this device yet. Enter your password to save it before locking — otherwise unlocking couldn't bring it back.",
password: 'Password',
confirmPassword: 'Confirm password',
pwMismatch: 'Passwords do not match.',