use crate::sql::Pool;
use crate::totp::TotpSecret;
use crate::Model;
#[derive(Model, Debug, Clone)]
#[rustango(table = "rustango_admin_totp", managed = false)]
#[allow(dead_code)]
pub struct AdminTotp {
#[rustango(primary_key)]
pub user_id: i64,
#[rustango(max_length = 64)]
pub secret_base32: String,
#[rustango(default = "false")]
pub confirmed: bool,
#[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(&[AdminTotp::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 device(pool: &Pool, user_id: i64) -> Option<AdminTotp> {
use crate::sql::FetcherPool as _;
AdminTotp::objects()
.filter("user_id", user_id)
.fetch(pool)
.await
.ok()
.and_then(|rows| rows.into_iter().next())
}
pub async fn confirmed_secret(pool: &Pool, user_id: i64) -> Option<TotpSecret> {
let d = device(pool, user_id).await?;
if !d.confirmed {
return None;
}
TotpSecret::from_base32(&d.secret_base32)
}
pub async fn start_enrollment(
pool: &Pool,
user_id: i64,
secret: &TotpSecret,
) -> Result<(), crate::sql::ExecError> {
let del = AdminTotp::objects()
.filter("user_id", user_id)
.compile_delete()?;
crate::sql::delete_pool(pool, &del).await?;
let row = AdminTotp {
user_id,
secret_base32: secret.to_base32(),
confirmed: false,
created_at: chrono::Utc::now(),
};
row.insert_pool(pool).await?;
Ok(())
}
pub async fn confirm(pool: &Pool, user_id: i64) -> Result<(), crate::sql::ExecError> {
use crate::sql::UpdaterPool as _;
AdminTotp::objects()
.filter("user_id", user_id)
.update()
.set("confirmed", true)
.execute_pool(pool)
.await?;
Ok(())
}