use crate::sql::Auto;
use crate::Model;
#[derive(Model, Debug, Clone)]
#[rustango(
table = "rustango_admin_users",
app = "admin",
display = "username",
admin(
list_display = "username, is_superuser, active, created_at",
search_fields = "username",
ordering = "username",
readonly_fields = "created_at, sessions_revoked_at",
formfield_overrides = "password_hash: password",
)
)]
pub struct AdminUser {
#[rustango(primary_key)]
pub id: Auto<i64>,
#[rustango(max_length = 150, unique)]
pub username: String,
#[rustango(max_length = 200)]
pub password_hash: String,
#[cfg(feature = "admin-sso")]
#[rustango(max_length = 254, unique)]
pub email: Option<String>,
#[rustango(default = "false")]
pub is_superuser: bool,
#[rustango(default = "true")]
pub active: bool,
pub created_at: chrono::DateTime<chrono::Utc>,
pub sessions_revoked_at: Option<chrono::DateTime<chrono::Utc>>,
}
fn admin_hash_password<'a>(
values: &'a mut Vec<(&'static str, crate::core::SqlValue)>,
before: Option<&'a serde_json::Value>,
) -> super::derived_fields::DeriveFuture<'a> {
Box::pin(async move {
let Some(plain) = super::derived_fields::take_secret(values, "password_hash") else {
return Ok(());
};
if before.and_then(|r| r.get("password_hash")?.as_str()) == Some(plain.as_str()) {
return Ok(());
}
let hash = crate::passwords::hash_async(&plain)
.await
.map_err(|e| e.to_string())?;
values.push(("password_hash", crate::core::SqlValue::String(hash)));
Ok::<(), String>(())
})
}
inventory::submit! {
super::derived_fields::AdminDerivedField {
table: "rustango_admin_users",
derive: admin_hash_password,
}
}
impl AdminUser {
#[allow(clippy::disallowed_methods)]
pub fn new_with_password(
username: impl Into<String>,
password: &str,
is_superuser: bool,
) -> Result<Self, crate::passwords::PasswordError> {
Ok(Self {
id: Auto::Unset,
username: username.into(),
password_hash: crate::passwords::hash(password)?,
#[cfg(feature = "admin-sso")]
email: None,
is_superuser,
active: true,
created_at: chrono::Utc::now(),
sessions_revoked_at: None,
})
}
}