507ed37227b1

fix: preserve TOTP during passkey consolidation (v4.8.9)

time
2026-08-08 06:27 UTC · 2 months ago
machine
koh2
parent
752c6edfb19f
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/vault.consolidate-passkeys.test.ts+32 −0src/background/vault.ts+9 −0
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "4.8.8",4+  "version": "4.8.9",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -2,7 +2,7 @@22   "manifest_version": 3,33   "minimum_chrome_version": "116",44   "name": "Peach Password Manager",5-  "version": "4.8.8",5+  "version": "4.8.9",66   "description": "Zero-knowledge password manager",77   "action": {88     "default_popup": "src/popup/index.html",

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "4.8.8",3+  "version": "4.8.9",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/background/vault.consolidate-passkeys.test.ts+32 −0
@@ -129,6 +129,38 @@129129     expect(passkeyAfter).toBeUndefined();130130   });131131 132+  it("preserves TOTP when the passkey entry is consolidated into a login", async () => {133+    const login = await vault.addEntry({134+      type: "login",135+      title: "Example Login",136+      username: "[email protected]",137+      password: "pw123",138+      url: "https://example.com/login",139+    });140+    const passkeyEntry = await vault.addEntry({141+      type: "login",142+      title: "Example Passkey",143+      username: "[email protected]",144+      password: "",145+      url: "https://example.com",146+      totp: "JBSWY3DPEHPK3PXP",147+      totpAlgorithm: "SHA256",148+      totpDigits: 8,149+      totpPeriod: 60,150+      passkey: makePasskey({ rpId: "example.com", credentialId: "cred-totp" }),151+    });152+153+    await vault.consolidatePasskeyEntries();154+155+    expect(vault.getEntryById(login.id)).toMatchObject({156+      totp: "JBSWY3DPEHPK3PXP",157+      totpAlgorithm: "SHA256",158+      totpDigits: 8,159+      totpPeriod: 60,160+    });161+    expect(vault.getEntryById(passkeyEntry.id)).toBeUndefined();162+  });163+132164   // T-023b: two passkey entries for the same rpId, no login → winner is kept133165   // (by lastUsedAt), loser is soft-deleted.134166   //

view file

src/background/vault.ts+9 −0
@@ -920,6 +920,15 @@920920 921921     if (loginTarget) {922922       loginTarget.passkey = winner.passkey;923+      // A passkey entry may also carry the site's TOTP seed. Preserve it when924+      // the passkey is consolidated onto an existing login instead of925+      // soft-deleting the only record that held the 2FA configuration.926+      if (!loginTarget.totp && winner.totp) {927+        loginTarget.totp = winner.totp;928+        loginTarget.totpAlgorithm = winner.totpAlgorithm;929+        loginTarget.totpDigits = winner.totpDigits;930+        loginTarget.totpPeriod = winner.totpPeriod;931+      }923932       loginTarget.modifiedAt = now;924933       for (const dup of group) {925934         if (!dup.deletedAt) {

view file