Fix passkey auth flow redirects (#3123)

* Fix passkey auth flow redirects
* Handle webauthn error
This commit is contained in:
Merlijn 2024-10-20 01:24:41 +02:00 committed by GitHub
parent c9bf304bc0
commit 5064712fe6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 47 additions and 35 deletions

View file

@ -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<ServerState>,
Extension(kopid): Extension<KOpId>,
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
Json(assertion): Json<Box<PublicKeyCredential>>,
Form(assertion): Form<JsonedPublicKeyCredential>,
) -> 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::<Box<PublicKeyCredential>>(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(

View file

@ -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) {};

View file

@ -13,11 +13,19 @@
defer></script>
(% if passkey %)
<button hx-disable type="button" class="btn btn-dark"
id="start-passkey-button">Use Passkey</button>
<form id="cred-form" action="/ui/login/passkey" method="POST">
<input hidden="hidden" name="cred" id="cred">
<button hx-disable type="button" class="btn btn-dark"
id="start-passkey-button">Use Passkey</button>
</form>
(% else %)
<button type="button" class="btn btn-dark" id="start-seckey-button">Use Security
Key</button>
<form id="cred-form" action="/ui/login/seckey" method="POST">
<input hidden="hidden" name="cred" id="cred">
<button type="button" class="btn btn-dark" id="start-seckey-button"
>Use Security Key</button>
</form>
(% endif %)
(% endblock %)