Files
r-zakarya b00da43ab0 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>
2026-06-29 04:41:39 +01:00

153 lines
9.9 KiB
Markdown
Raw Permalink 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.
# Test plan — commit `fa7ea19` (native save + auto-backup + folder customization + attachments + UX bundle)
Coche au fur et à mesure. Les **🔥 regression** sont les bugs déjà vus à
re-vérifier explicitement.
## 0. Setup
- [✅] `delphi-backend\assets\BuildAssets.cmd` + F9 (sinon embedded ≠ disque)
- [✅] Lance avec `config.txt` présent (debug panel + log)
- [✅] Crée 2 users de test ou utilise vault existant non critique
## 1. Native Save As
- [✅] Settings → Export encrypted JSON → maître pw OK → backup pw → boîte "Enregistrer sous" Windows native (pas le popup Téléchargements Edge)
- [✅] Export CSV → idem natif
- [✅] Annule la dialog → toast "save cancelled" attendu, pas d'erreur silencieuse
## 2. Auto-backup
- [✅] Settings → Auto-backup → toggle on → prompt pwd (< 6 chars rejeté)
- [✅] "Choose…" → folder Windows valide → path s'affiche
- [✅] "Backup now" → fichier `vault-autobackup-yyyymmdd-HHmmss.json` créé dans le folder
- [✅] Lance plusieurs "Backup now" (interval = 1 jour) → fichiers s'accumulent → après N+1, le plus vieux est purgé (vérifier `keep` retention)
- [✅] Toggle off → re-on → prompt pwd à nouveau (confirme que le pwd est effacé)
- [ ] Restart app, unlock → 5s plus tard, si `interval` passé, backup auto silencieux (regarde le folder)
- [✅] **Restore** : install fresh → import "Encrypted JSON" → choisis un backup → tape le pwd → entries restaurées intactes
## 3. Folder customization
- [✅] Sidebar → + Folders → modal s'ouvre → name+color+icon → Create → folder visible avec custom icon + color
- [✅] Hover sur folder → pencil apparaît → click → modal pré-rempli → change icon → Save → re-render OK
- [✅] **Folder chip sur card** : crée entry dans "Banking" → la chip sous le pwd montre l'icône shield + couleur (pas le default folder gris) 🔥
- [✅] Drag-reorder : grab folder → drag au-dessus d'un autre → ligne d'insertion cyan visible (above/below selon mouse Y) → drop → ordre changé persisté (refresh app)
## 4. Drag entries vers folders 🔥
- [✅] Drag d'une LOGIN entry → folder → toast "Moved to X" → entry change de folder
- [✅] Drag d'une NOTE entry → folder → toast "Moved to X" (régression v1, doit marcher maintenant)
- [✅] Drag d'une entry avec **TOTP configuré** → folder → ouvre l'entry après → **TOTP toujours là** (pas wipé)
- [✅] Drag d'une entry avec **custom_fields** → folder → custom_fields préservés
- [✅] Drag d'une entry vers son propre folder → no-op (pas d'erreur)
## 5. Recently used
- [✅] Vue vide au start (rien jamais touché) → "No entries"
- [✅] Copy password d'une entry → attends 2s → sidebar "Recently used" → entry visible en tête
- [✅] Ouvre une entry (slideover) → idem
- [✅] Copy 5 entries rapidement → max 10 visibles, ordre = plus récent d'abord
- [✅] Lock vault + unlock → liste rechargée du serveur (accessed_at persiste)
## 6. Encrypted attachments
- [✅] Crée entry, sauve, ré-ouvre → section "Attachments" visible (pas sur new entry)
- [✅] "Attach file" → file picker → upload PDF/PNG < 5 MB → toast OK → liste affiche filename+size+mime
- [✅] Upload fichier > 5 MB → toast erreur "File too large"
- [✅] Download → boîte Save As → fichier décrypté identique à l'original (hash check si paranoid)
- [✅] Delete → confirm modal → row disparaît
- [✅] Crée 2nd attachment, supprime entry parent → vérifier que les rows entry_attachments sont aussi delete (CASCADE FK) via `sqlite vault.db "select count(*) from entry_attachments"`
- [✅] Master pw rotation avec attachments → toast "Re-encrypting N attachment(s)…" puis "Attachments re-encrypted" → download d'un attachment **après rotation** réussit (re-chiffré client-side dans le flux change-master)
## 7. Password expiry
- [✅] Settings → Flag aged → 90 days
- [✅] Backdoor : `sqlite vault.db "UPDATE vault_entries SET password_changed_at = '2024-01-01' WHERE id = X"` → recharge → badge "Aged" visible sur la card de X
- [✅] Edit l'entry, change le password, Save → badge disparaît (password_changed_at bumpé)
- [✅] Edit l'entry, change **uniquement le title**, Save → badge **toujours là** (password_changed_at pas bumpé — le CASE SQL filtre)
- [✅] Filters dropdown → "Aged password" → liste filtrée
## 8. Print recovery code
- [✅] Settings → Recovery → Generate (ou regenerate) → modal avec code → bouton "Print"
- [✅] Click Print → preview Edge → vérifie : code 32px monospace, account name, date, instructions 3 étapes, page A4 propre (le reste de l'app caché)
- [✅] Annule la print preview → app revient normalement (cleanup du #printRecoveryArea)
## 9. Audit log
- [✅] Sidebar → Tools → Audit log → table visible avec actions chronologiques
- [✅] Fais une action (add entry, copy pwd) → refresh audit log → nouvelle ligne en tête
- [✅] Search dans audit log → filtre marche
## 10. Pinned entries
- [✅] Sur n'importe quelle card, click pushpin → toast "Pinned to top" → entry remonte en tête de la liste
- [✅] Re-click → toast "Unpinned" → entry retrouve sa place dans le sort courant
- [✅] Change le sort (name asc → date desc) → entries pinned restent en tête, le tri interne s'applique aux deux groupes
- [✅] Compact mode (Settings → Compact actions) → kebab menu contient "Pin to top" / "Unpin"
- [✅] Pin 3 entries, lock vault, unlock → les 3 sont toujours en tête (persist serveur)
- [ ] Master pw rotation → pinned préservé (pas wipé)
- [✅] Duplicate d'une entry pinned → la copie est **non pinned** (comportement voulu)
## 11. Search history
- [✅] Tape une recherche, attends 1s sans bouger → recherche ajoutée à l'historique (pas besoin d'Enter)
- [✅] Vide la searchbar → focus → dropdown "Recent searches" avec les dernières
- [✅] Click sur un item → re-filtre et bump en tête
- [✅] X individuel sur un item → suppression isolée
- [✅] "Clear" → vide tout l'historique
- [✅] Dropdown disparaît au click hors searchbar / Escape / blur => un petit probleme > focus search bar > dropdown search affiché > clique à nouveau search (focus non changé) dropdown non affiché
## 12. PIN unlock 🔥
### 12a. Setup
- [✅] Settings → "PIN unlock" section visible (Bridge actif requis)
- [✅] Status "No PIN set." + bouton "Set PIN" visible, "Remove PIN" caché
- [✅] Click "Set PIN" → prompt → tape **3 chiffres** → erreur inline "PIN must be 412 digits" (modal reste ouvert)
- [✅] Tape **abcd** → même erreur (digits only)
- [✅] Tape **123456** → toast "PIN set" → status passe à "PIN is set on this device." → bouton "Change PIN" + "Remove PIN" visibles
- [✅] Vérifie `%LOCALAPPDATA%\PMServer\pin-unlock.bin` existe
### 12b. Mode 'pin' (PIN only)
- [✅] Settings → dropdown "Unlock method" = "PIN only" → toast "Unlock method updated"
- [✅] Lock vault → auth screen affiche **PIN** input (pas master pw) + lien "Use master password instead"
- [✅] Tape bon PIN → unlock instant
- [✅] Lock → tape mauvais PIN → "Wrong PIN. Try again..."
- [✅] Lock → tape mauvais PIN 5 fois → blob auto-supprimé → écran retombe sur master pw → message "Too many wrong PIN attempts. Sign in with your master password."
- [✅] Settings après ça : status "No PIN set." → re-set possible
### 12c. Mode 'both' (master + PIN)
- [✅] Re-set PIN, dropdown = "Master password + PIN"
- [✅] Lock → auth affiche pw + PIN ensemble
- [✅] Tape bon pw + mauvais PIN → unlock initie, puis lockVault avec "Wrong PIN. Try again."
- [✅] Tape bon pw + bon PIN → unlock OK
- [✅] Pw seul (PIN vide) → submit bloqué (HTML5 required)
### 12d. Escape PIN-only
- [✅] Mode 'pin', PIN configuré → auth → click "Use master password instead" → champ pw apparaît, PIN caché → unlock master pw marche
- [✅] Lock après → revient en mode PIN (le choix master était one-shot)
### 12e. Quick Unlock + PIN coexistence
- [✅] Active Quick Unlock + définis PIN en mode 'both'
- [✅] Restart app → Quick Unlock cold-start réussit sans demander PIN (device trusted)
- [✅] Lock manuel → auth demande pw + PIN comme prévu
### 12f. Sensitive actions = master pw obligatoire
- [ ] Mode 'pin', unlocked → Settings → Export encrypted JSON → askReauth demande **master pw** (pas PIN)
- [ ] Idem : Change master password → master pw demandé
- [ ] Idem : Generate recovery code → master pw demandé
### 12g. Master pw rotation invalide le PIN
- [ ] Mode 'pin', PIN configuré → Change master pw → après rotation, status PIN passe à "No PIN set." (blob wipé) + unlockMode tombe à 'pw'
- [ ] Next lock → champ master pw → unlock OK avec nouveau pw
### 12h. Remove PIN
- [✅] Click "Remove PIN" → confirm dialog
- [✅] Confirm → blob deleted, status repasse à "No PIN set.", mode revert à 'pw'
### 12i. Recovery key continue à marcher
- [ ] Avec mode 'pin' actif, ouvre auth → click "Forgot master password? Use a recovery code" → flow recovery normal (le bouton reste visible)
## 13. Regressions générales 🔥
- [✅] Quick unlock encore fonctionnel après update
- [✅] Master password rotation OK + entries déchiffrables après
- [✅] Autofill (Ctrl+Shift+L/P) toujours OK
- [ ] Recovery code redeem fonctionne (sur compte test)
- [✅] Lock vault → unlock → toutes entries présentes
- [ ] Trash → restore → entry revient avec tous ses champs (kind, custom_fields, TOTP)
## 13. Console / log scrutiny
- [ ] Ouvre debug panel → execute scénarios ci-dessus → log Delphi sans `FAILED`/`ERROR` non attendu
- [ ] Si tu peux ouvrir console JS (DevTools désactivé en prod, faut hack — sinon skip) → pas d'erreur JS rouge pendant les flows
---
**Ordre conseillé** : 0 → 4 → 6 → 7 → 1 → 2 → 3 → 5 → 8 → 9 → 10 → 11 → 12 → 13. Le drag entries (#4) est le plus à risque (régression confirmée hier), le faire tôt + à fond.