From 5064712fe6a12a040a8920c4563386f3a216b317 Mon Sep 17 00:00:00 2001 From: Merlijn <32853531+ToxicMushroom@users.noreply.github.com> Date: Sun, 20 Oct 2024 01:24:41 +0200 Subject: [PATCH] Fix passkey auth flow redirects (#3123) * Fix passkey auth flow redirects * Handle webauthn error --- server/core/src/https/views/login.rs | 21 +++++++++-- server/core/static/pkhtml.js | 45 +++++++++-------------- server/core/templates/login_webauthn.html | 16 ++++++-- 3 files changed, 47 insertions(+), 35 deletions(-) diff --git a/server/core/src/https/views/login.rs b/server/core/src/https/views/login.rs index 03037ff13..c87453f98 100644 --- a/server/core/src/https/views/login.rs +++ b/server/core/src/https/views/login.rs @@ -1,4 +1,5 @@ use super::{cookies, empty_string_as_none, HtmlTemplate, UnrecoverableErrorView}; +use crate::https::views::errors::HtmxError; use crate::https::{ extractors::{DomainInfo, DomainInfoRead, VerifiedClientInformation}, middleware::KOpId, @@ -489,16 +490,30 @@ pub async fn view_login_backupcode_post( credential_step(state, kopid, jar, client_auth_info, auth_cred, domain_info).await } +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct JsonedPublicKeyCredential { + cred: String, +} + pub async fn view_login_passkey_post( State(state): State, Extension(kopid): Extension, VerifiedClientInformation(client_auth_info): VerifiedClientInformation, DomainInfo(domain_info): DomainInfo, jar: CookieJar, - Json(assertion): Json>, + Form(assertion): Form, ) -> Response { - let auth_cred = AuthCredential::Passkey(assertion); - credential_step(state, kopid, jar, client_auth_info, auth_cred, domain_info).await + let result = serde_json::from_str::>(assertion.cred.as_str()); + match result { + Ok(pkc) => { + let auth_cred = AuthCredential::Passkey(pkc); + credential_step(state, kopid, jar, client_auth_info, auth_cred, domain_info).await + } + Err(e) => { + error!(err = ?e, "Unable to deserialize credential submission"); + HtmxError::new(&kopid, OperationError::SerdeJsonError).into_response() + } + } } pub async fn view_login_seckey_post( diff --git a/server/core/static/pkhtml.js b/server/core/static/pkhtml.js index b2889425b..c6284aebe 100644 --- a/server/core/static/pkhtml.js +++ b/server/core/static/pkhtml.js @@ -1,5 +1,5 @@ -function asskey_login(target) { +function asskey_login() { let credentialRequestOptions = JSON.parse(document.getElementById('data').textContent); credentialRequestOptions.publicKey.challenge = Base64.toUint8Array(credentialRequestOptions.publicKey.challenge); credentialRequestOptions.publicKey.allowCredentials?.forEach(function (listItem) { @@ -8,46 +8,35 @@ function asskey_login(target) { navigator.credentials.get({ publicKey: credentialRequestOptions.publicKey }) .then((assertion) => { - const myRequest = new Request(target, { - method: 'POST', - redirect: 'follow', - headers: { - 'Content-Type': 'application/json' + document.getElementById("cred").value = JSON.stringify({ + id: assertion.id, + rawId: Base64.fromUint8Array(new Uint8Array(assertion.rawId), true), + type: assertion.type, + response: { + authenticatorData: Base64.fromUint8Array(new Uint8Array(assertion.response.authenticatorData), true), + clientDataJSON: Base64.fromUint8Array(new Uint8Array(assertion.response.clientDataJSON), true), + signature: Base64.fromUint8Array(new Uint8Array(assertion.response.signature), true), + userHandle: Base64.fromUint8Array(new Uint8Array(assertion.response.userHandle), true) }, - body: JSON.stringify({ - id: assertion.id, - rawId: Base64.fromUint8Array(new Uint8Array(assertion.rawId), true), - type: assertion.type, - response: { - authenticatorData: Base64.fromUint8Array(new Uint8Array(assertion.response.authenticatorData), true), - clientDataJSON: Base64.fromUint8Array(new Uint8Array(assertion.response.clientDataJSON), true), - signature: Base64.fromUint8Array(new Uint8Array(assertion.response.signature), true), - userHandle: Base64.fromUint8Array(new Uint8Array(assertion.response.userHandle), true) - }, - }), }); - fetch(myRequest).then((response) => { - if (response.redirected) { - window.location.replace(response.url); - return; - } else { - console.error("expected a redirect"); - } - }) - }) + document.getElementById("cred-form").submit(); + }).catch((error) => { + console.error(`Failed to complete passkey authentication: ${error}`); + throw error; + }); } try { const myButton = document.getElementById("start-passkey-button"); myButton.addEventListener("click", () => { - asskey_login('/ui/login/passkey'); + asskey_login(); }); } catch (_error) {}; try { const myButton = document.getElementById("start-seckey-button"); myButton.addEventListener("click", () => { - asskey_login('/ui/login/seckey'); + asskey_login(); }); } catch (_error) {}; diff --git a/server/core/templates/login_webauthn.html b/server/core/templates/login_webauthn.html index bb749ad35..f1e01fb69 100644 --- a/server/core/templates/login_webauthn.html +++ b/server/core/templates/login_webauthn.html @@ -13,11 +13,19 @@ defer> (% if passkey %) - +
+ + + +
(% else %) - +
+ + + +
(% endif %) (% endblock %)