feat(entries): encrypt template at rest, guided tour, import fixes, cleanup
Batched session work sharing app.js / index.html / Entries.pas, so it can't
split cleanly without interactive hunk staging.
- feat: encrypt `template` metadata at rest (template_enc/iv, added to
ENCRYPTED_META_FIELDS). withEncryptedMeta skips an absent template key so
partial re-ships (add-tag, move-to-folder) don't wipe it via LHasTemplate.
Cleartext column kept as migration fallback. +3 unit tests.
- feat: first-run guided tour ("How it works") — spotlight + bubble, no GIFs,
re-launchable from Settings, seen-flag in DPAPI prefs.
- fix(import): preserve original created_at on restore (was stamped to import
time); restore entry icons on overwrite (PUT ignores icon_b64).
- fix(settings): correct clipboard-privacy copy (already excluded from Win+V);
PIN text 4-6 -> 4-12; reorder Set-PIN above unlock-method; move tray/startup
toggles to General; dedicated backup-password button + warning status; tab icons.
- chore: remove dead legacy monolith (app-legacy.js, index-legacy.html,
style-legacy.css) + unused passkeyBtn stub.
- docs: full-source review (CODE_AUDIT 6b), template + favorite/pinned notes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -444,20 +444,27 @@ bulk ne les référencent plus (helpers `AddNullableField`/`BindNullable`) ;
|
||||
`loadEntries`/`loadTrash` déchiffrent → `e.<f>` en mémoire, donc
|
||||
**recherche/tri/render/autofill-match/favicon marchent inchangés** (tout est
|
||||
déjà côté client). Liste des champs : `ENCRYPTED_META_FIELDS =
|
||||
['username','site','title','tags']`. Choke-point d'écriture :
|
||||
['username','site','title','tags','template']`. Choke-point d'écriture :
|
||||
`withEncryptedMeta(obj)` (chiffre chaque champ, blanchit le clair) — enveloppe
|
||||
**chaque** body POST/PUT `/entries` (saveEntry, soSave, duplicateEntry,
|
||||
moveEntryToFolder, addTagToEntry, batchMove/AddTag, encryptImportEntry,
|
||||
migration). Lecture : `decryptEntryMeta(list)`. Anciennes lignes migrées au
|
||||
unlock par `migrateMetadataAtRest` (PUT re-ship, y compris corbeille, bump
|
||||
`updated_at` assumé une fois). Rotation master-pw re-chiffre les 4 champs sous
|
||||
`updated_at` assumé une fois). Rotation master-pw re-chiffre les champs sous
|
||||
la nouvelle clé (JS loop `ENCRYPTED_META_FIELDS` + UPDATE serveur).
|
||||
**Cas `template` (ajouté 2026-07-11, subtil)** : contrairement aux 4 autres,
|
||||
`template` est **omis** par les re-ships partiels (addTag, moveEntryToFolder,
|
||||
batch) — historiquement le serveur le préservait via `LHasTemplate` (clé
|
||||
absente). Pour garder ce contrat, `withEncryptedMeta` **skippe un champ absent
|
||||
de l'objet** (`if (!(f in obj)) continue`) au lieu de synthétiser un `_enc` vide
|
||||
qui ferait wiper le template stocké. La colonne clair `template` est **gardée**
|
||||
(fallback migration : `decryptEntryMeta` préfère `_enc`, retombe sur le clair)
|
||||
— pas encore DROP comme les 4 autres.
|
||||
**Le `?q=` serveur est neutralisé** (site+username chiffrés → LIKE inutile ;
|
||||
le front cherche côté client). **La validation « Site required » serveur est
|
||||
retirée** (site='' quand chiffré) — le client la fait. `folder` reste en clair
|
||||
(requête serveur de réassignation sur delete-folder). Reste en clair :
|
||||
`folder`, `kind`, `template`, métadonnées d'attachments, nombre de lignes,
|
||||
timestamps.
|
||||
`folder`, `kind`, métadonnées d'attachments, nombre de lignes, timestamps.
|
||||
|
||||
Quand tu ajoutes un nouveau champ (chiffré ou non), il faut **toujours**
|
||||
mettre à jour ces 6 endroits sous peine de perdre la donnée silencieusement
|
||||
@@ -931,7 +938,13 @@ le vide ET vide `FPendingURL` → écran noir permanent sur cold-start lent.
|
||||
(`POST /entries/{id}/icon`) et réassignation-sur-delete-folder
|
||||
(`PM.Handler.Folders`). `accessed_at` est **exempté** exprès (timestamp de
|
||||
lecture, non synced). Le bulk « clear all icons » ne bump pas non plus —
|
||||
assumé device-local (purge de cache favicon).
|
||||
assumé device-local (purge de cache favicon). **`favorite` et `pinned`
|
||||
(toggles `POST /entries/{id}/favorite|pin`) sont aussi exemptés exprès** :
|
||||
absents de `buildSyncSnapshot`, donc **device-local par design** (décidé
|
||||
2026-07-11) — comme l'avatar. `favorite` est quand même dans l'auto-backup +
|
||||
export/import (round-trip local), juste pas propagé par le sync. Si un jour on
|
||||
veut les synchroniser : ajouter au snapshot + bumper `updated_at` sur le toggle
|
||||
+ appliquer au merge.
|
||||
- **Timestamps = UTC partout** : tout `created_at`/`updated_at`/`deleted_at`
|
||||
écrit côté Delphi passe par `NowUTC`/`NowUTCStr` (`PM.Database`) — JAMAIS
|
||||
`FormatDateTime(..., Now)` (heure locale). SQLite `CURRENT_TIMESTAMP` /
|
||||
|
||||
Reference in New Issue
Block a user