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>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
// Diagnostic tool: compare what PHP's hash_pbkdf2 produces vs what Delphi stored.
|
||||
// Usage: http://localhost/password-manager/test_pbkdf2.php?u=YOURUSER&p=YOURPASSWORD
|
||||
//
|
||||
// DELETE THIS FILE after diagnosis — it exposes hashes/salts in plaintext.
|
||||
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
$u = $_GET['u'] ?? '';
|
||||
$p = $_GET['p'] ?? '';
|
||||
if (!$u || !$p) { echo "Provide ?u=USER&p=PASSWORD\n"; exit; }
|
||||
|
||||
$db = new SQLite3(__DIR__ . '/vault.db');
|
||||
$db->busyTimeout(5000);
|
||||
|
||||
$st = $db->prepare('SELECT id, username, password_hash, salt, hash_algo FROM users WHERE username=:u');
|
||||
$st->bindValue(':u', $u, SQLITE3_TEXT);
|
||||
$row = $st->execute()->fetchArray(SQLITE3_ASSOC);
|
||||
|
||||
if (!$row) { echo "User '$u' not found.\n"; exit; }
|
||||
|
||||
echo "=== Stored in vault.db ===\n";
|
||||
echo "id : " . $row['id'] . "\n";
|
||||
echo "username : " . $row['username'] . "\n";
|
||||
echo "hash_algo : " . ($row['hash_algo'] ?? '(null)') . "\n";
|
||||
echo "salt : " . $row['salt'] . "\n";
|
||||
echo "salt len : " . strlen($row['salt']) . "\n";
|
||||
echo "stored hash : " . $row['password_hash'] . "\n";
|
||||
echo "stored len : " . strlen($row['password_hash']) . "\n\n";
|
||||
|
||||
$algo = $row['hash_algo'] ?? 'pbkdf2';
|
||||
|
||||
if ($algo === 'pbkdf2') {
|
||||
$computed = hash_pbkdf2('sha256', $p, $row['salt'], 100000);
|
||||
echo "=== PHP hash_pbkdf2('sha256', '$p', salt, 100000) ===\n";
|
||||
echo "computed : $computed\n";
|
||||
echo "computed len : " . strlen($computed) . "\n\n";
|
||||
|
||||
$match = hash_equals($row['password_hash'], $computed);
|
||||
echo "Match : " . ($match ? 'YES ✓ (PHP would let this user in)' : 'NO ✗ (Delphi & PHP disagree on PBKDF2)') . "\n";
|
||||
|
||||
if (!$match) {
|
||||
echo "\nFirst chars side by side:\n";
|
||||
echo "Stored : " . substr($row['password_hash'], 0, 32) . "\n";
|
||||
echo "Computed : " . substr($computed, 0, 32) . "\n";
|
||||
}
|
||||
} else {
|
||||
echo "Account uses BCRYPT, not PBKDF2. Cannot diagnose PBKDF2 mismatch here.\n";
|
||||
}
|
||||
|
||||
$db->close();
|
||||
Reference in New Issue
Block a user