Files
Password-Manager/index-legacy.html
T
Zaki 40b3154a34 feat: MFA tools, single-instance, tray polish, prefs persistence
Session highlights:

- feat(prefs): DPAPI-backed key/value store (PM.UserPrefs) — fixes
  rememberedUsername being lost across reboots due to the random
  ephemeral HTTP port changing the localStorage origin every launch.
  Bridge cmd://prefs/{get,set} round-trips through Delphi.

- feat(tray): icon visible from startup (NIM_ADD at constructor, not
  at first minimize). Tray context menu themed via uxtheme!135
  SetPreferredAppMode so it follows the app's dark/light setting.

- feat(single-instance): named mutex + RegisterWindowMessage broadcast.
  Second launch posts WM_PMSHOW to HWND_BROADCAST and exits; the
  running bridge restores the window from tray. Mutex lives in Local\
  namespace so distinct Windows users can still each run one.

- feat(mfa): Authenticator sidebar view (live TOTP codes for every
  entry with a secret) + standalone TOTP generator modal (paste
  base32 / otpauth:// URI, or generate a random 20-byte secret).

- feat(sidebar): Folders / Tags / Tools sections collapsible with
  chevron toggle. Badge counts stay visible when collapsed. State
  persisted in settings_json (synced across devices).

- feat(autofill): hotkey when vault is locked now restores the app
  and focuses the master password input instead of no-op'ing
  silently. Cleaner UX for the common "I hit Ctrl+Shift+L but the
  vault was locked" path.

- feat(quick-unlock): when enabled, skip lockVault on Windows lock /
  sleep. Rationale: the DPAPI blob already gates access via the
  Windows account, so re-locking on top of the OS lock is redundant.
  Idle auto-lock still fires (separate opt-in).

- fix(quick-unlock): re-sync state.quickUnlockEnabled from DPAPI
  source-of-truth at boot, instead of trusting (now-volatile)
  localStorage.

- docs: CLAUDE.md updated with all new modules, bridge commands,
  and the port-ephemeral pitfall.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 21:31:39 +01:00

218 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🔐 Vault — Legacy UI</title>
<link rel="stylesheet" href="css/style-legacy.css">
</head>
<body>
<div class="toast-container" id="toastContainer"></div>
<div class="idle-warning" id="idleWarning">
<h3>⏰ Auto-lock</h3>
<p>Vault locks in <span id="idleCountdown">30</span>s</p>
<button class="btn btn-sm" onclick="resetIdle()">Stay Unlocked</button>
</div>
<div class="edit-modal" id="editModal">
<div class="edit-box">
<h3>✏️ Edit Entry</h3>
<label>Website / App</label><input type="text" id="editSite" placeholder="example.com">
<label style="margin-top:.6rem">Username / Email</label><input type="text" id="editUsername" placeholder="user@example.com">
<label style="margin-top:.6rem">Password</label>
<div style="position:relative">
<input type="password" id="editPassword" placeholder="Password" style="width:100%;padding-right:40px">
<button type="button" onclick="toggleEditPassword()" style="position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;color:var(--text2);cursor:pointer;font-size:.9rem">👁️</button>
</div>
<label style="margin-top:.6rem">Folder</label>
<select id="editFolder" style="width:100%;margin-bottom:.4rem"></select>
<input type="hidden" id="editId">
<div style="display:flex;gap:.5rem;margin-top:1rem">
<button class="btn" onclick="saveEdit()">💾 Save</button>
<button class="btn btn-outline" onclick="closeEdit()">Cancel</button>
</div>
</div>
</div>
<!-- Add Password Modal -->
<div class="modal-overlay" id="addModal">
<div class="modal-box">
<h3> New Password</h3>
<label>Website / App</label><input type="text" id="addSite" placeholder="example.com">
<label style="margin-top:.6rem">Username / Email (optional)</label><input type="text" id="addUsername" placeholder="user@example.com">
<label style="margin-top:.6rem">Password</label>
<div style="position:relative">
<input type="password" id="addPassword" placeholder="Password" style="width:100%;padding-right:40px" oninput="checkAddStrength()">
<button type="button" onclick="openGen()" style="position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;color:var(--text2);cursor:pointer;font-size:1.1rem;line-height:1" title="Generate">🎲</button>
</div>
<div class="strength-bar s0" id="addStrengthBar" style="margin-top:0.2rem;"></div>
<label style="margin-top:.6rem">Folder</label>
<select id="addFolder" style="width:100%;margin-bottom:.4rem"></select>
<div style="display:flex;gap:.5rem;margin-top:1rem">
<button class="btn" id="addEntryBtn" onclick="addEntry()">💾 Save</button>
<button class="btn btn-outline" onclick="closeAdd()">Cancel</button>
</div>
</div>
</div>
<!-- Floating action buttons -->
<button class="fab" onclick="openAdd()" title="Add password">+</button>
<button class="fab fab-trash" id="trashBtn" onclick="toggleTrash()" title="Trash">🗑️</button>
<div class="vault">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.6rem">
<h1 style="margin-bottom:0">🔐 Vault <span>XAMPP</span></h1>
<button class="btn btn-outline btn-xs" onclick="toggleTheme()" title="Toggle theme">🌓</button>
</div>
<!-- Auth Section -->
<div id="authSection" class="auth-section">
<div class="auth-tabs">
<button class="auth-tab active" onclick="switchTab('login')">Login</button>
<button class="auth-tab" onclick="switchTab('register')">Register</button>
</div>
<div id="loginForm">
<div class="input-group">
<input type="text" id="loginUsername" placeholder="Username" autocomplete="off">
<input type="password" id="loginPassword" placeholder="Master Password" autocomplete="off">
<button class="btn" id="loginBtn" onclick="login()">🔓 Unlock</button>
<button class="btn btn-outline btn-sm" onclick="loginWithPasskey()" title="Use passkey" style="white-space:nowrap">🔐 Passkey</button>
</div>
</div>
<div id="registerForm" class="hidden">
<div class="input-group">
<input type="text" id="regUsername" placeholder="Username (min 3)" autocomplete="off">
<input type="password" id="regPassword" placeholder="Password (min 8)" autocomplete="off" oninput="checkRegStrength()">
<button class="btn" id="registerBtn" onclick="register()">✨ Create</button>
</div>
<div class="strength-bar s0" id="regStrengthBar" style="margin-top:0.4rem;margin-bottom:0.2rem;flex-basis:100%"></div>
</div>
</div>
<!-- Vault Section -->
<div id="vaultSection" class="hidden">
<!-- Top bar -->
<div style="display:flex;justify-content:space-between;margin-bottom:.5rem;align-items:center;flex-wrap:wrap;gap:.4rem">
<span class="status-badge" id="connectionStatus">🟢 Connected</span>
<span id="entryCount" style="color:var(--text2);font-size:.8rem"></span>
<div style="display:flex;gap:.4rem;align-items:center">
<span id="currentUser" style="color:var(--text2);font-size:.8rem"></span>
<div class="settings-dropdown">
<button class="btn btn-outline btn-sm" id="settingsBtn" onclick="toggleSettings()">⚙️</button>
<div class="settings-menu hidden" id="settingsMenu">
<div class="settings-item">
<span>Auto-lock</span>
<select id="autoLockTimer" onchange="setAutoLock()">
<option value="0">No lock</option>
<option value="1">1 min</option>
<option value="5" selected>5 min</option>
<option value="15">15 min</option>
<option value="30">30 min</option>
<option value="60">1 hour</option>
</select>
</div>
<div class="settings-item" onclick="toggleSound();event.stopPropagation();">
<span>Sound</span>
<div class="toggle-switch" id="soundToggleSwitch"></div>
</div>
<div class="settings-item" onclick="toggleTheme();event.stopPropagation();">
<span>Theme</span>
<div class="toggle-switch" id="themeToggleSwitch"></div>
</div>
<div class="settings-item" onclick="toggleViewBtn();event.stopPropagation();">
<span>👁️ View password</span>
<div class="toggle-switch active" id="showViewBtnToggle"></div>
</div>
<div class="settings-item" onclick="toggleShowEmail();event.stopPropagation();">
<span>📧 Show email</span>
<div class="toggle-switch active" id="showEmailToggle"></div>
</div>
<div class="settings-item" onclick="if(window.PublicKeyCredential){registerPasskey()}else{toast('Passkeys not supported','error')};event.stopPropagation();">
<span>🔐 Set up passkey</span>
<span style="color:var(--accent);font-size:0.7rem"></span>
</div>
</div>
</div>
<button class="btn btn-outline btn-sm" onclick="doLogout()">🔒 Lock</button>
</div>
</div>
<!-- Add Entry Form -->
<!-- <form onsubmit="return false" class="input-group" autocomplete="off"> -->
<!-- <input type="text" id="siteInput" placeholder="Website *" autocomplete="off"> -->
<!-- <input type="text" id="usernameInput" placeholder="Username (optional)" autocomplete="off"> -->
<!-- <div style="flex:1;min-width:130px;position:relative"> -->
<!-- <input type="password" id="passwordInput" placeholder="Password *" autocomplete="new-password" style="width:100%;padding-right:40px" oninput="checkStrength()"> -->
<!-- <button type="button" onclick="openGen()" style="position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;color:var(--text2);cursor:pointer;font-size:1.1rem;line-height:1" title="Generate password">🎲</button> -->
<!-- </div> -->
<!-- <div class="strength-bar s0" id="strengthBar" style="flex-basis:100%;margin-top:-0.3rem;margin-bottom:0.3rem"></div> -->
<!-- <select id="addFolderSelect"></select> -->
<!-- <button type="button" class="btn" id="addBtn" onclick="addEntry()"> Add</button> -->
<!-- </form> -->
<!-- Toolbar with search + trash + views -->
<div class="toolbar">
<div class="search-box">
<input type="text" id="searchInput" placeholder="🔍 Search..." oninput="searchEntries()" autocomplete="off">
<button type="button" class="clear-search-btn" id="clearSearchBtn" onclick="clearSearch()" title="Clear search"></button>
</div>
<div style="display:flex;gap:.4rem;align-items:center;flex-wrap:wrap">
<button class="btn btn-outline btn-sm" onclick="showShortcutsHelp()" title="Shortcuts">⌨️</button>
<div class="view-dropdown" id="viewDropdown">
<button class="btn btn-outline btn-sm" id="viewDropdownBtn">🟫 Grid ▾</button>
<div class="view-dropdown-menu hidden" id="viewDropdownMenu">
<button class="view-opt" data-view="grid">🟫 Grid</button>
<button class="view-opt" data-view="compact">📝 Compact</button>
<button class="view-opt" data-view="list">📋 List</button>
<button class="view-opt" data-view="table">📊 Table</button>
<button class="view-opt" data-view="card">🃏 Card</button>
<button class="view-opt" data-view="grouped">📂 Grouped</button>
<button class="view-opt" data-view="detail">🔍 Detail</button>
</div>
</div>
</div>
</div>
<div class="folders-bar" id="foldersBar"></div>
<div id="trashActions" class="hidden" style="margin-bottom:.5rem;text-align:right;">
<button class="empty-trash-btn" onclick="emptyTrash()">🗑️ Empty Trash</button>
</div>
<div id="entriesContainer" class="grid-view">
<div style="text-align:center;color:var(--text2);padding:2rem;grid-column:1/-1">📭 No entries</div>
</div>
<div style="margin-top:.6rem;text-align:right">
<button class="btn btn-outline btn-sm" onclick="showExportModal()">📤 Export</button>
</div>
</div>
</div>
<!-- Generator Modal -->
<div id="genModal" style="position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.7);display:none;justify-content:center;align-items:center;z-index:2000">
<div style="background:var(--bg2);border-radius:1.5rem;padding:1.5rem;min-width:320px;max-width:90%">
<h3 style="margin-bottom:.8rem;color:var(--text)">🎲 Generator</h3>
<div style="background:var(--input);padding:.8rem;border-radius:1rem;font-family:monospace;text-align:center;color:#4ade80;margin:.6rem 0;word-break:break-all" id="genPreview">Click Generate</div>
<div style="display:flex;align-items:center;gap:.6rem;margin:.8rem 0">
<span>Length:</span>
<input type="range" id="pwdLen" min="8" max="64" value="16" oninput="onLenChange()" style="flex:1">
<span id="lenVal" style="background:var(--input);padding:.2rem .6rem;border-radius:1rem;min-width:30px;text-align:center">16</span>
</div>
<div style="display:flex;flex-wrap:wrap;gap:.5rem;margin:.5rem 0">
<label style="font-size:.8rem;color:var(--text2)"><input type="checkbox" id="useUpper" checked onchange="genPwd()"> A-Z</label>
<label style="font-size:.8rem;color:var(--text2)"><input type="checkbox" id="useLower" checked onchange="genPwd()"> a-z</label>
<label style="font-size:.8rem;color:var(--text2)"><input type="checkbox" id="useNum" checked onchange="genPwd()"> 0-9</label>
<label style="font-size:.8rem;color:var(--text2)"><input type="checkbox" id="useSym" checked onchange="genPwd()"> !@#$</label>
</div>
<div style="display:flex;gap:0.3rem;margin-top:0.8rem;flex-wrap:wrap">
<button class="btn btn-sm" onclick="genPreset(16,'upper+lower+num+sym')" style="flex:1;min-width:70px">🔒 Strong 16</button>
<button class="btn btn-sm" onclick="genPreset(20,'upper+lower+num+sym')" style="flex:1;min-width:70px">🔒 Strong 20</button>
<button class="btn btn-sm btn-danger" onclick="genPreset(32,'upper+lower+num+sym')" style="flex:1;min-width:70px">🛡️ Paranoid 32</button>
</div>
<div style="display:flex;gap:.4rem;margin-top:.6rem">
<button class="btn" onclick="genPwd()" style="flex:1">🔄</button>
<button class="btn" onclick="useGen()" style="flex:1">✅ Use</button>
<button class="btn btn-outline" onclick="closeGen()">Cancel</button>
</div>
</div>
</div>
<script src="js/app-legacy.js"></script>
</body>
</html>