1use crate::database::Database;
2use crate::model::{DatabaseError, ProfileCreate, ProfileLogin, TokenContext};
3use axum::http::{HeaderMap, HeaderValue};
4use hcaptcha_no_wasm::Hcaptcha;
5use databeam::prelude::DefaultReturn;
6
7use axum::response::IntoResponse;
8use axum::{
9 extract::{Query, State},
10 Json,
11};
12use axum_extra::extract::cookie::CookieJar;
13use serde::{Deserialize, Serialize};
14
15pub async fn create_request(
17 headers: HeaderMap,
18 State(database): State<Database>,
19 Json(props): Json<ProfileCreate>,
20) -> impl IntoResponse {
21 if !props.policy_consent {
22 return (
23 HeaderMap::new(),
24 serde_json::to_string(&DatabaseError::NotAllowed.to_json::<()>()).unwrap(),
25 );
26 }
27
28 let real_ip = if let Some(ref real_ip_header) = database.config.real_ip_header {
30 headers
31 .get(real_ip_header.to_owned())
32 .unwrap_or(&HeaderValue::from_static(""))
33 .to_str()
34 .unwrap_or("")
35 .to_string()
36 } else {
37 String::new()
38 };
39
40 if database.get_ipban_by_ip(&real_ip).await.is_ok() {
42 return (
43 HeaderMap::new(),
44 serde_json::to_string(&DatabaseError::NotAllowed.to_json::<()>()).unwrap(),
45 );
46 }
47
48 let res = match database.create_profile(props, &real_ip).await {
50 Ok(r) => r,
51 Err(e) => {
52 return (
53 HeaderMap::new(),
54 serde_json::to_string(&DefaultReturn {
55 success: false,
56 message: e.to_string(),
57 payload: (),
58 })
59 .unwrap(),
60 );
61 }
62 };
63
64 let mut headers = HeaderMap::new();
66
67 headers.insert(
68 "Set-Cookie",
69 format!(
70 "__Secure-Token={}; SameSite=Lax; Secure; Path=/; HostOnly=true; HttpOnly=true; Max-Age={}",
71 res,
72 60* 60 * 24 * 365
73 )
74 .parse()
75 .unwrap(),
76 );
77
78 (
79 headers,
80 serde_json::to_string(&DefaultReturn {
81 success: true,
82 message: res.clone(),
83 payload: (),
84 })
85 .unwrap(),
86 )
87}
88
89pub async fn login_request(
91 headers: HeaderMap,
92 State(database): State<Database>,
93 Json(props): Json<ProfileLogin>,
94) -> impl IntoResponse {
95 if let Err(e) = props
97 .valid_response(&database.config.captcha.secret, None)
98 .await
99 {
100 return (
101 HeaderMap::new(),
102 serde_json::to_string(&DefaultReturn {
103 success: false,
104 message: e.to_string(),
105 payload: (),
106 })
107 .unwrap(),
108 );
109 }
110
111 let mut ua = match database.get_profile_by_username(&props.username).await {
113 Ok(ua) => ua,
114 Err(e) => {
115 return (
116 HeaderMap::new(),
117 serde_json::to_string(&DefaultReturn {
118 success: false,
119 message: e.to_string(),
120 payload: (),
121 })
122 .unwrap(),
123 )
124 }
125 };
126
127 let input_password =
129 rainbeam_shared::hash::hash_salted(props.password.clone(), ua.salt.clone());
130
131 if input_password != ua.password {
132 return (
133 HeaderMap::new(),
134 serde_json::to_string(&DatabaseError::IncorrectPassword.to_json::<()>()).unwrap(),
135 );
136 }
137
138 let real_ip = if let Some(ref real_ip_header) = database.config.real_ip_header {
140 headers
141 .get(real_ip_header.to_owned())
142 .unwrap_or(&HeaderValue::from_static(""))
143 .to_str()
144 .unwrap_or("")
145 .to_string()
146 } else {
147 String::new()
148 };
149
150 if database.get_ipban_by_ip(&real_ip).await.is_ok() {
152 return (
153 HeaderMap::new(),
154 serde_json::to_string(&DatabaseError::NotAllowed.to_json::<()>()).unwrap(),
155 );
156 }
157
158 if !database.check_totp(&ua, &props.totp) {
160 return (
161 HeaderMap::new(),
162 serde_json::to_string(&DatabaseError::NotAllowed.to_json::<()>()).unwrap(),
163 );
164 }
165
166 let token = databeam::utility::uuid();
168 let token_hashed = databeam::utility::hash(token.clone());
169
170 ua.tokens.push(token_hashed);
171 ua.ips.push(real_ip);
172 ua.token_context.push(TokenContext::default());
173
174 database
175 .update_profile_tokens(&props.username, ua.tokens, ua.ips, ua.token_context)
176 .await
177 .unwrap();
178
179 let mut headers = HeaderMap::new();
181
182 headers.insert(
183 "Set-Cookie",
184 format!(
185 "__Secure-Token={}; SameSite=Lax; Secure; Path=/; HostOnly=true; HttpOnly=true; Max-Age={}",
186 token,
187 60* 60 * 24 * 365
188 )
189 .parse()
190 .unwrap(),
191 );
192
193 (
194 headers,
195 serde_json::to_string(&DefaultReturn {
196 success: true,
197 message: token,
198 payload: (),
199 })
200 .unwrap(),
201 )
202}
203
204#[derive(serde::Deserialize)]
205pub struct CallbackQueryProps {
206 pub token: String, }
208
209pub async fn callback_request(Query(params): Query<CallbackQueryProps>) -> impl IntoResponse {
210 (
212 [
213 ("Content-Type".to_string(), "text/html".to_string()),
214 (
215 "Set-Cookie".to_string(),
216 format!(
217 "__Secure-Token={}; SameSite=Lax; Secure; Path=/; HostOnly=true; HttpOnly=true; Max-Age={}",
218 params.token,
219 60 * 60 * 24 * 365
220 ),
221 ),
222 ],
223 "<head>
224 <meta http-equiv=\"Refresh\" content=\"0; URL=/\" />
225 </head>"
226 )
227}
228
229pub async fn logout_request(jar: CookieJar) -> impl IntoResponse {
230 if let Some(_) = jar.get("__Secure-Token") {
232 return (
233 [
234 ("Content-Type".to_string(), "text/plain".to_string()),
235 (
236 "Set-Cookie".to_string(),
237 "__Secure-Token=refresh; SameSite=Strict; Secure; Path=/; HostOnly=true; HttpOnly=true; Max-Age=0".to_string(),
238 ) ],
239 "You have been signed out. You can now close this tab.",
240 );
241 }
242
243 (
245 [
246 ("Content-Type".to_string(), "text/plain".to_string()),
247 ("Set-Cookie".to_string(), String::new()),
248 ],
249 "Failed to sign out of account.",
250 )
251}
252
253pub async fn remove_tag(jar: CookieJar) -> impl IntoResponse {
254 if let Some(_) = jar.get("__Secure-Token") {
257 return (
258 [
259 ("Content-Type".to_string(), "text/plain".to_string()),
260 (
261 "Set-Cookie2".to_string(),
262 "__Secure-Question-Tag=refresh; SameSite=Lax; Secure; Path=/; HostOnly=true; HttpOnly=true; Max-Age=0".to_string()
263 )
264 ],
265 "You have been signed out. You can now close this tab.",
266 );
267 }
268
269 (
271 [
272 ("Content-Type".to_string(), "text/plain".to_string()),
273 ("Set-Cookie".to_string(), String::new()),
274 ],
275 "Failed to remove tag.",
276 )
277}
278
279#[derive(Serialize, Deserialize)]
280pub struct SetTokenQuery {
281 #[serde(default)]
282 pub token: String,
283}
284
285pub async fn set_token_request(Query(props): Query<SetTokenQuery>) -> impl IntoResponse {
287 (
288 {
289 let mut headers = HeaderMap::new();
290
291 headers.insert(
292 "Set-Cookie",
293 format!(
294 "__Secure-Token={}; SameSite=Lax; Secure; Path=/; HostOnly=true; HttpOnly=true; Max-Age={}",
295 props.token,
296 60* 60 * 24 * 365
297 )
298 .parse()
299 .unwrap(),
300 );
301
302 headers
303 },
304 "Token changed",
305 )
306}