pub mod ceremony;
pub mod error;
pub mod session;
pub mod verify;
pub use ceremony::{
authentication_options_json, generate_challenge, registration_options_json,
verify_authentication, verify_registration, RegistrationOutcome,
};
pub use error::PasskeyError;
pub use session::{open_challenge, seal_challenge};
use crate::sql::{Auto, Pool};
use crate::Model;
#[derive(Model, Debug, Clone)]
#[rustango(table = "rustango_webauthn_credentials", managed = false)]
#[allow(dead_code)]
pub struct WebauthnCredential {
#[rustango(primary_key)]
pub id: Auto<i64>,
#[rustango(index)]
pub user_id: i64,
#[rustango(max_length = 255, unique)]
pub credential_id: String,
pub public_key: Vec<u8>,
#[rustango(default = "0")]
pub sign_count: i64,
#[rustango(max_length = 64, default = "''")]
pub label: String,
#[rustango(default = "now()")]
pub created_at: chrono::DateTime<chrono::Utc>,
}
pub async fn ensure_table(pool: &Pool) -> Result<(), sqlx::Error> {
use crate::core::Model as _;
let snapshot =
crate::migrate::SchemaSnapshot::from_models_forced(&[WebauthnCredential::SCHEMA]);
let changes =
crate::migrate::detect_changes(&crate::migrate::SchemaSnapshot::default(), &snapshot);
let batch =
crate::migrate::render_changes_split_with_dialect(&changes, &snapshot, pool.dialect())
.map_err(sqlx::Error::Protocol)?;
for stmt in batch.immediate.iter().chain(batch.deferred_fks.iter()) {
if let Err(e) = crate::sql::raw_execute_pool(pool, stmt, Vec::new()).await {
let msg = format!("{e}").to_lowercase();
if msg.contains("already exists") || msg.contains("duplicate") {
continue;
}
return Err(match e {
crate::sql::ExecError::Driver(err) => err,
other => sqlx::Error::Protocol(format!("{other}")),
});
}
}
Ok(())
}
pub async fn for_user(
pool: &Pool,
user_id: i64,
) -> Result<Vec<WebauthnCredential>, crate::sql::ExecError> {
use crate::sql::FetcherPool as _;
WebauthnCredential::objects()
.filter("user_id", user_id)
.fetch(pool)
.await
}
pub async fn by_credential_id(
pool: &Pool,
credential_id: &str,
) -> Result<Option<WebauthnCredential>, crate::sql::ExecError> {
use crate::sql::FetcherPool as _;
Ok(WebauthnCredential::objects()
.filter("credential_id", credential_id.to_owned())
.fetch(pool)
.await?
.into_iter()
.next())
}
pub async fn register(
pool: &Pool,
user_id: i64,
credential_id: &str,
public_key: Vec<u8>,
sign_count: i64,
label: &str,
) -> Result<(), crate::sql::ExecError> {
let mut row = WebauthnCredential {
id: Auto::Unset,
user_id,
credential_id: credential_id.to_owned(),
public_key,
sign_count,
label: label.to_owned(),
created_at: chrono::Utc::now(),
};
row.insert_pool(pool).await?;
Ok(())
}
pub async fn update_sign_count(
pool: &Pool,
credential_id: &str,
new_count: i64,
) -> Result<(), crate::sql::ExecError> {
use crate::sql::UpdaterPool as _;
WebauthnCredential::objects()
.filter("credential_id", credential_id.to_owned())
.update()
.set("sign_count", new_count)
.execute_pool(pool)
.await?;
Ok(())
}