1use crate::database::Database;
2use crate::model::{DataExportOptions, DatabaseError};
3use crate::ToHtml;
4use axum::body::Body;
5use axum::extract::Query;
6use axum::http::{HeaderMap, HeaderValue, Response};
7use axum_extra::extract::CookieJar;
8use hcaptcha_no_wasm::Hcaptcha;
9
10use authbeam::model::{FinePermission, IpBlockCreate, NotificationCreate};
11use databeam::prelude::DefaultReturn;
12
13use axum::{
14 extract::{Path, State},
15 response::{IntoResponse, Redirect},
16 routing::{get, post},
17 Json, Router,
18};
19
20pub fn routes(database: Database) -> Router {
21 Router::new()
22 .route("/{id}/report", post(report_request))
23 .route("/{id}/export", get(export_request)) .route("/{id}/ipblock", post(ipblock_request))
25 .with_state(database)
27}
28
29pub async fn expand_request(
33 Path(id): Path<String>,
34 State(database): State<Database>,
35) -> Response<Body> {
36 match database.get_profile(id).await {
37 Ok(r) => Redirect::to(&format!("/@{}", r.username)).into_response(),
38 Err(_) => (
39 axum::http::StatusCode::NOT_FOUND,
40 [(axum::http::header::CONTENT_TYPE, "text/html")],
41 DatabaseError::NotFound.to_html(database),
42 )
43 .into_response(),
44 }
45}
46
47pub async fn expand_ip_request(
49 jar: CookieJar,
50 Path(ip): Path<String>,
51 State(database): State<Database>,
52) -> impl IntoResponse {
53 match jar.get("__Secure-Token") {
55 Some(c) => {
56 if let Err(_) = database
57 .auth
58 .get_profile_by_unhashed(c.value_trimmed())
59 .await
60 {
61 return Redirect::to("/");
62 }
63 }
64 None => {
65 return Redirect::to("/");
66 }
67 };
68
69 match database.auth.get_profile_by_ip(&ip).await {
71 Ok(r) => Redirect::to(&format!("/@{}", r.username)),
72 Err(_) => Redirect::to("/"),
73 }
74}
75
76pub async fn report_request(
78 headers: HeaderMap,
79 Path(input): Path<String>,
80 State(database): State<Database>,
81 Json(req): Json<super::CreateReport>,
82) -> impl IntoResponse {
83 if let Err(e) = req
85 .valid_response(&database.config.captcha.secret, None)
86 .await
87 {
88 return Json(DefaultReturn {
89 success: false,
90 message: e.to_string(),
91 payload: (),
92 });
93 }
94
95 let profile = match database.get_profile(input.clone()).await {
97 Ok(p) => p,
98 Err(e) => return Json(e.to_json()),
99 };
100
101 let real_ip = if let Some(ref real_ip_header) = database.config.real_ip_header {
103 headers
104 .get(real_ip_header.to_owned())
105 .unwrap_or(&HeaderValue::from_static(""))
106 .to_str()
107 .unwrap_or("")
108 .to_string()
109 } else {
110 String::new()
111 };
112
113 if database.auth.get_ipban_by_ip(&real_ip).await.is_ok() {
115 return Json(DefaultReturn {
116 success: false,
117 message: DatabaseError::Banned.to_string(),
118 payload: (),
119 });
120 }
121
122 match database
124 .auth
125 .create_notification(
126 NotificationCreate {
127 title: format!("**PROFILE REPORT**: [/@{input}](/+u/{})", profile.id),
128 content: format!("{}\n\n***\n\n[{real_ip}](/+i/{real_ip})", req.content),
129 address: format!("/@{input}"),
130 recipient: "*".to_string(), },
132 None,
133 )
134 .await
135 {
136 Ok(_) => {
137 return Json(DefaultReturn {
138 success: true,
139 message: "Profile reported!".to_string(),
140 payload: (),
141 })
142 }
143 Err(_) => Json(DefaultReturn {
144 success: false,
145 message: DatabaseError::NotFound.to_string(),
146 payload: (),
147 }),
148 }
149}
150
151pub async fn export_request(
153 jar: CookieJar,
154 Path(username): Path<String>,
155 State(database): State<Database>,
156 Query(props): Query<DataExportOptions>,
157) -> impl IntoResponse {
158 let auth_user = match jar.get("__Secure-Token") {
160 Some(c) => match database
161 .auth
162 .get_profile_by_unhashed(c.value_trimmed())
163 .await
164 {
165 Ok(ua) => ua,
166 Err(e) => return Json(e.to_json()),
167 },
168 None => return Json(DatabaseError::NotAllowed.to_json()),
169 };
170
171 let group = match database.auth.get_group_by_id(auth_user.group).await {
172 Ok(g) => g,
173 Err(_) => {
174 return Json(DefaultReturn {
175 success: false,
176 message: DatabaseError::Other.to_string(),
177 payload: None,
178 })
179 }
180 };
181
182 if !group.permissions.check(FinePermission::EXPORT_DATA) {
183 return Json(DefaultReturn {
184 success: false,
185 message: DatabaseError::NotAllowed.to_string(),
186 payload: None,
187 });
188 }
189
190 let other_user = match database.auth.get_profile_by_username(&username).await {
192 Ok(ua) => ua,
193 Err(_) => {
194 return Json(DefaultReturn {
195 success: false,
196 message: DatabaseError::NotFound.to_string(),
197 payload: None,
198 })
199 }
200 };
201
202 match database.create_data_export(other_user.id, props).await {
204 Ok(export) => {
205 return Json(DefaultReturn {
206 success: true,
207 message: "Acceptable".to_string(),
208 payload: Some(export),
209 })
210 }
211 Err(e) => return Json(e.to_json()),
212 }
213}
214
215pub async fn ipblock_request(
217 jar: CookieJar,
218 Path(id): Path<String>,
219 State(database): State<Database>,
220) -> impl IntoResponse {
221 let auth_user = match jar.get("__Secure-Token") {
223 Some(c) => match database
224 .auth
225 .get_profile_by_unhashed(c.value_trimmed())
226 .await
227 {
228 Ok(ua) => ua,
229 Err(_) => {
230 return Json(DatabaseError::NotAllowed.into());
231 }
232 },
233 None => {
234 return Json(DatabaseError::NotAllowed.into());
235 }
236 };
237
238 let profile = match database.auth.get_profile(&id).await {
240 Ok(p) => p,
241 Err(e) => return Json(e.to_json()),
242 };
243
244 for ip in profile.ips {
246 if let Err(_) = database
247 .auth
248 .create_ipblock(
249 IpBlockCreate {
250 ip,
251 context: profile.username.clone(),
252 },
253 auth_user.clone(),
254 )
255 .await
256 {
257 continue;
258 }
259 }
260
261 return Json(DefaultReturn {
262 success: true,
263 message: "IPs blocked!".to_string(),
264 payload: (),
265 });
266}