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::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 internal_error = |e: &dyn std::fmt::Display| {
92 let msg = format!("{} {}", t!("Database error:"), e);
93 log::error!("{msg}");
94 let error_response = serde_json::json!({ "status": "fail", "message": msg });
95 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
96 };
97
98 let user_id = uuid::Uuid::new_v4();
99 let keypair = server::auth_keys::generate()
103 .await
104 .map_err(|e| internal_error(&e))?;
105 let db_name = server::provision::create_user_database(user_id)
106 .await
107 .map_err(|e| internal_error(&e))?;
108
109 if let Err(e) =
110 server::provision::store_user_private_key(&db_name, &keypair.private_pem_b64).await
111 {
112 if let Err(drop_err) = server::provision::drop_user_database(user_id).await {
115 log::error!(
116 "{} {}",
117 t!("Failed to clean up provisioned database:"),
118 drop_err
119 );
120 }
121 return Err(internal_error(&e));
122 }
123
124 let insert = sqlx::query_as!(
125 User,
126 "INSERT INTO users (id,user_name,email,user_password,db_name,jwt_public_key) VALUES ($1, $2, $3, $4, $5, $6) 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",
127 user_id,
128 body.name.clone(),
129 body.email.clone().to_ascii_lowercase(),
130 hashed_password,
131 db_name,
132 keypair.public_pem_b64
133 )
134 .fetch_one(&mut *conn)
135 .await;
136
137 let user = match insert {
138 Ok(user) => user,
139 Err(e) => {
140 if let Err(drop_err) = server::provision::drop_user_database(user_id).await {
144 log::error!(
145 "{} {}",
146 t!("Failed to clean up provisioned database after registration error:"),
147 drop_err
148 );
149 }
150 let msg = format!("{} {}", t!("Database error:"), e);
151 log::error!("{msg}");
152 let error_response = serde_json::json!({
153 "status": "fail",
154 "message": msg,
155 });
156 return Err((StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)));
157 }
158 };
159
160 let user_response = serde_json::json!({"status": "success","data": serde_json::json!({
161 "user": filter_user_record(&user)
162 })});
163
164 Ok(Json(user_response))
165}
166
167struct LoginCookies {
168 access: Cookie<'static>,
169 refresh: Cookie<'static>,
170 logged_in: Cookie<'static>,
171 access_token: String,
172}
173
174async fn authenticate_user(
175 data: &Arc<AppState>,
176 email: &str,
177 password: &str,
178) -> Result<LoginCookies, (StatusCode, Json<serde_json::Value>)> {
179 let verified = server::command::user::verify_user_password(email, password)
180 .await
181 .map_err(|e| {
182 let msg = format!("{} {}", t!("Database error:"), e);
183 log::error!("{msg}");
184 let error_response = serde_json::json!({
185 "status": "error",
186 "message": msg,
187 });
188 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
189 })?
190 .ok_or_else(|| {
191 let msg = format!("{}", t!("Invalid email or password"));
192 log::error!("{msg}");
193 let error_response = serde_json::json!({
194 "status": "fail",
195 "message": msg,
196 });
197 (StatusCode::BAD_REQUEST, Json(error_response))
198 })?;
199
200 let access_token_details = mint_token(
201 verified.user_id,
202 data.conf.access_token_max_age,
203 crate::token::TokenType::Access,
204 )
205 .await?;
206 let refresh_token_details = mint_token(
207 verified.user_id,
208 data.conf.refresh_token_max_age,
209 crate::token::TokenType::Refresh,
210 )
211 .await?;
212
213 save_token_data_to_redis(data, &access_token_details, data.conf.access_token_max_age).await?;
214 save_token_data_to_redis(
215 data,
216 &refresh_token_details,
217 data.conf.refresh_token_max_age,
218 )
219 .await?;
220
221 let access_cookie = Cookie::build((
222 "access_token",
223 access_token_details.token.clone().unwrap_or_default(),
224 ))
225 .path("/")
226 .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
227 .same_site(SameSite::Lax)
228 .secure(crate::auth_keys::secure_cookies())
229 .http_only(true)
230 .build();
231
232 let refresh_cookie = Cookie::build((
233 "refresh_token",
234 refresh_token_details.token.unwrap_or_default(),
235 ))
236 .path("/")
237 .max_age(time::Duration::minutes(
238 data.conf.refresh_token_max_age * 60,
239 ))
240 .same_site(SameSite::Lax)
241 .secure(crate::auth_keys::secure_cookies())
242 .http_only(true)
243 .build();
244
245 let logged_in_cookie = Cookie::build(("logged_in", "true"))
246 .path("/")
247 .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
248 .same_site(SameSite::Lax)
249 .http_only(false)
250 .build();
251
252 Ok(LoginCookies {
253 access: access_cookie,
254 refresh: refresh_cookie,
255 logged_in: logged_in_cookie,
256 access_token: access_token_details.token.unwrap_or_default(),
257 })
258}
259
260fn build_cookie_headers(cookies: &LoginCookies) -> HeaderMap {
261 let mut headers = HeaderMap::new();
262 headers.append(
263 header::SET_COOKIE,
264 cookies.access.to_string().parse().unwrap(),
265 );
266 headers.append(
267 header::SET_COOKIE,
268 cookies.refresh.to_string().parse().unwrap(),
269 );
270 headers.append(
271 header::SET_COOKIE,
272 cookies.logged_in.to_string().parse().unwrap(),
273 );
274 headers
275}
276
277pub async fn login_user_handler(
278 State(data): State<Arc<AppState>>,
279 Json(body): Json<LoginUserSchema>,
280) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
281 let cookies = authenticate_user(&data, &body.email, &body.password).await?;
282
283 let mut response = Response::new(
284 json!({"status": "success", "access_token": cookies.access_token}).to_string(),
285 );
286 let mut headers = build_cookie_headers(&cookies);
287 headers.append("HX-Redirect", "/".to_string().parse().unwrap());
288 response.headers_mut().extend(headers);
289 Ok(response)
290}
291
292pub async fn login_form_handler(
293 State(data): State<Arc<AppState>>,
294 axum::Form(body): axum::Form<LoginUserSchema>,
295) -> impl IntoResponse {
296 match authenticate_user(&data, &body.email, &body.password).await {
297 Ok(cookies) => {
298 let mut headers = build_cookie_headers(&cookies);
299 headers.insert(header::LOCATION, "/".parse().unwrap());
300 (StatusCode::SEE_OTHER, headers, "").into_response()
301 }
302 Err((status, json_err)) => {
303 let msg = json_err
304 .get("message")
305 .and_then(|v| v.as_str())
306 .unwrap_or("Login failed");
307 (status, msg.to_string()).into_response()
308 }
309 }
310}
311
312pub async fn refresh_access_token_handler(
313 cookie_jar: CookieJar,
314 State(data): State<Arc<AppState>>,
315) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
316 let message = t!("could not refresh access token");
317
318 let refresh_token = cookie_jar
319 .get("refresh_token")
320 .map(|cookie| cookie.value().to_string())
321 .ok_or_else(|| {
322 let error_response = serde_json::json!({
323 "status": "fail",
324 "message": message
325 });
326 (StatusCode::FORBIDDEN, Json(error_response))
327 })?;
328
329 let refresh_token_details =
330 match crate::auth_keys::verify(&refresh_token, crate::token::TokenType::Refresh).await {
331 Some(token_details) => token_details,
332 None => {
333 let error_response = serde_json::json!({
334 "status": "fail",
335 "message": t!("Token is invalid or session has expired")
336 });
337 return Err((StatusCode::UNAUTHORIZED, Json(error_response)));
338 }
339 };
340
341 let mut redis_client = data
342 .redis_client
343 .get_multiplexed_async_connection()
344 .await
345 .map_err(|e| {
346 let msg = format!("{} {}", t!("Redis error:"), e);
347 log::error!("{msg}");
348 let error_response = serde_json::json!({
349 "status": "error",
350 "message": msg,
351 });
352 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
353 })?;
354
355 let redis_token_user_id = redis_client
359 .get_del::<_, Option<String>>(refresh_token_details.token_uuid.to_string())
360 .await
361 .map_err(|e| {
362 let msg = format!("{} {}", t!("Redis error:"), e);
363 log::error!("{msg}");
364 let error_response = serde_json::json!({
365 "status": "error",
366 "message": msg,
367 });
368 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
369 })?
370 .ok_or_else(|| {
371 let msg = format!("{}", t!("Token is invalid or session has expired"));
372 log::error!("{msg}");
373 let error_response = serde_json::json!({
374 "status": "error",
375 "message": msg,
376 });
377 (StatusCode::UNAUTHORIZED, Json(error_response))
378 })?;
379
380 let user_id_uuid = uuid::Uuid::parse_str(&redis_token_user_id).map_err(|_| {
381 let msg = format!("{}", t!("Token is invalid or session has expired"));
382 log::error!("{msg}");
383 let error_response = serde_json::json!({
384 "status": "error",
385 "message": msg,
386 });
387 (StatusCode::UNAUTHORIZED, Json(error_response))
388 })?;
389
390 if user_id_uuid != refresh_token_details.user_id {
392 let error_response = serde_json::json!({
393 "status": "fail",
394 "message": t!("Token is invalid or session has expired")
395 });
396 return Err((StatusCode::UNAUTHORIZED, Json(error_response)));
397 }
398
399 let mut conn = get_connection().await.map_err(|e| {
400 let msg = format!("{} {}", t!("Database error:"), e);
401 log::error!("{msg}");
402 let error_response = serde_json::json!({
403 "status": "fail",
404 "message": msg,
405 });
406 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
407 })?;
408
409 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)
410 .fetch_optional(&mut *conn)
411 .await
412 .map_err(|e| {
413 let msg = format!("{} {}", t!("Error fetching user from database:"), e);
414 log::error!("{msg}");
415 let error_response = serde_json::json!({
416 "status": "fail",
417 "message": msg,
418 });
419 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
420 })?;
421
422 let user = user.ok_or_else(|| {
423 let msg = format!(
424 "{}",
425 t!("The user belonging to this token no longer exists")
426 );
427 log::error!("{msg}");
428 let error_response = serde_json::json!({
429 "status": "fail",
430 "message": msg,
431 });
432 (StatusCode::UNAUTHORIZED, Json(error_response))
433 })?;
434
435 let access_token_details = mint_token(
436 user.id,
437 data.conf.access_token_max_age,
438 crate::token::TokenType::Access,
439 )
440 .await?;
441
442 save_token_data_to_redis(&data, &access_token_details, data.conf.access_token_max_age).await?;
443
444 let refresh_token_details = mint_token(
448 user.id,
449 data.conf.refresh_token_max_age,
450 crate::token::TokenType::Refresh,
451 )
452 .await?;
453
454 save_token_data_to_redis(
455 &data,
456 &refresh_token_details,
457 data.conf.refresh_token_max_age,
458 )
459 .await?;
460
461 let access_cookie = Cookie::build((
462 "access_token",
463 access_token_details.token.clone().unwrap_or_default(),
464 ))
465 .path("/")
466 .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
467 .same_site(SameSite::Lax)
468 .secure(crate::auth_keys::secure_cookies())
469 .http_only(true);
470
471 let refresh_cookie = Cookie::build((
472 "refresh_token",
473 refresh_token_details.token.unwrap_or_default(),
474 ))
475 .path("/")
476 .max_age(time::Duration::minutes(
477 data.conf.refresh_token_max_age * 60,
478 ))
479 .same_site(SameSite::Lax)
480 .secure(crate::auth_keys::secure_cookies())
481 .http_only(true);
482
483 let logged_in_cookie = Cookie::build(("logged_in", "true"))
484 .path("/")
485 .max_age(time::Duration::minutes(data.conf.access_token_max_age * 60))
486 .same_site(SameSite::Lax)
487 .http_only(false);
488
489 let mut response = Response::new(
490 json!({"status": "success", "access_token": access_token_details.token.unwrap()})
491 .to_string(),
492 );
493 let mut headers = HeaderMap::new();
494 headers.append(
495 header::SET_COOKIE,
496 access_cookie.to_string().parse().unwrap(),
497 );
498 headers.append(
499 header::SET_COOKIE,
500 refresh_cookie.to_string().parse().unwrap(),
501 );
502 headers.append(
503 header::SET_COOKIE,
504 logged_in_cookie.to_string().parse().unwrap(),
505 );
506
507 response.headers_mut().extend(headers);
508 Ok(response)
509}
510
511pub async fn logout_handler(
512 cookie_jar: CookieJar,
513 Extension(auth_guard): Extension<JWTAuthMiddleware>,
514 State(data): State<Arc<AppState>>,
515) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
516 let message = t!("Token is invalid or session has expired");
517
518 let refresh_token = cookie_jar
519 .get("refresh_token")
520 .map(|cookie| cookie.value().to_string())
521 .ok_or_else(|| {
522 let error_response = serde_json::json!({
523 "status": "fail",
524 "message": message
525 });
526 (StatusCode::FORBIDDEN, Json(error_response))
527 })?;
528
529 let refresh_token_details =
530 match crate::auth_keys::verify(&refresh_token, crate::token::TokenType::Refresh).await {
531 Some(token_details) => token_details,
532 None => {
533 let error_response = serde_json::json!({
534 "status": "fail",
535 "message": t!("Token is invalid or session has expired")
536 });
537 return Err((StatusCode::UNAUTHORIZED, Json(error_response)));
538 }
539 };
540
541 let mut redis_client = data
542 .redis_client
543 .get_multiplexed_async_connection()
544 .await
545 .map_err(|e| {
546 let msg = format!("{} {}", t!("Redis error:"), e);
547 log::error!("{msg}");
548 let error_response = serde_json::json!({
549 "status": "error",
550 "message": msg,
551 });
552 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
553 })?;
554
555 if refresh_token_details.user_id != auth_guard.user.id {
560 let error_response = serde_json::json!({
561 "status": "fail",
562 "message": t!("Token is invalid or session has expired")
563 });
564 return Err((StatusCode::UNAUTHORIZED, Json(error_response)));
565 }
566
567 let _: bool = redis_client
568 .del(&[
569 refresh_token_details.token_uuid.to_string(),
570 auth_guard.access_token_uuid.to_string(),
571 ])
572 .await
573 .map_err(|e| {
574 let error_response = serde_json::json!({
575 "status": "error",
576 "message": format_args!("{:?}", e)
577 });
578 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
579 })?;
580
581 let access_cookie = Cookie::build(("access_token", ""))
582 .path("/")
583 .max_age(time::Duration::minutes(-1))
584 .same_site(SameSite::Lax)
585 .secure(crate::auth_keys::secure_cookies())
586 .http_only(true);
587 let refresh_cookie = Cookie::build(("refresh_token", ""))
588 .path("/")
589 .max_age(time::Duration::minutes(-1))
590 .same_site(SameSite::Lax)
591 .secure(crate::auth_keys::secure_cookies())
592 .http_only(true);
593
594 let logged_in_cookie = Cookie::build(("logged_in", "true"))
595 .path("/")
596 .max_age(time::Duration::minutes(-1))
597 .same_site(SameSite::Lax)
598 .http_only(false);
599
600 let mut headers = HeaderMap::new();
601 headers.append(
602 header::SET_COOKIE,
603 access_cookie.to_string().parse().unwrap(),
604 );
605 headers.append(
606 header::SET_COOKIE,
607 refresh_cookie.to_string().parse().unwrap(),
608 );
609 headers.append(
610 header::SET_COOKIE,
611 logged_in_cookie.to_string().parse().unwrap(),
612 );
613 headers.append("HX-Redirect", "/".to_string().parse().unwrap());
614
615 let mut response = Response::new(json!({"status": "success"}).to_string());
616 response.headers_mut().extend(headers);
617 Ok(response)
618}
619
620pub async fn get_me_handler(
621 Extension(jwtauth): Extension<JWTAuthMiddleware>,
622) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
623 let json_response = serde_json::json!({
624 "status": "success",
625 "data": serde_json::json!({
626 "user": filter_user_record(&jwtauth.user)
627 })
628 });
629
630 Ok(Json(json_response))
631}
632
633pub async fn get_version() -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
634 if let Some(CmdResult::String(version)) = server::command::config::GetVersion::new()
635 .run()
636 .await
637 .map_err(|_| {
638 (
639 StatusCode::INTERNAL_SERVER_ERROR,
640 Json(json!("Can't get version")),
641 )
642 })?
643 && let Some(CmdResult::String(build_date)) = server::command::config::GetBuildDate::new()
644 .run()
645 .await
646 .map_err(|_| {
647 (
648 StatusCode::INTERNAL_SERVER_ERROR,
649 Json(json!("Can't get version")),
650 )
651 })?
652 {
653 Ok(Response::new(format!(
654 "<span class=\"version\">{version}</span><span class=\"build_date\" data-iso=\"{build_date}\"><script>
655 const el = document.currentScript.parentElement;
656 el.textContent = new Date(el.dataset.iso.trim()).toLocaleString();
657 </script></span>"
658 )))
659 } else {
660 Ok(Response::new("Unversioned".to_string()))
661 }
662}
663
664pub async fn get_logout_link() -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
665 Ok(Response::new(
666 "<a hx-get=\"/api/auth/logout\">Logout</a>".to_string(),
667 ))
668}
669
670pub async fn get_home_link() -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
671 Ok(Response::new("<a href='/'>Back to main</a>".to_string()))
672}
673
674fn filter_user_record(user: &User) -> FilteredUser {
675 FilteredUser {
676 id: user.id.to_string(),
677 email: user.email.clone(),
678 name: user.name.clone(),
679 photo: user.photo.clone(),
680 role: user.role.clone(),
681 verified: user.verified,
682 createdAt: user.created_at.unwrap(),
683 updatedAt: user.updated_at.unwrap(),
684 }
685}
686
687async fn mint_token(
688 user_id: uuid::Uuid,
689 max_age: i64,
690 token_type: crate::token::TokenType,
691) -> Result<TokenDetails, (StatusCode, Json<serde_json::Value>)> {
692 crate::auth_keys::mint(user_id, max_age, token_type)
693 .await
694 .map_err(|e| {
695 let msg = format!("{} {}", t!("error generating token:"), e);
696 log::error!("{msg}");
697 let error_response = serde_json::json!({
698 "status": "error",
699 "message": msg,
700 });
701 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
702 })
703}
704
705async fn save_token_data_to_redis(
706 data: &Arc<AppState>,
707 token_details: &TokenDetails,
708 max_age: i64,
709) -> Result<(), (StatusCode, Json<serde_json::Value>)> {
710 let mut redis_client = data
711 .redis_client
712 .get_multiplexed_async_connection()
713 .await
714 .map_err(|e| {
715 let msg = format!("{} {}", t!("Redis error:"), e);
716 log::error!("{msg}");
717 let error_response = serde_json::json!({
718 "status": "error",
719 "message": msg,
720 });
721 (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
722 })?;
723 let _: bool = redis_client
724 .set_ex(
725 token_details.token_uuid.to_string(),
726 token_details.user_id.to_string(),
727 (max_age * 60) as u64,
728 )
729 .await
730 .map_err(|e| {
731 let error_response = serde_json::json!({
732 "status": "error",
733 "message": format_args!("{}", e),
734 });
735 (StatusCode::UNPROCESSABLE_ENTITY, Json(error_response))
736 })?;
737 Ok(())
738}