use argon2::{Argon2, PasswordHash, PasswordVerifier};
use jiff::Timestamp;
use otpauth::TOTP;
use serde::{Deserialize, Serialize};
use url::Url;
use webauthn_rs::{
Webauthn, WebauthnBuilder,
prelude::{PasskeyAuthentication, PublicKeyCredential, RequestChallengeResponse},
};
#[derive(Default, Serialize, Deserialize)]
pub struct LoginChallenge {
pub password: bool,
pub totp: bool,
pub webauthn: Option<serde_json::Value>,
}
impl LoginChallenge {
pub fn webauthn_key(&self) -> Option<RequestChallengeResponse> {
self.webauthn
.as_ref()
.and_then(|w| serde_json::from_value(w.clone()).ok())
}
}
#[derive(Default)]
pub struct LoginChallengeResponse {
password: Option<String>,
totp: Option<u32>,
webauthn: Option<serde_json::Value>,
}
impl LoginChallengeResponse {
pub fn verify_password(&self, stored: &PasswordHash) -> bool {
if let Some(pwd) = &self.password {
Argon2::default()
.verify_password(pwd.as_bytes(), stored)
.is_ok()
} else {
false
}
}
pub fn verify_totp(&self, stored: &TOTP) -> bool {
if let Some(code) = self.totp {
let now: u64 = Timestamp::now().as_second().try_into().expect("after 1970");
stored.verify(code, 30, now)
} else {
false
}
}
pub fn verify_webauthn(&self, setup: &Webauthn, stored: &PasskeyAuthentication) -> bool {
if let Some(pubkey_cred) = self.webauthn_pubkey() {
setup
.finish_passkey_authentication(&pubkey_cred, stored)
.is_ok()
} else {
false }
}
pub fn webauthn_pubkey(&self) -> Option<PublicKeyCredential> {
self.webauthn
.as_ref()
.and_then(|w| serde_json::from_value(w.clone()).ok())
}
}
pub fn webauthn_setup(
relaying_party: &str,
effective_domain_name: &str,
rp_origin: &Url,
) -> Webauthn {
let mut builder = WebauthnBuilder::new(effective_domain_name, rp_origin).unwrap();
builder = builder.rp_name(relaying_party);
builder.build().unwrap()
}