use serde_json::{Value, json}; use sqlx::{PgPool, Postgres, Transaction}; use uuid::Uuid; pub const ACTION_LOGIN: &str = "login"; pub const FOLDER_AUTH: &str = "auth"; fn login_detail(provider: &str, client_ip: Option<&str>, user_agent: Option<&str>) -> Value { json!({ "provider": provider, "client_ip": client_ip, "user_agent": user_agent, }) } /// Write a login audit entry without requiring an explicit transaction. pub async fn log_login( pool: &PgPool, entry_type: &str, provider: &str, user_id: Uuid, client_ip: Option<&str>, user_agent: Option<&str>, ) { let detail = login_detail(provider, client_ip, user_agent); let result: Result<_, sqlx::Error> = sqlx::query( "INSERT INTO audit_log (user_id, action, folder, type, name, detail) \ VALUES ($1, $2, $3, $4, $5, $6)", ) .bind(user_id) .bind(ACTION_LOGIN) .bind(FOLDER_AUTH) .bind(entry_type) .bind(provider) .bind(&detail) .execute(pool) .await; if let Err(e) = result { tracing::warn!(error = %e, entry_type, provider, "failed to write login audit log"); } else { tracing::debug!(entry_type, provider, ?user_id, "login audit logged"); } } /// Write an audit entry within an existing transaction. pub async fn log_tx( tx: &mut Transaction<'_, Postgres>, user_id: Option, action: &str, folder: &str, entry_type: &str, name: &str, detail: Value, ) { let result: Result<_, sqlx::Error> = sqlx::query( "INSERT INTO audit_log (user_id, action, folder, type, name, detail) \ VALUES ($1, $2, $3, $4, $5, $6)", ) .bind(user_id) .bind(action) .bind(folder) .bind(entry_type) .bind(name) .bind(&detail) .execute(&mut **tx) .await; if let Err(e) = result { tracing::warn!(error = %e, "failed to write audit log"); } else { tracing::debug!(action, folder, entry_type, name, "audit logged"); } } #[cfg(test)] mod tests { use super::*; #[test] fn login_detail_includes_expected_fields() { let detail = login_detail("google", Some("127.0.0.1"), Some("Mozilla/5.0")); assert_eq!(detail["provider"], "google"); assert_eq!(detail["client_ip"], "127.0.0.1"); assert_eq!(detail["user_agent"], "Mozilla/5.0"); } }