1use std::sync::Arc;
2
3use argon2::{Argon2, PasswordHasher};
4use axum::{
5 Extension, Json,
6 extract::State,
7 http::{HeaderMap, Response, StatusCode, header},
8 response::IntoResponse,
9};
10use axum_extra::extract::{
11 CookieJar,
12 cookie::{Cookie, SameSite},
13};
14use serde_json::json;
15
16use crate::{
17 AppState,
18 jwt_auth::JWTAuthMiddleware,
19 model::{LoginUserSchema, RegisterUserSchema, User},
20 response::FilteredUser,
21 token::{self, TokenDetails},
22};
23use rust_i18n::t;
24
25use redis::AsyncCommands;
26use server::{command::CmdResult, db::get_connection};
27
28pub async fn register_user_handler(
29 State(_data): State<Arc<AppState>>,
30 Json(body): Json<RegisterUserSchema>,
31) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
32 let mut conn = get_connection().await.map_err(|e| {
33 let msg = format!("{} {}", t!("Database error:"), e);
34 log::error!("{msg}");
35 let error_response = serde_json::json!({
36 "status": "fail",
37 "message": msg,
38 });
39 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
40 })?;
41
42 let user_exists: Option<bool> =
43 sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM users WHERE email = $1)")
44 .bind(body.email.clone().to_ascii_lowercase())
45 .fetch_one(&mut *conn)
46 .await
47 .map_err(|e| {
48 let msg = format!("{} {}", t!("Database error:"), e);
49 log::error!("{msg}");
50 let error_response = serde_json::json!({
51 "status": "fail",
52 "message": msg,
53 });
54 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
55 })?;
56
57 if let Some(exists) = user_exists
58 && exists
59 {
60 let msg = format!("{}", t!("User with that email already exists"));
61 log::error!("{msg}");
62 let error_response = serde_json::json!({
63 "status": "fail",
64 "message": msg,
65 });
66 return Err((StatusCode::CONFLICT, Json(error_response)));
67 }
68
69 let hashed_password = Argon2::default()
70 .hash_password(body.password.as_bytes())
71 .map_err(|e| {
72 let msg = format!("{} {}", t!("Error while hashing password:"), e);
73 log::error!("{msg}");
74 let error_response = serde_json::json!({
75 "status": "fail",
76 "message": msg,
77 });
78 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
79 })
80 .map(|hash| hash.to_string())?;
81 let mut conn = get_connection().await.map_err(|e| {
82 let msg = format!("{} {}", t!("Database error:"), e);
83 log::error!("{msg}");
84 let error_response = serde_json::json!({
85 "status": "fail",
86 "message": msg,
87 });
88 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
89 })?;
90
91 let user = sqlx::query_as!(
92 User,
93 "INSERT INTO users (user_name,email,user_password) VALUES ($1, $2, $3) RETURNING id, user_name as name, email, user_password as password, user_role as role, photo, verified, db_name as database, created_at, updated_at",
94 body.name.clone(),
95 body.email.clone().to_ascii_lowercase(),
96 hashed_password
97 )
98 .fetch_one(&mut *conn)
99 .await
100 .map_err(|e| {
101 let msg = format!("{} {}", t!("Database error:"), e);
102 log::error!("{msg}");
103 let error_response = serde_json::json!({
104 "status": "fail",
105 "message": msg,
106 });
107 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
108 })?;
109
110 let user_response = serde_json::json!({"status": "success","data": serde_json::json!({
111 "user": filter_user_record(&user)
112 })});
113
114 Ok(Json(user_response))
115}
116
117struct LoginCookies {
118 access: Cookie<'static>,
119 refresh: Cookie<'static>,
120 logged_in: Cookie<'static>,
121 access_token: String,
122}
123
124async fn authenticate_user(
125 data: &Arc<AppState>,
126 email: &str,
127 password: &str,
128) -> Result<LoginCookies, (StatusCode, Json<serde_json::Value>)> {
129 let verified = server::command::user::verify_user_password(email, password)
130 .await
131 .map_err(|e| {
132 let msg = format!("{} {}", t!("Database error:"), e);
133 log::error!("{msg}");
134 let error_response = serde_json::json!({
135 "status": "error",
136 "message": msg,
137 });
138 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
139 })?
140 .ok_or_else(|| {
141 let msg = format!("{}", t!("Invalid email or password"));
142 log::error!("{msg}");
143 let error_response = serde_json::json!({
144 "status": "fail",
145 "message": msg,
146 });
147 (StatusCode::BAD_REQUEST, Json(error_response))
148 })?;
149
150 let access_token_details = generate_token(
151 verified.user_id,
152 data.conf.access_token_max_age,
153 &data.conf.access_token_private_key,
154 )?;
155 let refresh_token_details = generate_token(
156 verified.user_id,
157 data.conf.refresh_token_max_age,
158 &data.conf.refresh_token_private_key,
159 )?;
160
161 save_token_data_to_redis(data, &access_token_details, data.conf.access_token_max_age).await?;
162 save_token_data_to_redis(
163 data,
164 &refresh_token_details,
165 data.conf.refresh_token_max_age,
166 )
167 .await?;
168
169 let access_cookie = Cookie::build((
170 "access_token",
171 access_token_details.token.clone().unwrap_or_default(),
172 ))
173 .path("/")
174 .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
175 .same_site(SameSite::Lax)
176 .http_only(true)
177 .build();
178
179 let refresh_cookie = Cookie::build((
180 "refresh_token",
181 refresh_token_details.token.unwrap_or_default(),
182 ))
183 .path("/")
184 .max_age(time::Duration::minutes(
185 data.conf.refresh_token_max_age * 60,
186 ))
187 .same_site(SameSite::Lax)
188 .http_only(true)
189 .build();
190
191 let logged_in_cookie = Cookie::build(("logged_in", "true"))
192 .path("/")
193 .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
194 .same_site(SameSite::Lax)
195 .http_only(false)
196 .build();
197
198 Ok(LoginCookies {
199 access: access_cookie,
200 refresh: refresh_cookie,
201 logged_in: logged_in_cookie,
202 access_token: access_token_details.token.unwrap_or_default(),
203 })
204}
205
206fn build_cookie_headers(cookies: &LoginCookies) -> HeaderMap {
207 let mut headers = HeaderMap::new();
208 headers.append(
209 header::SET_COOKIE,
210 cookies.access.to_string().parse().unwrap(),
211 );
212 headers.append(
213 header::SET_COOKIE,
214 cookies.refresh.to_string().parse().unwrap(),
215 );
216 headers.append(
217 header::SET_COOKIE,
218 cookies.logged_in.to_string().parse().unwrap(),
219 );
220 headers
221}
222
223pub async fn login_user_handler(
224 State(data): State<Arc<AppState>>,
225 Json(body): Json<LoginUserSchema>,
226) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
227 let cookies = authenticate_user(&data, &body.email, &body.password).await?;
228
229 let mut response = Response::new(
230 json!({"status": "success", "access_token": cookies.access_token}).to_string(),
231 );
232 let mut headers = build_cookie_headers(&cookies);
233 headers.append("HX-Redirect", "/".to_string().parse().unwrap());
234 response.headers_mut().extend(headers);
235 Ok(response)
236}
237
238pub async fn login_form_handler(
239 State(data): State<Arc<AppState>>,
240 axum::Form(body): axum::Form<LoginUserSchema>,
241) -> impl IntoResponse {
242 match authenticate_user(&data, &body.email, &body.password).await {
243 Ok(cookies) => {
244 let mut headers = build_cookie_headers(&cookies);
245 headers.insert(header::LOCATION, "/".parse().unwrap());
246 (StatusCode::SEE_OTHER, headers, "").into_response()
247 }
248 Err((status, json_err)) => {
249 let msg = json_err
250 .get("message")
251 .and_then(|v| v.as_str())
252 .unwrap_or("Login failed");
253 (status, msg.to_string()).into_response()
254 }
255 }
256}
257
258pub async fn refresh_access_token_handler(
259 cookie_jar: CookieJar,
260 State(data): State<Arc<AppState>>,
261) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
262 let message = t!("could not refresh access token");
263
264 let refresh_token = cookie_jar
265 .get("refresh_token")
266 .map(|cookie| cookie.value().to_string())
267 .ok_or_else(|| {
268 let error_response = serde_json::json!({
269 "status": "fail",
270 "message": message
271 });
272 (StatusCode::FORBIDDEN, Json(error_response))
273 })?;
274
275 let refresh_token_details =
276 match token::verify_jwt_token(&data.conf.refresh_token_public_key, &refresh_token) {
277 Ok(token_details) => token_details,
278 Err(e) => {
279 let error_response = serde_json::json!({
280 "status": "fail",
281 "message": format_args!("{:?}", e)
282 });
283 return Err((StatusCode::UNAUTHORIZED, Json(error_response)));
284 }
285 };
286
287 let mut redis_client = data
288 .redis_client
289 .get_multiplexed_async_connection()
290 .await
291 .map_err(|e| {
292 let msg = format!("{} {}", t!("Redis error:"), e);
293 log::error!("{msg}");
294 let error_response = serde_json::json!({
295 "status": "error",
296 "message": msg,
297 });
298 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
299 })?;
300
301 let redis_token_user_id = redis_client
302 .get::<_, String>(refresh_token_details.token_uuid.to_string())
303 .await
304 .map_err(|_| {
305 let msg = format!("{}", t!("Token is invalid or session has expired"));
306 log::error!("{msg}");
307 let error_response = serde_json::json!({
308 "status": "error",
309 "message": msg,
310 });
311 (StatusCode::UNAUTHORIZED, Json(error_response))
312 })?;
313
314 let user_id_uuid = uuid::Uuid::parse_str(&redis_token_user_id).map_err(|_| {
315 let msg = format!("{}", t!("Token is invalid or session has expired"));
316 log::error!("{msg}");
317 let error_response = serde_json::json!({
318 "status": "error",
319 "message": msg,
320 });
321 (StatusCode::UNAUTHORIZED, Json(error_response))
322 })?;
323
324 let mut conn = get_connection().await.map_err(|e| {
325 let msg = format!("{} {}", t!("Database error:"), e);
326 log::error!("{msg}");
327 let error_response = serde_json::json!({
328 "status": "fail",
329 "message": msg,
330 });
331 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
332 })?;
333
334 let user = sqlx::query_as!(User, "SELECT id, user_name as name, email, user_password as password, user_role as role, photo, verified, db_name as database, created_at, updated_at FROM users WHERE id = $1", user_id_uuid)
335 .fetch_optional(&mut *conn)
336 .await
337 .map_err(|e| {
338 let msg = format!("{} {}", t!("Error fetching user from database:"), e);
339 log::error!("{msg}");
340 let error_response = serde_json::json!({
341 "status": "fail",
342 "message": msg,
343 });
344 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
345 })?;
346
347 let user = user.ok_or_else(|| {
348 let msg = format!(
349 "{}",
350 t!("The user belonging to this token no longer exists")
351 );
352 log::error!("{msg}");
353 let error_response = serde_json::json!({
354 "status": "fail",
355 "message": msg,
356 });
357 (StatusCode::UNAUTHORIZED, Json(error_response))
358 })?;
359
360 let access_token_details = generate_token(
361 user.id,
362 data.conf.access_token_max_age,
363 &data.conf.access_token_private_key,
364 )?;
365
366 save_token_data_to_redis(&data, &access_token_details, data.conf.access_token_max_age).await?;
367
368 let access_cookie = Cookie::build((
369 "access_token",
370 access_token_details.token.clone().unwrap_or_default(),
371 ))
372 .path("/")
373 .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
374 .same_site(SameSite::Lax)
375 .http_only(true);
376
377 let logged_in_cookie = Cookie::build(("logged_in", "true"))
378 .path("/")
379 .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
380 .same_site(SameSite::Lax)
381 .http_only(false);
382
383 let mut response = Response::new(
384 json!({"status": "success", "access_token": access_token_details.token.unwrap()})
385 .to_string(),
386 );
387 let mut headers = HeaderMap::new();
388 headers.append(
389 header::SET_COOKIE,
390 access_cookie.to_string().parse().unwrap(),
391 );
392 headers.append(
393 header::SET_COOKIE,
394 logged_in_cookie.to_string().parse().unwrap(),
395 );
396
397 response.headers_mut().extend(headers);
398 Ok(response)
399}
400
401pub async fn logout_handler(
402 cookie_jar: CookieJar,
403 Extension(auth_guard): Extension<JWTAuthMiddleware>,
404 State(data): State<Arc<AppState>>,
405) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
406 let message = t!("Token is invalid or session has expired");
407
408 let refresh_token = cookie_jar
409 .get("refresh_token")
410 .map(|cookie| cookie.value().to_string())
411 .ok_or_else(|| {
412 let error_response = serde_json::json!({
413 "status": "fail",
414 "message": message
415 });
416 (StatusCode::FORBIDDEN, Json(error_response))
417 })?;
418
419 let refresh_token_details =
420 match token::verify_jwt_token(&data.conf.refresh_token_public_key, &refresh_token) {
421 Ok(token_details) => token_details,
422 Err(e) => {
423 let error_response = serde_json::json!({
424 "status": "fail",
425 "message": format_args!("{:?}", e)
426 });
427 return Err((StatusCode::UNAUTHORIZED, Json(error_response)));
428 }
429 };
430
431 let mut redis_client = data
432 .redis_client
433 .get_multiplexed_async_connection()
434 .await
435 .map_err(|e| {
436 let msg = format!("{} {}", t!("Redis error:"), e);
437 log::error!("{msg}");
438 let error_response = serde_json::json!({
439 "status": "error",
440 "message": msg,
441 });
442 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
443 })?;
444
445 let _: bool = redis_client
446 .del(&[
447 refresh_token_details.token_uuid.to_string(),
448 auth_guard.access_token_uuid.to_string(),
449 ])
450 .await
451 .map_err(|e| {
452 let error_response = serde_json::json!({
453 "status": "error",
454 "message": format_args!("{:?}", e)
455 });
456 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
457 })?;
458
459 let access_cookie = Cookie::build(("access_token", ""))
460 .path("/")
461 .max_age(time::Duration::minutes(-1))
462 .same_site(SameSite::Lax)
463 .http_only(true);
464 let refresh_cookie = Cookie::build(("refresh_token", ""))
465 .path("/")
466 .max_age(time::Duration::minutes(-1))
467 .same_site(SameSite::Lax)
468 .http_only(true);
469
470 let logged_in_cookie = Cookie::build(("logged_in", "true"))
471 .path("/")
472 .max_age(time::Duration::minutes(-1))
473 .same_site(SameSite::Lax)
474 .http_only(false);
475
476 let mut headers = HeaderMap::new();
477 headers.append(
478 header::SET_COOKIE,
479 access_cookie.to_string().parse().unwrap(),
480 );
481 headers.append(
482 header::SET_COOKIE,
483 refresh_cookie.to_string().parse().unwrap(),
484 );
485 headers.append(
486 header::SET_COOKIE,
487 logged_in_cookie.to_string().parse().unwrap(),
488 );
489 headers.append("HX-Redirect", "/".to_string().parse().unwrap());
490
491 let mut response = Response::new(json!({"status": "success"}).to_string());
492 response.headers_mut().extend(headers);
493 Ok(response)
494}
495
496pub async fn get_me_handler(
497 Extension(jwtauth): Extension<JWTAuthMiddleware>,
498) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
499 let json_response = serde_json::json!({
500 "status": "success",
501 "data": serde_json::json!({
502 "user": filter_user_record(&jwtauth.user)
503 })
504 });
505
506 Ok(Json(json_response))
507}
508
509pub async fn get_version() -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
510 if let Some(CmdResult::String(version)) = server::command::config::GetVersion::new()
511 .run()
512 .await
513 .map_err(|_| {
514 (
515 StatusCode::INTERNAL_SERVER_ERROR,
516 Json(json!("Can't get version")),
517 )
518 })?
519 && let Some(CmdResult::String(build_date)) = server::command::config::GetBuildDate::new()
520 .run()
521 .await
522 .map_err(|_| {
523 (
524 StatusCode::INTERNAL_SERVER_ERROR,
525 Json(json!("Can't get version")),
526 )
527 })?
528 {
529 Ok(Response::new(format!(
530 "<span class=\"version\">{version}</span><span class=\"build_date\" data-iso=\"{build_date}\"><script>
531 const el = document.currentScript.parentElement;
532 el.textContent = new Date(el.dataset.iso.trim()).toLocaleString();
533 </script></span>"
534 )))
535 } else {
536 Ok(Response::new("Unversioned".to_string()))
537 }
538}
539
540pub async fn get_logout_link() -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
541 Ok(Response::new(
542 "<a hx-get=\"/api/auth/logout\">Logout</a>".to_string(),
543 ))
544}
545
546pub async fn get_home_link() -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
547 Ok(Response::new("<a href='/'>Back to main</a>".to_string()))
548}
549
550fn filter_user_record(user: &User) -> FilteredUser {
551 FilteredUser {
552 id: user.id.to_string(),
553 email: user.email.clone(),
554 name: user.name.clone(),
555 photo: user.photo.clone(),
556 role: user.role.clone(),
557 verified: user.verified,
558 createdAt: user.created_at.unwrap(),
559 updatedAt: user.updated_at.unwrap(),
560 }
561}
562
563fn generate_token(
564 user_id: uuid::Uuid,
565 max_age: i64,
566 private_key: &str,
567) -> Result<TokenDetails, (StatusCode, Json<serde_json::Value>)> {
568 token::generate_jwt_token(user_id, max_age, private_key).map_err(|e| {
569 let msg = format!("{} {}", t!("error generating token:"), e);
570 log::error!("{msg}");
571 let error_response = serde_json::json!({
572 "status": "error",
573 "message": msg,
574 });
575 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
576 })
577}
578
579async fn save_token_data_to_redis(
580 data: &Arc<AppState>,
581 token_details: &TokenDetails,
582 max_age: i64,
583) -> Result<(), (StatusCode, Json<serde_json::Value>)> {
584 let mut redis_client = data
585 .redis_client
586 .get_multiplexed_async_connection()
587 .await
588 .map_err(|e| {
589 let msg = format!("{} {}", t!("Redis error:"), e);
590 log::error!("{msg}");
591 let error_response = serde_json::json!({
592 "status": "error",
593 "message": msg,
594 });
595 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
596 })?;
597 let _: bool = redis_client
598 .set_ex(
599 token_details.token_uuid.to_string(),
600 token_details.user_id.to_string(),
601 (max_age * 60) as u64,
602 )
603 .await
604 .map_err(|e| {
605 let error_response = serde_json::json!({
606 "status": "error",
607 "message": format_args!("{}", e),
608 });
609 (StatusCode::UNPROCESSABLE_ENTITY, Json(error_response))
610 })?;
611 Ok(())
612}