fix(import): la restauration dupliquait l'historique, comme la sync

Le meme defaut, par une autre porte : restaurer une sauvegarde par-dessus
des entrees existantes passe aussi par encryptImportEntry puis PUT, donc
re-chiffre un mot de passe inchange et fait croire au serveur qu'il a
change. La sync etait la cause de ce qui se voyait, pas la seule.

La regle est extraite en une fonction, keepCiphertextIfSamePassword,
appelee par les deux. Les re-publications partielles - tag, dossier,
migration des metadonnees - sont hors de cause : elles reutilisent deja
le chiffre stocke.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-08-28 23:47:36 +01:00
parent 8905599214
commit fba05f1ce5
2 changed files with 35 additions and 25 deletions
+5 -24
View File
@@ -539,30 +539,11 @@ async function applyRemoteSnapshot(remote) {
const remoteWins = (r.updated_at || '') > (local.updated_at || '');
if (!remoteWins) continue;
try {
const enc = await encryptImportEntry(r);
// LE MOT DE PASSE INCHANGÉ GARDE SON CHIFFRÉ, BIT POUR BIT.
//
// L'instantané transporte le clair, et encryptImportEntry le
// re-chiffre avec un IV neuf : le chiffré diffère donc TOUJOURS,
// même quand le mot de passe n'a pas bougé. Or le serveur décide
// d'écrire une version dans l'historique en comparant les
// chiffrés (`encrypted_password <> :newenc`) — il voyait une
// différence là où il n'y en avait aucune, et chaque
// synchronisation qui redescendait une entrée y déposait un
// doublon daté du jour.
//
// Le serveur ne peut pas trancher : il ne voit que du chiffré.
// Seul le client connaît les deux clairs. On compare ici, et on
// rend son chiffré d'origine quand ils sont égaux — ce que le
// commentaire du handler appelle déjà « JS reuses
// originalEncrypted bit-for-bit ».
if (local.encrypted_password && local.iv) {
const before = await decryptPwd(local.encrypted_password, local.iv);
if (before !== '[ERROR]' && before === (r.password || '')) {
enc.encrypted_password = local.encrypted_password;
enc.iv = local.iv;
}
}
// Le chiffré d'origine si le mot de passe n'a pas bougé :
// sans cela le serveur archive une version d'historique à
// chaque synchronisation. Voir keepCiphertextIfSamePassword.
const enc = await keepCiphertextIfSamePassword(
await encryptImportEntry(r), local, r.password);
// PUT keeps the existing id but accepts the encrypted blobs.
// template + uuid not touched here (server preserves columns
// when fields aren't in body — uuid is immutable anyway).