fix(login): improve webauthn error handling (#9474)

This PR improves error handling around webauthn functions in the login.
This commit is contained in:
Max Peintner
2025-03-05 15:47:48 +01:00
committed by GitHub
parent b0fa974419
commit a82f5805b6
3 changed files with 68 additions and 50 deletions

View File

@@ -15,7 +15,17 @@ function checkWebauthnSupported(button, func) {
function webauthnError(error) { function webauthnError(error) {
let err = document.getElementById("wa-error"); let err = document.getElementById("wa-error");
err.getElementsByClassName("cause")[0].innerText = error.message; let causeElement = err.getElementsByClassName("cause")[0];
if (error.message) {
causeElement.innerText = error.message;
} else if (error.value) {
causeElement.innerText = error.value;
} else {
console.error("Unknown error:", error);
causeElement.innerText = "An unknown error occurred.";
}
err.classList.remove("hidden"); err.classList.remove("hidden");
} }

View File

@@ -3,29 +3,36 @@ document.addEventListener(
checkWebauthnSupported("btn-login", login) checkWebauthnSupported("btn-login", login)
); );
function login() { async function login() {
document.getElementById("wa-error").classList.add("hidden"); document.getElementById("wa-error").classList.add("hidden");
let makeAssertionOptions = JSON.parse( let makeAssertionOptions;
atob(document.getElementsByName("credentialAssertionData")[0].value) try {
); makeAssertionOptions = JSON.parse(atob(document.getElementsByName("credentialAssertionData")[0].value));
makeAssertionOptions.publicKey.challenge = bufferDecode( } catch (e) {
makeAssertionOptions.publicKey.challenge, webauthnError({ message: "Failed to parse credential assertion data." });
"publicKey.challenge" return;
); }
makeAssertionOptions.publicKey.allowCredentials.forEach(function (listItem) {
listItem.id = bufferDecode(listItem.id, "publicKey.allowCredentials.id"); try {
}); makeAssertionOptions.publicKey.challenge = bufferDecode(makeAssertionOptions.publicKey.challenge, "publicKey.challenge");
navigator.credentials makeAssertionOptions.publicKey.allowCredentials.forEach(function (listItem) {
.get({ listItem.id = bufferDecode(listItem.id, "publicKey.allowCredentials.id");
publicKey: makeAssertionOptions.publicKey,
})
.then(function (credential) {
verifyAssertion(credential);
})
.catch(function (err) {
webauthnError(err);
}); });
} catch (e) {
webauthnError({ message: "Failed to decode buffer data." });
return;
}
try {
const credential = await navigator.credentials.get({
publicKey: makeAssertionOptions.publicKey,
});
verifyAssertion(credential);
} catch (err) {
webauthnError(err);
}
} }
function verifyAssertion(assertedCredential) { function verifyAssertion(assertedCredential) {
@@ -49,6 +56,6 @@ function verifyAssertion(assertedCredential) {
}, },
}); });
document.getElementsByName("credentialData")[0].value = btoa(data); document.getElementsByName("credentialData")[0].value = window.btoa(data);
document.getElementsByTagName("form")[0].submit(); document.getElementsByTagName("form")[0].submit();
} }

View File

@@ -3,40 +3,41 @@ document.addEventListener(
checkWebauthnSupported("btn-register", registerCredential) checkWebauthnSupported("btn-register", registerCredential)
); );
function registerCredential() { async function registerCredential() {
document.getElementById("wa-error").classList.add("hidden"); document.getElementById("wa-error").classList.add("hidden");
let opt = JSON.parse( let opt;
atob(document.getElementsByName("credentialCreationData")[0].value) try {
); opt = JSON.parse(atob(document.getElementsByName("credentialCreationData")[0].value));
opt.publicKey.challenge = bufferDecode( } catch (e) {
opt.publicKey.challenge, webauthnError({ message: "Failed to parse credential creation data." });
"publicKey.challenge" return;
); }
opt.publicKey.user.id = bufferDecode(
opt.publicKey.user.id, try {
"publicKey.user.id" opt.publicKey.challenge = bufferDecode(opt.publicKey.challenge, "publicKey.challenge");
); opt.publicKey.user.id = bufferDecode(opt.publicKey.user.id, "publicKey.user.id");
if (opt.publicKey.excludeCredentials) { if (opt.publicKey.excludeCredentials) {
for (let i = 0; i < opt.publicKey.excludeCredentials.length; i++) { for (let i = 0; i < opt.publicKey.excludeCredentials.length; i++) {
if (opt.publicKey.excludeCredentials[i].id !== null) { if (opt.publicKey.excludeCredentials[i].id !== null) {
opt.publicKey.excludeCredentials[i].id = bufferDecode( opt.publicKey.excludeCredentials[i].id = bufferDecode(opt.publicKey.excludeCredentials[i].id, "publicKey.excludeCredentials");
opt.publicKey.excludeCredentials[i].id, }
"publicKey.excludeCredentials"
);
} }
} }
} catch (e) {
webauthnError({ message: "Failed to decode buffer data." });
return;
} }
navigator.credentials
.create({ try {
const credential = await navigator.credentials.create({
publicKey: opt.publicKey, publicKey: opt.publicKey,
})
.then(function (credential) {
createCredential(credential);
})
.catch(function (err) {
webauthnError(err);
}); });
createCredential(credential);
} catch (err) {
webauthnError(err);
}
} }
function createCredential(newCredential) { function createCredential(newCredential) {
@@ -56,6 +57,6 @@ function createCredential(newCredential) {
}, },
}); });
document.getElementsByName("credentialData")[0].value = btoa(data); document.getElementsByName("credentialData")[0].value = window.btoa(data);
document.getElementsByTagName("form")[0].submit(); document.getElementsByTagName("form")[0].submit();
} }