mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-09 19:27:45 +02:00
[eric] passkeys: an empty credential list falls through to Chromium instead of cancelling silently (ENG-269)
This commit is contained in:
+9
-1
@@ -2362,12 +2362,20 @@ app.whenReady().then(async () => {
|
||||
ses.on('select-webauthn-account', (event, details, callback) => {
|
||||
const accounts = (details && details.accounts) || [];
|
||||
console.log('[passkey] select-webauthn-account rp=', details && details.relyingPartyId, 'n=', accounts.length);
|
||||
// Nothing to choose from. This authenticator only ever sees credentials in OUR keychain
|
||||
// access group, so a passkey the user made in Safari or Chrome is invisible here and the
|
||||
// list arrives empty. Taking over the event and answering null is an explicit CANCEL, which
|
||||
// the page renders as a prompt that simply never appears. Leave the event alone instead and
|
||||
// let Chromium run its own default, so the site can fall through to "Try another way".
|
||||
if (accounts.length === 0) {
|
||||
console.warn('[passkey] no credential in our keychain group; leaving the choice to Chromium');
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
// One passkey is unambiguous, so answering it directly keeps the flow to a single Touch ID
|
||||
// prompt. With several, the OS sheet is the right chooser and we must not silently guess a
|
||||
// credential the user did not pick; a picker is the follow-up, never a blind first().
|
||||
if (accounts.length === 1) return callback(accounts[0].credentialId);
|
||||
if (accounts.length === 0) return callback(null);
|
||||
console.warn('[passkey] multiple passkeys offered; needs a picker, defaulting to the first');
|
||||
callback(accounts[0].credentialId);
|
||||
});
|
||||
|
||||
@@ -14,9 +14,11 @@ function makeHandler(log = () => {}) {
|
||||
return (event, details, callback) => {
|
||||
const accounts = (details && details.accounts) || [];
|
||||
log(details && details.relyingPartyId, accounts.length);
|
||||
// Zero credentials: do NOT take the event over. Answering null here is an explicit cancel and
|
||||
// the page shows a prompt that never appears; leaving it lets Chromium offer "Try another way".
|
||||
if (accounts.length === 0) return;
|
||||
event.preventDefault();
|
||||
if (accounts.length === 1) return callback(accounts[0].credentialId);
|
||||
if (accounts.length === 0) return callback(null);
|
||||
callback(accounts[0].credentialId);
|
||||
};
|
||||
}
|
||||
@@ -39,13 +41,14 @@ test('several passkeys still answer a real credentialId (never null)', () => {
|
||||
assert.equal(r.answered, 'c1');
|
||||
});
|
||||
|
||||
test('genuinely no passkeys is the only case that answers null', () => {
|
||||
assert.equal(drive({ relyingPartyId: 'google.com', accounts: [] }).answered, null);
|
||||
});
|
||||
|
||||
|
||||
test('a malformed details object cannot throw or hang the ceremony', () => {
|
||||
// Same reasoning as the empty list: nothing to choose means stay out of the way, so the callback
|
||||
// is never invoked and Chromium keeps ownership of the ceremony.
|
||||
for (const d of [undefined, null, {}, { accounts: undefined }]) {
|
||||
assert.equal(drive(d).answered, null, JSON.stringify(d));
|
||||
assert.equal(drive(d).answered, 'NOT CALLED', JSON.stringify(d));
|
||||
assert.equal(drive(d).prevented, false, JSON.stringify(d));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -60,3 +63,12 @@ test('the OLD signature would have cancelled every sign-in', () => {
|
||||
(v) => { answered = v; });
|
||||
assert.equal(answered, null, 'the old handler answered null even with a real passkey present');
|
||||
});
|
||||
|
||||
test('zero credentials leaves the event alone so the site can offer another way', () => {
|
||||
// The failure this encodes: our authenticator only sees passkeys in OUR keychain access group, so
|
||||
// one made in Safari or Chrome arrives as an empty list. Cancelling it is why "Complete sign-in
|
||||
// using your passkey" sat there with no Touch ID prompt ever appearing (ENG-269, reported live).
|
||||
const r = drive({ relyingPartyId: 'google.com', accounts: [] });
|
||||
assert.equal(r.prevented, false, 'took over the event and cancelled instead of falling through');
|
||||
assert.equal(r.answered, 'NOT CALLED', 'answered the callback when it should have stayed silent');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user