1use axum::{Json, extract::State, http::StatusCode};
5
6use crate::db::queries::auth;
7use crate::models::user::User;
8use crate::models::user::{
9 AuthTokenResponse, DeleteAccountRequest, GetUserDataRequest, LoginRequest, SignupRequest,
10 TokenResponse, UpdateUserProfileRequest, UserDataResponse,
11};
12use crate::utility::auth_common::generate_backup_codes;
13use quorum_core::db::DB;
14use quorum_core::db::queries::audit_logs::log_audit_event;
15use quorum_core::models::server::AuditEvent;
16use quorum_core::utility::config::Config;
17
18use chrono;
19use serde::{Deserialize, Serialize};
20
21#[derive(Debug, Deserialize)]
22pub struct RefreshTokenRequest {
23 pub user_id: String,
24 pub refresh_token: String,
25}
26
27#[derive(Debug, Serialize)]
28pub struct SignupResponse {
29 #[serde(flatten)]
30 pub auth: AuthTokenResponse,
31
32 pub backup_codes: Option<Vec<String>>,
33}
34
35async fn verify_user_credentials(
46 db: &DB,
47 username_or_email: &str,
48 password: &str,
49) -> Result<User, (StatusCode, String)> {
50 auth::verify_user_credentials(db, username_or_email, password)
51 .await
52 .map_err(|_| {
53 (
54 StatusCode::UNAUTHORIZED,
55 "Invalid username/email or password".to_string(),
56 )
57 })
58}
59
60fn extract_user_id(user: &User) -> Result<String, String> {
69 match &user.id.key {
70 surrealdb_types::RecordIdKey::String(s) => Ok(s.to_string()),
71 surrealdb_types::RecordIdKey::Number(n) => Ok(n.to_string()),
72 _ => Err("Invalid user ID format".to_string()),
73 }
74}
75
76pub async fn signup(
99 State(db): State<DB>,
100 Json(payload): Json<SignupRequest>,
101) -> (StatusCode, Json<SignupResponse>) {
102 let mut plain_backup_codes = None;
103 let mut hash_backup_codes = None;
104
105 if payload.email.is_none() {
106 let backup_codes = Some(generate_backup_codes());
107
108 plain_backup_codes = Some(
109 backup_codes
110 .as_ref()
111 .unwrap()
112 .iter()
113 .filter_map(|code| code.plain.clone())
114 .collect::<Vec<String>>(),
115 );
116 hash_backup_codes = Some(
117 backup_codes
118 .as_ref()
119 .unwrap()
120 .iter()
121 .map(|code| code.hash.clone())
122 .collect::<Vec<String>>(),
123 );
124 }
125
126 let signup_result = auth::signup_user(
127 &db,
128 &payload.username,
129 payload.email.as_deref(),
130 &payload.password,
131 hash_backup_codes.clone(),
132 )
133 .await;
134
135 if let Err(e) = signup_result {
136 let message = e.to_string();
137 let formatted = format!("Failed to create user: {}", message);
138
139 let status = if message.contains("Invalid password length")
140 || message.contains("idx_username")
141 || message.contains("idx_email")
142 {
143 StatusCode::BAD_REQUEST
144 } else if message.contains("idx_username") || message.contains("UNIQUE") {
145 StatusCode::CONFLICT
146 } else {
147 StatusCode::INTERNAL_SERVER_ERROR
148 };
149
150 let _ = log_audit_event(
151 &db,
152 AuditEvent {
153 log_type: "signup_failed".to_string(),
154 action: Some(message.clone()),
155 ..Default::default()
156 },
157 )
158 .await;
159
160 return (
161 status,
162 Json(SignupResponse {
163 auth: AuthTokenResponse {
164 success: false,
165 user: None,
166 tokens: None,
167 message: formatted,
168 },
169 backup_codes: None,
170 }),
171 );
172 }
173
174 let user = signup_result.unwrap();
175
176 let user_id = match extract_user_id(&user) {
177 Ok(id) => id,
178 Err(e) => {
179 return (
180 StatusCode::INTERNAL_SERVER_ERROR,
181 Json(SignupResponse {
182 auth: AuthTokenResponse {
183 success: false,
184 user: None,
185 tokens: None,
186 message: e,
187 },
188 backup_codes: None,
189 }),
190 );
191 }
192 };
193
194 let access_token = match crate::utility::jwt::generate_access_token(&user_id, &user.username) {
195 Ok(token) => token,
196 Err(_) => {
197 return (
198 StatusCode::INTERNAL_SERVER_ERROR,
199 Json(SignupResponse {
200 auth: AuthTokenResponse {
201 success: false,
202 user: None,
203 tokens: None,
204 message: "Failed to generate access token".to_string(),
205 },
206 backup_codes: None,
207 }),
208 );
209 }
210 };
211
212 let refresh_token = match crate::utility::jwt::generate_refresh_token(&user_id, &user.username)
213 {
214 Ok(token) => token,
215 Err(_) => {
216 return (
217 StatusCode::INTERNAL_SERVER_ERROR,
218 Json(SignupResponse {
219 auth: AuthTokenResponse {
220 success: false,
221 user: None,
222 tokens: None,
223 message: "Failed to generate refresh token".to_string(),
224 },
225 backup_codes: None,
226 }),
227 );
228 }
229 };
230
231 let config = quorum_core::utility::config::Config::get();
232 let expires_at = chrono::Utc::now().timestamp() + (config.jwt_refresh_days * 86400);
233
234 if auth::store_refresh_token(&db, &user_id, &refresh_token, expires_at)
235 .await
236 .is_err()
237 {
238 return (
239 StatusCode::INTERNAL_SERVER_ERROR,
240 Json(SignupResponse {
241 auth: AuthTokenResponse {
242 success: false,
243 user: None,
244 tokens: None,
245 message: "Failed to store refresh token".to_string(),
246 },
247 backup_codes: None,
248 }),
249 );
250 }
251
252 let _ = log_audit_event(
253 &db,
254 AuditEvent {
255 log_type: "signup_success".to_string(),
256 target_type_table: Some("users".to_string()),
257 target_type_table_id: Some(user_id.clone()),
258 user_id: Some(user_id.clone()),
259 ..Default::default()
260 },
261 )
262 .await;
263
264 (
265 StatusCode::CREATED,
266 Json(SignupResponse {
267 auth: AuthTokenResponse {
268 success: true,
269 user: Some(user.to_response()),
270 tokens: Some(TokenResponse {
271 access_token,
272 refresh_token,
273 expires_in: quorum_core::utility::config::Config::get().jwt_access_minutes * 60,
274 }),
275 message: "User created successfully".to_string(),
276 },
277 backup_codes: plain_backup_codes,
278 }),
279 )
280}
281
282pub async fn login(
303 State(db): State<DB>,
304 Json(payload): Json<LoginRequest>,
305) -> (StatusCode, Json<AuthTokenResponse>) {
306 let user =
307 match verify_user_credentials(&db, &payload.username_or_email, &payload.password).await {
308 Ok(user) => user,
309 Err((status, message)) => {
310 return (
311 status,
312 Json(AuthTokenResponse {
313 success: false,
314 user: None,
315 tokens: None,
316 message,
317 }),
318 );
319 }
320 };
321
322 let user_id = match extract_user_id(&user) {
323 Ok(id) => id,
324 Err(e) => {
325 let _ = log_audit_event(
326 &db,
327 AuditEvent {
328 log_type: "login_failed".to_string(),
329 action: Some(e.clone()),
330 ..Default::default()
331 },
332 )
333 .await;
334
335 return (
336 StatusCode::INTERNAL_SERVER_ERROR,
337 Json(AuthTokenResponse {
338 success: false,
339 user: None,
340 tokens: None,
341 message: e,
342 }),
343 );
344 }
345 };
346
347 let access_token = match crate::utility::jwt::generate_access_token(&user_id, &user.username) {
348 Ok(token) => token,
349 Err(_) => {
350 return (
351 StatusCode::INTERNAL_SERVER_ERROR,
352 Json(AuthTokenResponse {
353 success: false,
354 user: None,
355 tokens: None,
356 message: "Failed to generate access token".to_string(),
357 }),
358 );
359 }
360 };
361
362 let refresh_token = match crate::utility::jwt::generate_refresh_token(&user_id, &user.username)
363 {
364 Ok(token) => token,
365 Err(_) => {
366 return (
367 StatusCode::INTERNAL_SERVER_ERROR,
368 Json(AuthTokenResponse {
369 success: false,
370 user: None,
371 tokens: None,
372 message: "Failed to generate refresh token".to_string(),
373 }),
374 );
375 }
376 };
377
378 let expires_at = chrono::Utc::now().timestamp() + (Config::get().jwt_refresh_days * 86400);
379 if auth::store_refresh_token(&db, &user_id, &refresh_token, expires_at)
380 .await
381 .is_err()
382 {
383 return (
384 StatusCode::INTERNAL_SERVER_ERROR,
385 Json(AuthTokenResponse {
386 success: false,
387 user: None,
388 tokens: None,
389 message: "Failed to store refresh token".to_string(),
390 }),
391 );
392 }
393
394 let _ = log_audit_event(
395 &db,
396 AuditEvent {
397 log_type: "login_success".to_string(),
398 target_type_table: Some("users".to_string()),
399 target_type_table_id: Some(user_id.clone()),
400 user_id: Some(user_id.clone()),
401 ..Default::default()
402 },
403 )
404 .await;
405
406 (
407 StatusCode::OK,
408 Json(AuthTokenResponse {
409 success: true,
410 user: Some(user.to_response()),
411 tokens: Some(TokenResponse {
412 access_token,
413 refresh_token,
414 expires_in: Config::get().jwt_access_minutes * 60,
415 }),
416 message: "Login successful".to_string(),
417 }),
418 )
419}
420
421pub async fn delete_account(
439 State(db): State<DB>,
440 Json(payload): Json<DeleteAccountRequest>,
441) -> (StatusCode, Json<AuthTokenResponse>) {
442 let _user =
443 match auth::verify_user_credentials(&db, &payload.username_or_email, &payload.password)
444 .await
445 {
446 Ok(user) => user,
447 Err((status, message)) => {
448 let _ = log_audit_event(
449 &db,
450 AuditEvent {
451 log_type: "delete_account_failed".to_string(),
452 action: Some(message.clone()),
453 target_type_table: Some("users".to_string()),
454 target_type_table_id: Some(payload.user_id.clone()),
455 ..Default::default()
456 },
457 )
458 .await;
459
460 return (
461 status,
462 Json(AuthTokenResponse {
463 success: false,
464 user: None,
465 tokens: None,
466 message,
467 }),
468 );
469 }
470 };
471
472 if auth::delete_user_by_id(&db, &payload.user_id)
473 .await
474 .is_err()
475 {
476 return (
477 StatusCode::INTERNAL_SERVER_ERROR,
478 Json(AuthTokenResponse {
479 success: false,
480 user: None,
481 tokens: None,
482 message: "Failed to delete account".to_string(),
483 }),
484 );
485 }
486
487 let _ = log_audit_event(
488 &db,
489 AuditEvent {
490 log_type: "delete_account_success".to_string(),
491 ..Default::default()
492 },
493 )
494 .await;
495
496 (
497 StatusCode::OK,
498 Json(AuthTokenResponse {
499 success: true,
500 user: None,
501 tokens: None,
502 message: "Account deleted successfully".to_string(),
503 }),
504 )
505}
506
507pub async fn get_user_data(
526 State(db): State<DB>,
527 Json(payload): Json<GetUserDataRequest>,
528) -> (StatusCode, Json<UserDataResponse>) {
529 let user =
530 match verify_user_credentials(&db, &payload.username_or_email, &payload.password).await {
531 Ok(user) => user,
532 Err((status, message)) => {
533 let _ = log_audit_event(
534 &db,
535 AuditEvent {
536 log_type: "get_user_data_failed".to_string(),
537 action: Some(message.clone()),
538 target_type_table: Some("users".to_string()),
539 target_type_table_id: Some(payload.username_or_email.clone()),
540 user_id: Some(payload.user_id),
541 ..Default::default()
542 },
543 )
544 .await;
545
546 return (
547 status,
548 Json(UserDataResponse {
549 success: false,
550 data: None,
551 message,
552 }),
553 );
554 }
555 };
556
557 let mut user_data = serde_json::Map::new();
558
559 for field in &payload.fields {
560 match field.as_str() {
561 "id" => {
562 user_data.insert(
563 "id".to_string(),
564 serde_json::Value::String(format!("{:?}", user.id.key)),
565 );
566 }
567 "username" => {
568 user_data.insert(
569 "username".to_string(),
570 serde_json::Value::String(user.username.clone()),
571 );
572 }
573 "email" => {
574 if let Some(email) = &user.email {
575 user_data.insert(
576 "email".to_string(),
577 serde_json::Value::String(email.clone()),
578 );
579 }
580 }
581 "created_at" => {
582 user_data.insert(
583 "created_at".to_string(),
584 serde_json::Value::String(user.created_at.to_string()),
585 );
586 }
587 _ => {}
588 }
589 }
590
591 let _ = log_audit_event(
592 &db,
593 AuditEvent {
594 log_type: "get_user_data_success".to_string(),
595 target_type_table: Some("users".to_string()),
596 target_type_table_id: Some(payload.user_id.clone()),
597 user_id: Some(payload.user_id),
598 ..Default::default()
599 },
600 )
601 .await;
602
603 (
604 StatusCode::OK,
605 Json(UserDataResponse {
606 success: true,
607 data: Some(user_data),
608 message: "User data retrieved successfully".to_string(),
609 }),
610 )
611}
612
613pub async fn refresh_token(
634 State(db): State<DB>,
635 Json(payload): Json<RefreshTokenRequest>,
636) -> (StatusCode, Json<AuthTokenResponse>) {
637 let claims = match crate::utility::jwt::verify_token(&payload.refresh_token) {
639 Ok(claims) => {
640 if claims.token_type != "refresh" {
641 return (
642 StatusCode::UNAUTHORIZED,
643 Json(AuthTokenResponse {
644 success: false,
645 user: None,
646 tokens: None,
647 message: "Invalid token type".to_string(),
648 }),
649 );
650 }
651 claims
652 }
653 Err(_) => {
654 let user_id = payload.user_id.clone();
655
656 let _ = log_audit_event(
657 &db,
658 AuditEvent {
659 log_type: "refresh_token_failed".to_string(),
660 action: Some("Invalid or expired refresh token".to_string()),
661 target_type_table_id: Some(user_id.clone()),
662 user_id: Some(user_id),
663 ..Default::default()
664 },
665 )
666 .await;
667
668 return (
669 StatusCode::UNAUTHORIZED,
670 Json(AuthTokenResponse {
671 success: false,
672 user: None,
673 tokens: None,
674 message: "Invalid or expired refresh token".to_string(),
675 }),
676 );
677 }
678 };
679
680 let user_id = claims.sub.clone();
681
682 if auth::validate_refresh_token(&db, &user_id, &payload.refresh_token)
683 .await
684 .is_err()
685 {
686 let _ = log_audit_event(
687 &db,
688 AuditEvent {
689 log_type: "refresh_token_failed".to_string(),
690 action: Some("Refresh token not found or revoked".to_string()),
691 target_type_table: Some("users".to_string()),
692 target_type_table_id: Some(user_id.clone()),
693 user_id: Some(user_id.clone()),
694 ..Default::default()
695 },
696 )
697 .await;
698
699 return (
700 StatusCode::UNAUTHORIZED,
701 Json(AuthTokenResponse {
702 success: false,
703 user: None,
704 tokens: None,
705 message: "Invalid or expired refresh token".to_string(),
706 }),
707 );
708 }
709
710 let access_token =
712 match crate::utility::jwt::generate_access_token(&claims.sub, &claims.username) {
713 Ok(token) => token,
714 Err(_) => {
715 return (
716 StatusCode::INTERNAL_SERVER_ERROR,
717 Json(AuthTokenResponse {
718 success: false,
719 user: None,
720 tokens: None,
721 message: "Failed to generate access token".to_string(),
722 }),
723 );
724 }
725 };
726
727 let _ = log_audit_event(
728 &db,
729 AuditEvent {
730 log_type: "refresh_token_success".to_string(),
731 target_type_table: Some("users".to_string()),
732 target_type_table_id: Some(user_id.clone()),
733 user_id: Some(user_id),
734 ..Default::default()
735 },
736 )
737 .await;
738
739 (
740 StatusCode::OK,
741 Json(AuthTokenResponse {
742 success: true,
743 user: None,
744 tokens: Some(TokenResponse {
745 access_token,
746 refresh_token: payload.refresh_token,
747 expires_in: Config::get().jwt_access_minutes * 60,
748 }),
749 message: "Token refreshed successfully".to_string(),
750 }),
751 )
752}
753
754pub async fn logout(
775 State(db): State<DB>,
776 Json(payload): Json<RefreshTokenRequest>,
777) -> (StatusCode, Json<AuthTokenResponse>) {
778 if crate::utility::jwt::verify_token(&payload.refresh_token).is_err() {
779 let user_id = payload.user_id.clone();
780
781 let _ = log_audit_event(
782 &db,
783 AuditEvent {
784 log_type: "logout_failed".to_string(),
785 action: Some("Invalid or expired refresh token".to_string()),
786 user_id: Some(user_id),
787 ..Default::default()
788 },
789 )
790 .await;
791
792 return (
793 StatusCode::UNAUTHORIZED,
794 Json(AuthTokenResponse {
795 success: false,
796 user: None,
797 tokens: None,
798 message: "Invalid or expired refresh token".to_string(),
799 }),
800 );
801 }
802
803 if auth::revoke_refresh_token(&db, &payload.refresh_token)
804 .await
805 .is_err()
806 {
807 let user_id = payload.user_id.clone();
808
809 let _ = log_audit_event(
810 &db,
811 AuditEvent {
812 log_type: "logout_failed".to_string(),
813 action: Some("Failed to revoke refresh token".to_string()),
814 user_id: Some(user_id),
815 ..Default::default()
816 },
817 )
818 .await;
819
820 return (
821 StatusCode::INTERNAL_SERVER_ERROR,
822 Json(AuthTokenResponse {
823 success: false,
824 user: None,
825 tokens: None,
826 message: "Failed to logout".to_string(),
827 }),
828 );
829 }
830
831 let _ = log_audit_event(
832 &db,
833 AuditEvent {
834 log_type: "logout_success".to_string(),
835 user_id: Some(payload.user_id),
836 ..Default::default()
837 },
838 )
839 .await;
840
841 (
842 StatusCode::OK,
843 Json(AuthTokenResponse {
844 success: true,
845 user: None,
846 tokens: None,
847 message: "Logged out successfully".to_string(),
848 }),
849 )
850}
851
852pub async fn update_user_profile(
872 State(db): State<DB>,
873 Json(payload): Json<UpdateUserProfileRequest>,
874) -> (StatusCode, Json<AuthTokenResponse>) {
875 if payload.username.trim().is_empty() {
876 return (
877 StatusCode::BAD_REQUEST,
878 Json(AuthTokenResponse {
879 success: false,
880 user: None,
881 tokens: None,
882 message: "Username cannot be empty".to_string(),
883 }),
884 );
885 }
886
887 let user = match auth::update_user_profile(&db, &payload).await {
888 Ok(user) => user,
889 Err((status, message)) => {
890 let _ = log_audit_event(
891 &db,
892 AuditEvent {
893 log_type: "update_user_profile_failed".to_string(),
894 action: Some(message.clone()),
895 target_type_table: Some("users".to_string()),
896 target_type_table_id: Some(payload.user_id.clone()),
897 user_id: Some(payload.user_id),
898 ..Default::default()
899 },
900 )
901 .await;
902
903 return (
904 status,
905 Json(AuthTokenResponse {
906 success: false,
907 user: None,
908 tokens: None,
909 message,
910 }),
911 );
912 }
913 };
914
915 let _ = log_audit_event(
916 &db,
917 AuditEvent {
918 log_type: "update_user_profile_success".to_string(),
919 target_type_table: Some("users".to_string()),
920 target_type_table_id: Some(payload.user_id.clone()),
921 user_id: Some(payload.user_id),
922 ..Default::default()
923 },
924 )
925 .await;
926
927 (
928 StatusCode::OK,
929 Json(AuthTokenResponse {
930 success: true,
931 user: Some(user.to_response()),
932 tokens: None,
933 message: "Profile updated successfully".to_string(),
934 }),
935 )
936}