tetratto_core/database/
ipbans.rs

1use oiseau::cache::Cache;
2use crate::model::addr::RemoteAddr;
3use crate::model::moderation::AuditLogEntry;
4use crate::model::{Error, Result, auth::IpBan, auth::User, permissions::FinePermission};
5use crate::{auto_method, DataManager};
6
7use oiseau::{PostgresRow, execute, get, query_row, query_rows, params};
8
9impl DataManager {
10    /// Get a [`IpBan`] from an SQL row.
11    pub(crate) fn get_ipban_from_row(x: &PostgresRow) -> IpBan {
12        IpBan {
13            ip: get!(x->0(String)),
14            created: get!(x->1(i64)) as usize,
15            reason: get!(x->2(String)),
16            moderator: get!(x->3(i64)) as usize,
17        }
18    }
19
20    auto_method!(get_ipban_by_ip(&str)@get_ipban_from_row -> "SELECT * FROM ipbans WHERE ip = $1" --name="ip ban" --returns=IpBan --cache-key-tmpl="atto.ipban:{}");
21
22    /// Get an IP ban as a [`RemoteAddr`].
23    ///
24    /// # Arguments
25    /// * `prefix`
26    pub async fn get_ipban_by_addr(&self, addr: &RemoteAddr) -> Result<IpBan> {
27        let conn = match self.0.connect().await {
28            Ok(c) => c,
29            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
30        };
31
32        let res = query_row!(
33            &conn,
34            "SELECT * FROM ipbans WHERE ip = $1",
35            &[&addr.prefix(None)],
36            |x| { Ok(Self::get_ipban_from_row(x)) }
37        );
38
39        if res.is_err() {
40            return Err(Error::GeneralNotFound("ip ban".to_string()));
41        }
42
43        Ok(res.unwrap())
44    }
45
46    /// Get all IP bans (paginated).
47    ///
48    /// # Arguments
49    /// * `batch` - the limit of items in each page
50    /// * `page` - the page number
51    pub async fn get_ipbans(&self, batch: usize, page: usize) -> Result<Vec<IpBan>> {
52        let conn = match self.0.connect().await {
53            Ok(c) => c,
54            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
55        };
56
57        let res = query_rows!(
58            &conn,
59            "SELECT * FROM ipbans ORDER BY created DESC LIMIT $1 OFFSET $2",
60            &[&(batch as i64), &((page * batch) as i64)],
61            |x| { Self::get_ipban_from_row(x) }
62        );
63
64        if res.is_err() {
65            return Err(Error::GeneralNotFound("ip ban".to_string()));
66        }
67
68        Ok(res.unwrap())
69    }
70
71    /// Create a new IP ban in the database.
72    ///
73    /// # Arguments
74    /// * `data` - a mock [`IpBan`] object to insert
75    pub async fn create_ipban(&self, data: IpBan) -> Result<()> {
76        let user = self.get_user_by_id(data.moderator).await?;
77
78        // ONLY moderators can create ip bans
79        if !user.permissions.check(FinePermission::MANAGE_BANS) {
80            return Err(Error::NotAllowed);
81        }
82
83        let conn = match self.0.connect().await {
84            Ok(c) => c,
85            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
86        };
87
88        let res = execute!(
89            &conn,
90            "INSERT INTO ipbans VALUES ($1, $2, $3, $4)",
91            params![
92                &data.ip.as_str(),
93                &(data.created as i64),
94                &data.reason.as_str(),
95                &(data.moderator as i64)
96            ]
97        );
98
99        if let Err(e) = res {
100            return Err(Error::DatabaseError(e.to_string()));
101        }
102
103        // create audit log entry
104        self.create_audit_log_entry(AuditLogEntry::new(
105            user.id,
106            format!("invoked `create_ipban` with x value `{}`", data.ip),
107        ))
108        .await?;
109
110        // return
111        Ok(())
112    }
113
114    pub async fn delete_ipban(&self, ip: &str, user: User) -> Result<()> {
115        // ONLY moderators can manage ip bans
116        if !user.permissions.check(FinePermission::MANAGE_BANS) {
117            return Err(Error::NotAllowed);
118        }
119
120        let conn = match self.0.connect().await {
121            Ok(c) => c,
122            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
123        };
124
125        let res = execute!(&conn, "DELETE FROM ipbans WHERE ip = $1", &[&ip]);
126
127        if let Err(e) = res {
128            return Err(Error::DatabaseError(e.to_string()));
129        }
130
131        self.0.1.remove(format!("atto.ipban:{}", ip)).await;
132
133        // create audit log entry
134        self.create_audit_log_entry(AuditLogEntry::new(
135            user.id,
136            format!("invoked `delete_ipban` with x value `{ip}`"),
137        ))
138        .await?;
139
140        // return
141        Ok(())
142    }
143}