mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FIX: Broken login with security key when passkeys enabled (#24249)
In some browsers, 2FA login was failing with a "request is already pending" error. This only applied when `experimental_passkeys` was enabled and on Chrome only. This was due to the fact that the webauthn API only supports one auth attempt at a time, so the security key call needs to abort the passkey's conditional UI request before starting. I am not sure we can test this. We have system specs that simulate webauthn credentials and they didn't catch this (probably because the simulation covers the whole flow).
This commit is contained in:
@@ -17,6 +17,27 @@ export function isWebauthnSupported() {
|
||||
return typeof PublicKeyCredential !== "undefined";
|
||||
}
|
||||
|
||||
// The webauthn API only supports one auth attempt at a time
|
||||
// We need this service to cancel the previous attempt when a new one is started
|
||||
class WebauthnAbortService {
|
||||
controller = undefined;
|
||||
|
||||
signal() {
|
||||
if (this.controller) {
|
||||
const abortError = new Error("Cancelling pending webauthn call");
|
||||
abortError.name = "AbortError";
|
||||
this.controller.abort(abortError);
|
||||
}
|
||||
|
||||
this.controller = new AbortController();
|
||||
return this.controller.signal;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to use a singleton here to reset the active webauthn ceremony
|
||||
// Inspired by the BaseWebAuthnAbortService in https://github.com/MasterKale/SimpleWebAuthn
|
||||
const WebauthnAbortHandler = new WebauthnAbortService();
|
||||
|
||||
export function getWebauthnCredential(
|
||||
challenge,
|
||||
allowedCredentialIds,
|
||||
@@ -49,6 +70,7 @@ export function getWebauthnCredential(
|
||||
// (this is only a hint, though, browser may still prompt)
|
||||
userVerification: "discouraged",
|
||||
},
|
||||
signal: WebauthnAbortHandler.signal(),
|
||||
})
|
||||
.then((credential) => {
|
||||
// 3. If credential.response is not an instance of AuthenticatorAssertionResponse, abort the ceremony.
|
||||
@@ -94,27 +116,6 @@ export function getWebauthnCredential(
|
||||
});
|
||||
}
|
||||
|
||||
// The webauthn API only supports one auth attempt at a time
|
||||
// We need this service to cancel the previous attempt when a new one is started
|
||||
class WebauthnAbortService {
|
||||
controller = undefined;
|
||||
|
||||
signal() {
|
||||
if (this.controller) {
|
||||
const abortError = new Error("Cancelling pending webauthn call");
|
||||
abortError.name = "AbortError";
|
||||
this.controller.abort(abortError);
|
||||
}
|
||||
|
||||
this.controller = new AbortController();
|
||||
return this.controller.signal;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to use a singleton here to reset the active webauthn ceremony
|
||||
// Inspired by the BaseWebAuthnAbortService in https://github.com/MasterKale/SimpleWebAuthn
|
||||
const WebauthnAbortHandler = new WebauthnAbortService();
|
||||
|
||||
export async function getPasskeyCredential(
|
||||
challenge,
|
||||
errorCallback,
|
||||
|
||||
Reference in New Issue
Block a user