507ed37227b1
fix: preserve TOTP during passkey consolidation (v4.8.9)
- time
- 2026-08-08 06:27 UTC · 2 months ago
- machine
- koh2
- parent
- 752c6edfb19f
↓ peach-browser-507ed372.tar.gzatlas at this save
files changed5 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/vault.consolidate-passkeys.test.ts+32 −0src/background/vault.ts+9 −0manifest.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": {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",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",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 //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) {