2c99399f84d6
fix: isUvpaa returns true unconditionally (was gated on unlock — caused 'passkey can't be created on this device'); handleCreateRequest now waits for unlock instead of immediately rejecting (v3.6.106)
- time
- 2026-05-28 17:02 UTC · 5 months ago
- machine
- koh2
- parent
- 9b1e359cf8d6
↓ peach-browser-2c99399f.tar.gzatlas at this save
files changed4 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/passkey.ts+38 −4manifest.firefox.json+1 −1
@@ -2,7 +2,7 @@22 "_comment_audio_capture": "Firefox does not honor the Chrome 'audioCapture' optional_permission — getUserMedia in WebExtensions is gated solely by the runtime permission prompt. Microphone access for chirp pairing works from the popup under the existing '<all_urls>' host permission; no manifest entry is required or accepted here.",33 "manifest_version": 3,44 "name": "Peach Password Manager",5- "version": "3.6.105",5+ "version": "3.6.106",66 "description": "Zero-knowledge password manager",77 "browser_specific_settings": {88 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "3.6.105",4+ "version": "3.6.106",55 "description": "Zero-knowledge password manager",66 "action": {77 "default_popup": "src/popup/index.html",package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "3.6.105",3+ "version": "3.6.106",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {src/background/passkey.ts+38 −4
@@ -370,9 +370,37 @@370370 }371371 372372 if (!vault.isUnlocked()) {373- await openApprovalPopup();374- await rejectCreate(request.requestId, "NotAllowedError", "Unlock Peach first.");375- return;373+ // Same wait-for-unlock pattern handleGetRequest uses: summon the inline374+ // unlock prompt on the active tab so the user can unlock without leaving375+ // the page, then hold the create request open until the vault unlocks or376+ // the timeout fires. Immediately rejecting here means Google shows377+ // "Something went wrong" the instant the user picks Peach.378+ const tabs = await new Promise<chrome.tabs.Tab[]>((resolve) =>379+ chrome.tabs.query({ active: true, lastFocusedWindow: true }, resolve)380+ );381+ const activeTab = tabs[0];382+ if (activeTab?.id) {383+ chrome.tabs.sendMessage(activeTab.id, {384+ type: 'SUMMON_AUTOFILL',385+ source: 'passkey-unlock',386+ }).catch(() => { void openApprovalPopup(); });387+ } else {388+ await openApprovalPopup();389+ }390+391+ const unlocked = await new Promise<boolean>((resolve) => {392+ const timeoutId = setTimeout(() => {393+ unlockWaiters.delete(request.requestId);394+ resolve(false);395+ }, APPROVAL_TIMEOUT_MS);396+ unlockWaiters.set(request.requestId, { resolve, timeoutId });397+ });398+399+ if (!unlocked || !vault.isUnlocked()) {400+ await rejectCreate(request.requestId, "NotAllowedError", "Not unlocked in time.");401+ return;402+ }403+ // Vault is now unlocked — fall through to approval.376404 }377405 378406 const approved = await promptUserApproval({@@ -1123,9 +1151,15 @@11231151 11241152 if (!listenersRegistered) {11251153 chrome.webAuthenticationProxy.onIsUvpaaRequest.addListener((requestInfo) => {1154+ // Peach is a user-verifying platform authenticator whenever the proxy1155+ // is attached. The lock state is irrelevant here — the create/get1156+ // handlers prompt for unlock when needed. Returning false while locked1157+ // makes Google (and any RP that checks isUvpaa as a precondition) show1158+ // "A passkey can't be created on this device" before the user even1159+ // gets a chance to start the ceremony.11261160 void chrome.webAuthenticationProxy.completeIsUvpaaRequest({11271161 requestId: requestInfo.requestId,1128- isUvpaa: vault.isUnlocked(),1162+ isUvpaa: true,11291163 });11301164 });11311165 chrome.webAuthenticationProxy.onCreateRequest.addListener((requestInfo) => {