feat: website favicons + vault health dashboard
Favicons
- PM.Favicon (new): THTTPClient/WinHTTP proxy to icons.duckduckgo.com.
Native Windows TLS — no OpenSSL DLLs to ship (Indy would fail
silently without them). 5 s timeout, max 3 redirects, 64 KB cap,
magic-byte MIME sniffing.
- DB: vault_entries.icon_b64 TEXT (idempotent migration).
- Endpoints: POST /entries/{id}/icon stores a cached data URI without
forcing a full PUT (which would re-encrypt the password). DELETE
/entries/icons/all purges the cache.
- Bridge cmd://favicon/fetch?host=X&reqId=Y runs in an anonymous thread
so the up-to-5 s HTTP GET doesn't block the main thread; result
shipped back via Bridge.onFaviconResult(reqId, host, dataUri).
- Hostname validated on both sides (JS faviconHost + Delphi
NormalizeHost) so brand labels like "Gitea" never leak upstream.
- Settings: opt-in "Fetch website icons" toggle (synced), three explicit
actions (Fetch missing / Re-fetch all / Clear cache) that bypass the
toggle — manual user actions always work.
- Entry card avatar shows <img> when cached, falls back to initials.
onerror handler recovers silently from a corrupt data URI.
Vault health
- New sidebar Tools → "Vault health" view. Four category cards:
Weak (strength < 50), Reused (same plaintext on ≥ 2 entries), Old
(updated_at > 365d), Pwned (HIBP cache).
- Score 0-100 with colour band (Good/Fair/At risk/Critical).
- One-shot computation cached per session (healthCache), invalidated
on lockVault, entry save, and the explicit "Recompute" button.
- "Fix" button on each item opens the slideover for the affected
entry, unmasks the password, focuses it, and pulses the dice button
— full context preserved, user decides how to fix.
- Click handler stopPropagation prevents the document-level
"click outside slideover" listener from closing the panel that
we just opened in the same click event.
Fixes
- openSlideover typo (lowercase O) → openSlideOver across all call
sites. Was silently breaking the Authenticator card click and the
Vault health Fix button.
- W1050 WideChar warning in PM.Favicon — replaced set-membership
with explicit Ord-style range comparisons.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@ plus courant après modif frontend.
|
||||
| Prefs key/value DPAPI (`prefs.bin`) | `delphi-backend/Source/PM.UserPrefs.pas` |
|
||||
| Single-instance mutex + broadcast | `delphi-backend/Source/PM.SingleInstance.pas` |
|
||||
| Start with Windows (HKCU Run) | `delphi-backend/Source/PM.AutoStart.pas` |
|
||||
| Favicon proxy (DuckDuckGo, async THTTPClient/WinHTTP) | `delphi-backend/Source/PM.Favicon.pas` |
|
||||
| Handlers REST | `delphi-backend/Handlers/PM.Handler.*.pas` |
|
||||
| Frontend complet | `js/app.js` |
|
||||
| HTML racine | `index.html` |
|
||||
@@ -53,6 +54,7 @@ Commandes connues :
|
||||
- `quickunlock/{store,get,clear,status}`
|
||||
- `prefs/{get,set}?key=...` (device-bound DPAPI key/value, voir plus bas)
|
||||
- `autostart/{get,set}?enabled=1|0` (HKCU Run registry, "Start with Windows")
|
||||
- `favicon/fetch?host=X&reqId=Y` (async via anonymous thread, callback `Bridge.onFaviconResult(reqId, host, dataUri)`)
|
||||
- `autofill/{configure,hotkeys,execute,cancel}`
|
||||
- `app/focus` (ramène la fenêtre au premier plan, pour le picker)
|
||||
- `app/ready` (page chargée → SetFocus WebBrowser + DOM focus auth input)
|
||||
@@ -158,6 +160,34 @@ la fenêtre + clipboard clear + balloon first-time.
|
||||
Menu : Open / Lock vault / Quit (via `TrackPopupMenu`, themé par
|
||||
`SetPreferredAppMode` ci-dessus).
|
||||
|
||||
## Favicons (`PM.Favicon`)
|
||||
|
||||
Opt-in (`state.faviconsEnabled`, default OFF, synced via `settings_json`).
|
||||
La SEULE feature qui sort sur le réseau côté Delphi (HIBP est côté JS).
|
||||
|
||||
- Source : `https://icons.duckduckgo.com/ip3/<host>.ico` — proxy DDG, pas
|
||||
de tracking, retourne PNG 16-32 px. Centralisé → seul DDG voit la
|
||||
liste des domaines stockés, vs hit chaque /favicon.ico (qui leakerait
|
||||
TOUT le vault à chaque site)
|
||||
- `THTTPClient` (`System.Net.HttpClient`) qui wrappe **WinHTTP** sur
|
||||
Windows → TLS natif via le store de certificats Windows. Pas de
|
||||
DLLs OpenSSL à shipper (Indy aurait silencieusement fail sans
|
||||
`libcrypto-3.dll`/`libssl-3.dll`). 5 s timeout, max 3 redirects,
|
||||
cap 64 KB. Toute erreur → return `''`
|
||||
- Magic-byte sniffing pour le MIME (PNG/JPEG/GIF/SVG/ICO)
|
||||
- Cmd async : `TThread.CreateAnonymousThread` car un GET HTTP bloquerait
|
||||
le main thread jusqu'à 5 s. Callback via `TThread.Queue` →
|
||||
`Bridge.onFaviconResult(reqId, host, dataUri)`
|
||||
- Stockage : colonne `vault_entries.icon_b64 TEXT` (nullable). Endpoint
|
||||
dédié `POST /entries/{id}/icon` pour ne pas forcer un PUT complet
|
||||
(qui re-PUT le password chiffré)
|
||||
- Bulk : `DELETE /entries/icons/all` purge tout (bouton "Clear cache")
|
||||
- Render : `entry-avatar` contient `<img class="entry-avatar-img">` si
|
||||
`icon_b64`, sinon fallback aux initials. `onerror` repasse aux
|
||||
initials si la data URI est corrompue
|
||||
- Auto-fetch après save d'entry, backfill via "Fetch missing" /
|
||||
"Re-fetch all" boutons dans Settings
|
||||
|
||||
## Start with Windows (`PM.AutoStart`)
|
||||
|
||||
- Toggle dans Settings → "Start with Windows" (visible seulement quand
|
||||
@@ -223,6 +253,29 @@ Sidebar Tools expose 2 items MFA :
|
||||
Code TOTP retourne `{ code, period, secondsLeft }` — attention au nom,
|
||||
**pas `remaining`** (utiliser `t.secondsLeft`).
|
||||
|
||||
## Vault health dashboard
|
||||
|
||||
Sidebar Tools → "Vault health" (`state.view = 'health'`). Vue dédiée avec
|
||||
un score 0-100 et 4 cards de catégorie :
|
||||
|
||||
- **Weak** : `computeStrength(plain) < 50`
|
||||
- **Reused** : groupes d'entries partageant le même plaintext (≥ 2)
|
||||
- **Old** : `updated_at > 365 days`
|
||||
- **Pwned** : depuis `state.hibpResults` (n'apparaît que si HIBP actif)
|
||||
|
||||
`computeHealthCache()` décrypte chaque entry une fois et stocke dans
|
||||
`healthCache` (module-level let, pas dans state pour ne pas polluer).
|
||||
Invalidé sur lock, save d'entry, et bouton "Recompute".
|
||||
|
||||
Score : start 100 → -5/weak (cap -40), -10/reused (cap -30), -2/old (cap
|
||||
-20), -15/pwned (cap -50). Bandes : ≥80 Good (vert), ≥50 Fair (cyan),
|
||||
≥25 At risk (orange), <25 Critical (rouge).
|
||||
|
||||
Bouton "Fix" par item → `openEntryForFix(id)` = `openSlideover` puis
|
||||
click sur le bouton edit-password (fallback : juste ouvre le slideover).
|
||||
|
||||
Liste tronquée à 20 items par catégorie + count "+N more".
|
||||
|
||||
## Sidebar sections collapsibles
|
||||
|
||||
Sections `Folders`, `Tags`, `Tools` ont chacune un `.section-toggle`
|
||||
|
||||
Reference in New Issue
Block a user