1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use crate::db::Db;
5use crate::error::AuthError;
6use crate::types::{AuditEntryId, UserId};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)]
10#[sqlx(rename_all = "snake_case")]
11pub enum AuditEvent {
12 Login,
13 LoginFailed,
14 Logout,
15 Register,
16 PasswordChange,
17 PasswordReset,
18 RoleAssigned,
19 RoleUnassigned,
20 PermissionAssigned,
21 PermissionUnassigned,
22 SessionCreated,
23 SessionExpired,
24 UserUpdated,
25 UserDeleted,
26}
27
28#[derive(Debug, Clone, Serialize, sqlx::FromRow)]
30pub struct AuditEntry {
31 pub id: AuditEntryId,
32 pub event_type: AuditEvent,
33 pub user_id: Option<UserId>,
34 pub target_id: Option<String>,
35 pub ip_address: Option<String>,
36 pub user_agent: Option<String>,
37 pub detail: Option<String>,
38 pub created_at: DateTime<Utc>,
39}
40
41impl Db {
42 pub async fn log_audit(
47 &self,
48 event_type: AuditEvent,
49 user_id: Option<&UserId>,
50 target_id: Option<&str>,
51 ip_address: Option<&str>,
52 user_agent: Option<&str>,
53 detail: Option<&str>,
54 ) -> Result<(), AuthError> {
55 let id = AuditEntryId::new();
56 sqlx::query(
57 "INSERT INTO allowthem_audit_log
58 (id, event_type, user_id, target_id, ip_address, user_agent, detail)
59 VALUES (?, ?, ?, ?, ?, ?, ?)",
60 )
61 .bind(id)
62 .bind(event_type)
63 .bind(user_id.copied())
64 .bind(target_id)
65 .bind(ip_address)
66 .bind(user_agent)
67 .bind(detail)
68 .execute(self.pool())
69 .await
70 .map_err(AuthError::Database)?;
71 Ok(())
72 }
73
74 pub async fn get_audit_log(
78 &self,
79 user_id: Option<&UserId>,
80 limit: u32,
81 offset: u32,
82 ) -> Result<Vec<AuditEntry>, AuthError> {
83 match user_id {
84 Some(uid) => {
85 sqlx::query_as::<_, AuditEntry>(
86 "SELECT id, event_type, user_id, target_id, ip_address, user_agent, detail, created_at
87 FROM allowthem_audit_log
88 WHERE user_id = ?
89 ORDER BY created_at DESC
90 LIMIT ? OFFSET ?",
91 )
92 .bind(*uid)
93 .bind(limit)
94 .bind(offset)
95 .fetch_all(self.pool())
96 .await
97 .map_err(AuthError::Database)
98 }
99 None => {
100 sqlx::query_as::<_, AuditEntry>(
101 "SELECT id, event_type, user_id, target_id, ip_address, user_agent, detail, created_at
102 FROM allowthem_audit_log
103 ORDER BY created_at DESC
104 LIMIT ? OFFSET ?",
105 )
106 .bind(limit)
107 .bind(offset)
108 .fetch_all(self.pool())
109 .await
110 .map_err(AuthError::Database)
111 }
112 }
113 }
114
115 pub async fn get_audit_log_by_event(
119 &self,
120 event_type: AuditEvent,
121 limit: u32,
122 offset: u32,
123 ) -> Result<Vec<AuditEntry>, AuthError> {
124 sqlx::query_as::<_, AuditEntry>(
125 "SELECT id, event_type, user_id, target_id, ip_address, user_agent, detail, created_at
126 FROM allowthem_audit_log
127 WHERE event_type = ?
128 ORDER BY created_at DESC
129 LIMIT ? OFFSET ?",
130 )
131 .bind(event_type)
132 .bind(limit)
133 .bind(offset)
134 .fetch_all(self.pool())
135 .await
136 .map_err(AuthError::Database)
137 }
138}