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)?;
crate::migrate::apply_idempotent(pool, &batch).await?;
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> {
confirmed_secret_checked(pool, user_id).await.ok().flatten()
}
pub async fn confirmed_secret_checked(
pool: &Pool,
user_id: i64,
) -> Result<Option<TotpSecret>, crate::sql::ExecError> {
use crate::sql::FetcherPool as _;
let rows = AdminTotp::objects()
.filter("user_id", user_id)
.fetch(pool)
.await?;
let Some(d) = rows.into_iter().next() else {
return Ok(None);
};
if !d.confirmed {
return Ok(None);
}
Ok(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(())
}