feat: PIN unlock + table column picker + edit-position chooser + UX
- PIN unlock: device-local 4-12 digit shortcut, DPAPI-wrapped vault
key. Three modes (state.unlockMode): pw / pin / pw+pin. PIN
derives a wrap key via PBKDF2(pin, salt, 100k) and unwraps the
stored vault key (mirrors the Quick Unlock blob shape).
Anti-brute-force: 5 wrong attempts wipes the blob. Setup gated by
master-pw reauth so an unattended unlocked laptop can't be
backdoored. Master pw rotation clears the PIN blob (key drift).
loadServerSettings post-sync demotes pin/both -> pw when the local
blob is missing, so a wiped device re-syncs the correct mode up.
New unit PM.PinUnlock.pas + cmd://pin/{store,get,clear,status}.
- Table column picker: ⚙ in topbar (table view only), checkbox menu
for Site/Username/Folder/Updated. Site also drives showSiteOnCards
so the existing "Show site / URL" toggle in Settings stays in
sync. NAME column auto-widths (180px min, content max, +32px
right padding) so column hugs the next one without truncating.
- Editor position chooser (Appearance setting): Slide-over right /
left / Centered modal. Scoped to #slideover + #settingsPanel so
the click-outside / pointer-events logic doesn't accidentally
trap the modal-style empty viewport.
- Confirm before discarding unsaved edits: state.confirmOnUnsaved
setting (default ON), prompts on X / Esc / click-outside / switch-
to-other-entry. Also gates Lock vault / Sign out actions when the
editor is dirty; auto-lock and system-lock paths bypass to avoid
blocking on an unattended machine.
- Open-in-browser button added to the actions cell of the table
view (was card-only).
- Entry templates pass folder customization + template id through
duplicate / export / import / auto-backup roundtrips.
- Folder color + icon now persisted across export/import: payload.
folders carries name/color/icon; import creates missing folders
additively (existing local customisation kept).
- Bulk move-to-folder, batch add-tag, single add-tag now re-ship
the full entry payload so partial PUTs don't silently wipe
TOTP / custom_fields / kind / template.
- FireDAC: switched ftString -> ftMemo for icon_b64 / custom_fields
/ TOTP / template params and replaced .AsString with .Value so a
large (~200 KB) DeepSeek favicon no longer gets truncated at the
default ANSI 4000-char cap.
- Unicode filenames: attachment INSERT now uses ftWideString +
.AsWideString so non-ANSI filenames round-trip instead of being
mangled to "?".
- HandleSetEntryIcon cap raised 256 KB -> 512 KB chars to accept
base64 data URIs produced by max-raw favicon fetches.
- promptDialog + askReauth support inline `error` line + retry-
with-count loops on doExport reauth and auto-backup password
setup (5 attempts cap before bailing).
- Recently used moved from Tools to Vault section in the sidebar.
- Auth screen passkey button hidden (Delphi backend stubs WebAuthn).
- Sensitive cmd://favicon/refresh-style buttons in Settings now
stopPropagation so the document-level "close panel" handler
doesn't dismiss Settings mid-async during DOM reparenting.
- TEST_PLAN.md: +PIN unlock section.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -454,6 +454,44 @@ hotkeys autofill, etc. **Device-only** (localStorage seulement) :
|
||||
`quickUnlockEnabled` (DPAPI lié au compte Windows), `autofillEnabled`
|
||||
(toggle hotkey Win32), `rememberedUsername` (auth screen autofill local).
|
||||
|
||||
## PIN unlock
|
||||
|
||||
Optional shortcut unlock with a 4–12 digit PIN, complementary to Quick
|
||||
Unlock. Three modes (`state.unlockMode`, synced via `settings_json`) :
|
||||
|
||||
- `pw` — master password only (legacy, default)
|
||||
- `pin` — PIN unlocks the vault on this device
|
||||
- `both` — master password first, then PIN verified before access
|
||||
|
||||
Storage : `PM.PinUnlock.pas` writes a DPAPI blob to
|
||||
`%LOCALAPPDATA%\PMServer\pin-unlock.bin`. Bridge cmds: `pin/store`,
|
||||
`pin/get`, `pin/clear`, `pin/status` (mirror Quick Unlock exactly,
|
||||
separate file so both features coexist).
|
||||
|
||||
Crypto wrap: `wrapKey = PBKDF2(pin, salt, 100k iter)` — 100k instead of
|
||||
600k because PIN entropy is low (~13–40 bits), more iterations mostly
|
||||
slow down honest users. Vault key bytes are AES-GCM(wrapKey, key_raw)
|
||||
inside the blob. Successful PIN → unwrap → import as `state.cryptoKey`
|
||||
→ fresh `/login` with `verifier = hex(rawKey)` (same pattern as Quick
|
||||
Unlock cold-start).
|
||||
|
||||
Anti-brute-force : each failed PIN attempt increments `attempts` in the
|
||||
blob and rewrites it via `pin/store`. Past 5 fails → `pin/clear` → user
|
||||
falls back to master pw. Successful unlock resets the counter to 0.
|
||||
|
||||
`both` mode : `doLogin` saves the typed PIN on `window._pinAfterMaster`,
|
||||
runs the regular master-pw flow, then `enterApp` calls
|
||||
`verifyPinAfterMasterUnlock(pin)` BEFORE flipping to the app shell. PIN
|
||||
mismatch → `lockVault()` + "Wrong PIN" hint. Quick Unlock cold-start
|
||||
bypasses the PIN check (the device is already trusted).
|
||||
|
||||
Sensitive actions (`askReauth` paths: export, change master pw,
|
||||
recovery code, etc.) ALWAYS require master pw — PIN never substitutes.
|
||||
|
||||
Master pw rotation clears the PIN blob (same reason as Quick Unlock :
|
||||
stored wrapped key + server verifier drift). User re-sets PIN from
|
||||
Settings after rotation.
|
||||
|
||||
## Quick Unlock
|
||||
|
||||
DPAPI blob à `%LOCALAPPDATA%\PMServer\quickunlock.bin` (tied to Windows
|
||||
|
||||
Reference in New Issue
Block a user