1use actix_web::{dev::Payload, web, FromRequest, HttpMessage, HttpRequest, HttpResponse};
9use chrono::{Duration, Utc};
10use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
11use rusqlite::OptionalExtension;
12use serde::{Deserialize, Serialize};
13use sha2::{Digest, Sha256};
14use std::future::{ready, Ready};
15use uuid::Uuid;
16
17const JWT_SECRET: &[u8] = b"csm_jwt_secret_key_change_in_production_2024";
23const JWT_EXPIRY_HOURS: i64 = 24;
24const REFRESH_TOKEN_EXPIRY_DAYS: i64 = 30;
25
26#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
31#[serde(rename_all = "snake_case")]
32#[derive(Default)]
33pub enum SubscriptionTier {
34 #[default]
36 Free,
37 Pro,
39 Enterprise,
41}
42
43impl SubscriptionTier {
44 pub fn as_str(&self) -> &'static str {
45 match self {
46 Self::Free => "free",
47 Self::Pro => "pro",
48 Self::Enterprise => "enterprise",
49 }
50 }
51
52 pub fn from_str(s: &str) -> Option<Self> {
53 match s.to_lowercase().as_str() {
54 "free" => Some(Self::Free),
55 "pro" => Some(Self::Pro),
56 "enterprise" => Some(Self::Enterprise),
57 _ => None,
58 }
59 }
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct SubscriptionFeatures {
68 pub max_workspaces: Option<u32>,
70 pub max_sessions: Option<u32>,
72 pub max_agents: Option<u32>,
74 pub max_swarms: Option<u32>,
76 pub realtime_sync: bool,
78 pub cross_device_sync: bool,
80 pub priority_support: bool,
82 pub team_collaboration: bool,
84 pub analytics: bool,
86 pub api_access: bool,
88 pub custom_integrations: bool,
90}
91
92impl SubscriptionFeatures {
93 pub fn for_tier(tier: SubscriptionTier) -> Self {
94 match tier {
95 SubscriptionTier::Free => Self {
96 max_workspaces: Some(10),
97 max_sessions: Some(100),
98 max_agents: Some(5),
99 max_swarms: Some(1),
100 realtime_sync: true,
101 cross_device_sync: true,
102 priority_support: false,
103 team_collaboration: false,
104 analytics: false,
105 api_access: false,
106 custom_integrations: false,
107 },
108 SubscriptionTier::Pro => Self {
109 max_workspaces: Some(100),
110 max_sessions: None, max_agents: Some(100),
112 max_swarms: Some(20),
113 realtime_sync: true,
114 cross_device_sync: true,
115 priority_support: true,
116 team_collaboration: false,
117 analytics: true,
118 api_access: true,
119 custom_integrations: false,
120 },
121 SubscriptionTier::Enterprise => Self {
122 max_workspaces: None, max_sessions: None,
124 max_agents: None,
125 max_swarms: None,
126 realtime_sync: true,
127 cross_device_sync: true,
128 priority_support: true,
129 team_collaboration: true,
130 analytics: true,
131 api_access: true,
132 custom_integrations: true,
133 },
134 }
135 }
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct User {
144 pub id: String,
145 pub email: String,
146 pub display_name: String,
147 #[serde(skip_serializing)]
148 #[allow(dead_code)]
149 pub password_hash: String,
150 pub subscription_tier: SubscriptionTier,
151 pub subscription_expires_at: Option<i64>,
152 pub created_at: i64,
153 pub updated_at: i64,
154 pub last_login_at: Option<i64>,
155 pub email_verified: bool,
156 pub avatar_url: Option<String>,
157 pub metadata: Option<String>,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct PublicUser {
162 pub id: String,
163 pub email: String,
164 pub display_name: String,
165 pub subscription_tier: SubscriptionTier,
166 pub subscription_expires_at: Option<i64>,
167 pub created_at: i64,
168 pub email_verified: bool,
169 pub avatar_url: Option<String>,
170 pub features: SubscriptionFeatures,
171}
172
173impl From<User> for PublicUser {
174 fn from(user: User) -> Self {
175 Self {
176 id: user.id,
177 email: user.email,
178 display_name: user.display_name,
179 subscription_tier: user.subscription_tier,
180 subscription_expires_at: user.subscription_expires_at,
181 created_at: user.created_at,
182 email_verified: user.email_verified,
183 avatar_url: user.avatar_url,
184 features: SubscriptionFeatures::for_tier(user.subscription_tier),
185 }
186 }
187}
188
189#[derive(Debug, Serialize, Deserialize)]
194pub struct Claims {
195 pub sub: String,
197 pub email: String,
199 pub tier: String,
201 pub iat: i64,
203 pub exp: i64,
205 pub token_type: String,
207}
208
209#[derive(Debug, Clone)]
214#[allow(dead_code)]
215pub struct AuthenticatedUser {
216 pub user_id: String,
217 pub email: String,
218 pub tier: SubscriptionTier,
219}
220
221impl FromRequest for AuthenticatedUser {
222 type Error = actix_web::Error;
223 type Future = Ready<Result<Self, Self::Error>>;
224
225 fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
226 if let Some(user) = req.extensions().get::<AuthenticatedUser>() {
228 return ready(Ok(user.clone()));
229 }
230
231 if let Some(auth_header) = req.headers().get("Authorization") {
233 if let Ok(auth_str) = auth_header.to_str() {
234 if let Some(token) = auth_str.strip_prefix("Bearer ") {
235 if let Some(user) = validate_token(token) {
236 return ready(Ok(user));
237 }
238 }
239 }
240 }
241
242 ready(Ok(AuthenticatedUser {
243 user_id: String::new(),
244 email: String::new(),
245 tier: SubscriptionTier::Free,
246 }))
247 }
248}
249
250#[derive(Debug, Deserialize)]
255pub struct RegisterRequest {
256 pub email: String,
257 pub password: String,
258 pub display_name: String,
259}
260
261#[derive(Debug, Deserialize)]
262pub struct LoginRequest {
263 pub email: String,
264 pub password: String,
265}
266
267#[derive(Debug, Deserialize)]
268pub struct RefreshTokenRequest {
269 pub refresh_token: String,
270}
271
272#[derive(Debug, Serialize)]
273pub struct AuthResponse {
274 pub user: PublicUser,
275 pub access_token: String,
276 pub refresh_token: String,
277 pub expires_in: i64,
278}
279
280#[derive(Debug, Deserialize)]
281pub struct UpdateProfileRequest {
282 pub display_name: Option<String>,
283 pub avatar_url: Option<String>,
284}
285
286#[derive(Debug, Deserialize)]
287pub struct ChangePasswordRequest {
288 pub current_password: String,
289 pub new_password: String,
290}
291
292#[derive(Debug, Deserialize)]
293pub struct UpgradeSubscriptionRequest {
294 pub tier: String,
295 #[allow(dead_code)]
297 pub payment_token: Option<String>,
298}
299
300pub fn hash_password(password: &str, salt: &str) -> String {
306 let mut hasher = Sha256::new();
307 hasher.update(password.as_bytes());
308 hasher.update(salt.as_bytes());
309 format!("{:x}", hasher.finalize())
310}
311
312pub fn generate_access_token(user: &User) -> Option<String> {
314 let now = Utc::now();
315 let exp = now + Duration::hours(JWT_EXPIRY_HOURS);
316
317 let claims = Claims {
318 sub: user.id.clone(),
319 email: user.email.clone(),
320 tier: user.subscription_tier.as_str().to_string(),
321 iat: now.timestamp(),
322 exp: exp.timestamp(),
323 token_type: "access".to_string(),
324 };
325
326 encode(
327 &Header::default(),
328 &claims,
329 &EncodingKey::from_secret(JWT_SECRET),
330 )
331 .ok()
332}
333
334pub fn generate_refresh_token(user: &User) -> Option<String> {
336 let now = Utc::now();
337 let exp = now + Duration::days(REFRESH_TOKEN_EXPIRY_DAYS);
338
339 let claims = Claims {
340 sub: user.id.clone(),
341 email: user.email.clone(),
342 tier: user.subscription_tier.as_str().to_string(),
343 iat: now.timestamp(),
344 exp: exp.timestamp(),
345 token_type: "refresh".to_string(),
346 };
347
348 encode(
349 &Header::default(),
350 &claims,
351 &EncodingKey::from_secret(JWT_SECRET),
352 )
353 .ok()
354}
355
356pub fn validate_token(token: &str) -> Option<AuthenticatedUser> {
358 let validation = Validation::new(Algorithm::HS256);
359
360 let token_data =
361 decode::<Claims>(token, &DecodingKey::from_secret(JWT_SECRET), &validation).ok()?;
362
363 let claims = token_data.claims;
364
365 if claims.exp < Utc::now().timestamp() {
367 return None;
368 }
369
370 Some(AuthenticatedUser {
371 user_id: claims.sub,
372 email: claims.email,
373 tier: SubscriptionTier::from_str(&claims.tier).unwrap_or_default(),
374 })
375}
376
377pub fn validate_refresh_token(token: &str) -> Option<AuthenticatedUser> {
379 let validation = Validation::new(Algorithm::HS256);
380
381 let token_data =
382 decode::<Claims>(token, &DecodingKey::from_secret(JWT_SECRET), &validation).ok()?;
383
384 let claims = token_data.claims;
385
386 if claims.token_type != "refresh" {
388 return None;
389 }
390
391 if claims.exp < Utc::now().timestamp() {
393 return None;
394 }
395
396 Some(AuthenticatedUser {
397 user_id: claims.sub,
398 email: claims.email,
399 tier: SubscriptionTier::from_str(&claims.tier).unwrap_or_default(),
400 })
401}
402
403pub fn init_auth_tables(conn: &rusqlite::Connection) -> rusqlite::Result<()> {
409 conn.execute(
410 "CREATE TABLE IF NOT EXISTS users (
411 id TEXT PRIMARY KEY,
412 email TEXT UNIQUE NOT NULL,
413 display_name TEXT NOT NULL,
414 password_hash TEXT NOT NULL,
415 password_salt TEXT NOT NULL,
416 subscription_tier TEXT NOT NULL DEFAULT 'free',
417 subscription_expires_at INTEGER,
418 created_at INTEGER NOT NULL,
419 updated_at INTEGER NOT NULL,
420 last_login_at INTEGER,
421 email_verified INTEGER DEFAULT 0,
422 avatar_url TEXT,
423 metadata TEXT
424 )",
425 [],
426 )?;
427
428 conn.execute(
429 "CREATE TABLE IF NOT EXISTS refresh_tokens (
430 id TEXT PRIMARY KEY,
431 user_id TEXT NOT NULL,
432 token_hash TEXT NOT NULL,
433 expires_at INTEGER NOT NULL,
434 created_at INTEGER NOT NULL,
435 revoked INTEGER DEFAULT 0,
436 device_info TEXT,
437 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
438 )",
439 [],
440 )?;
441
442 conn.execute(
443 "CREATE TABLE IF NOT EXISTS user_sessions (
444 id TEXT PRIMARY KEY,
445 user_id TEXT NOT NULL,
446 device_id TEXT,
447 device_name TEXT,
448 platform TEXT,
449 ip_address TEXT,
450 last_active_at INTEGER NOT NULL,
451 created_at INTEGER NOT NULL,
452 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
453 )",
454 [],
455 )?;
456
457 conn.execute(
459 "CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)",
460 [],
461 )?;
462 conn.execute(
463 "CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user_id ON refresh_tokens(user_id)",
464 [],
465 )?;
466 conn.execute(
467 "CREATE INDEX IF NOT EXISTS idx_user_sessions_user_id ON user_sessions(user_id)",
468 [],
469 )?;
470
471 Ok(())
472}
473
474pub async fn register(
480 app_state: web::Data<crate::api::state::AppState>,
481 body: web::Json<RegisterRequest>,
482) -> HttpResponse {
483 let db = app_state.db.lock().unwrap();
484
485 if let Err(e) = init_auth_tables(&db.conn) {
487 return HttpResponse::InternalServerError().json(serde_json::json!({
488 "success": false,
489 "error": format!("Database error: {}", e)
490 }));
491 }
492
493 if body.email.is_empty() || !body.email.contains('@') {
495 return HttpResponse::BadRequest().json(serde_json::json!({
496 "success": false,
497 "error": "Invalid email address"
498 }));
499 }
500
501 if body.password.len() < 8 {
502 return HttpResponse::BadRequest().json(serde_json::json!({
503 "success": false,
504 "error": "Password must be at least 8 characters"
505 }));
506 }
507
508 if body.display_name.is_empty() {
509 return HttpResponse::BadRequest().json(serde_json::json!({
510 "success": false,
511 "error": "Display name is required"
512 }));
513 }
514
515 let existing: rusqlite::Result<Option<String>> = db
517 .conn
518 .query_row(
519 "SELECT id FROM users WHERE email = ?1",
520 rusqlite::params![body.email.to_lowercase()],
521 |row| row.get(0),
522 )
523 .optional();
524
525 if let Ok(Some(_)) = existing {
526 return HttpResponse::Conflict().json(serde_json::json!({
527 "success": false,
528 "error": "Email already registered"
529 }));
530 }
531
532 let user_id = Uuid::new_v4().to_string();
534 let salt = Uuid::new_v4().to_string();
535 let password_hash = hash_password(&body.password, &salt);
536 let now = Utc::now().timestamp();
537
538 let result = db.conn.execute(
539 "INSERT INTO users (id, email, display_name, password_hash, password_salt,
540 subscription_tier, created_at, updated_at)
541 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
542 rusqlite::params![
543 user_id,
544 body.email.to_lowercase(),
545 body.display_name,
546 password_hash,
547 salt,
548 "free",
549 now,
550 now,
551 ],
552 );
553
554 if let Err(e) = result {
555 return HttpResponse::InternalServerError().json(serde_json::json!({
556 "success": false,
557 "error": format!("Failed to create user: {}", e)
558 }));
559 }
560
561 let user = User {
563 id: user_id,
564 email: body.email.to_lowercase(),
565 display_name: body.display_name.clone(),
566 password_hash,
567 subscription_tier: SubscriptionTier::Free,
568 subscription_expires_at: None,
569 created_at: now,
570 updated_at: now,
571 last_login_at: None,
572 email_verified: false,
573 avatar_url: None,
574 metadata: None,
575 };
576
577 let access_token = match generate_access_token(&user) {
579 Some(t) => t,
580 None => {
581 return HttpResponse::InternalServerError().json(serde_json::json!({
582 "success": false,
583 "error": "Failed to generate access token"
584 }))
585 }
586 };
587
588 let refresh_token = match generate_refresh_token(&user) {
589 Some(t) => t,
590 None => {
591 return HttpResponse::InternalServerError().json(serde_json::json!({
592 "success": false,
593 "error": "Failed to generate refresh token"
594 }))
595 }
596 };
597
598 HttpResponse::Created().json(serde_json::json!({
599 "success": true,
600 "data": AuthResponse {
601 user: PublicUser::from(user),
602 access_token,
603 refresh_token,
604 expires_in: JWT_EXPIRY_HOURS * 3600,
605 }
606 }))
607}
608
609pub async fn login(
611 app_state: web::Data<crate::api::state::AppState>,
612 body: web::Json<LoginRequest>,
613) -> HttpResponse {
614 let db = app_state.db.lock().unwrap();
615
616 if let Err(e) = init_auth_tables(&db.conn) {
618 return HttpResponse::InternalServerError().json(serde_json::json!({
619 "success": false,
620 "error": format!("Database error: {}", e)
621 }));
622 }
623
624 let user_result: rusqlite::Result<(
626 String,
627 String,
628 String,
629 String,
630 String,
631 String,
632 Option<i64>,
633 i64,
634 i64,
635 Option<i64>,
636 i32,
637 Option<String>,
638 )> = db.conn.query_row(
639 "SELECT id, email, display_name, password_hash, password_salt,
640 subscription_tier, subscription_expires_at, created_at, updated_at,
641 last_login_at, email_verified, avatar_url
642 FROM users WHERE email = ?1",
643 rusqlite::params![body.email.to_lowercase()],
644 |row| {
645 Ok((
646 row.get(0)?,
647 row.get(1)?,
648 row.get(2)?,
649 row.get(3)?,
650 row.get(4)?,
651 row.get(5)?,
652 row.get(6)?,
653 row.get(7)?,
654 row.get(8)?,
655 row.get(9)?,
656 row.get(10)?,
657 row.get(11)?,
658 ))
659 },
660 );
661
662 let (
663 id,
664 email,
665 display_name,
666 stored_hash,
667 salt,
668 tier_str,
669 sub_expires,
670 created_at,
671 updated_at,
672 _last_login,
673 verified,
674 avatar,
675 ) = match user_result {
676 Ok(data) => data,
677 Err(_) => {
678 return HttpResponse::Unauthorized().json(serde_json::json!({
679 "success": false,
680 "error": "Invalid email or password"
681 }))
682 }
683 };
684
685 let provided_hash = hash_password(&body.password, &salt);
687 if provided_hash != stored_hash {
688 return HttpResponse::Unauthorized().json(serde_json::json!({
689 "success": false,
690 "error": "Invalid email or password"
691 }));
692 }
693
694 let now = Utc::now().timestamp();
696 let _ = db.conn.execute(
697 "UPDATE users SET last_login_at = ?1 WHERE id = ?2",
698 rusqlite::params![now, id],
699 );
700
701 let user = User {
702 id,
703 email,
704 display_name,
705 password_hash: stored_hash,
706 subscription_tier: SubscriptionTier::from_str(&tier_str).unwrap_or_default(),
707 subscription_expires_at: sub_expires,
708 created_at,
709 updated_at,
710 last_login_at: Some(now),
711 email_verified: verified == 1,
712 avatar_url: avatar,
713 metadata: None,
714 };
715
716 let access_token = match generate_access_token(&user) {
718 Some(t) => t,
719 None => {
720 return HttpResponse::InternalServerError().json(serde_json::json!({
721 "success": false,
722 "error": "Failed to generate access token"
723 }))
724 }
725 };
726
727 let refresh_token = match generate_refresh_token(&user) {
728 Some(t) => t,
729 None => {
730 return HttpResponse::InternalServerError().json(serde_json::json!({
731 "success": false,
732 "error": "Failed to generate refresh token"
733 }))
734 }
735 };
736
737 HttpResponse::Ok().json(serde_json::json!({
738 "success": true,
739 "data": AuthResponse {
740 user: PublicUser::from(user),
741 access_token,
742 refresh_token,
743 expires_in: JWT_EXPIRY_HOURS * 3600,
744 }
745 }))
746}
747
748pub async fn refresh_token(
750 app_state: web::Data<crate::api::state::AppState>,
751 body: web::Json<RefreshTokenRequest>,
752) -> HttpResponse {
753 let auth_user = match validate_refresh_token(&body.refresh_token) {
755 Some(u) => u,
756 None => {
757 return HttpResponse::Unauthorized().json(serde_json::json!({
758 "success": false,
759 "error": "Invalid or expired refresh token"
760 }))
761 }
762 };
763
764 let db = app_state.db.lock().unwrap();
765
766 let user_result: rusqlite::Result<(
768 String,
769 String,
770 String,
771 String,
772 Option<i64>,
773 i64,
774 i64,
775 i32,
776 Option<String>,
777 )> = db.conn.query_row(
778 "SELECT id, email, display_name, subscription_tier, subscription_expires_at,
779 created_at, updated_at, email_verified, avatar_url
780 FROM users WHERE id = ?1",
781 rusqlite::params![auth_user.user_id],
782 |row| {
783 Ok((
784 row.get(0)?,
785 row.get(1)?,
786 row.get(2)?,
787 row.get(3)?,
788 row.get(4)?,
789 row.get(5)?,
790 row.get(6)?,
791 row.get(7)?,
792 row.get(8)?,
793 ))
794 },
795 );
796
797 let (id, email, display_name, tier_str, sub_expires, created_at, updated_at, verified, avatar) =
798 match user_result {
799 Ok(data) => data,
800 Err(_) => {
801 return HttpResponse::NotFound().json(serde_json::json!({
802 "success": false,
803 "error": "User not found"
804 }))
805 }
806 };
807
808 let user = User {
809 id,
810 email,
811 display_name,
812 password_hash: String::new(), subscription_tier: SubscriptionTier::from_str(&tier_str).unwrap_or_default(),
814 subscription_expires_at: sub_expires,
815 created_at,
816 updated_at,
817 last_login_at: None,
818 email_verified: verified == 1,
819 avatar_url: avatar,
820 metadata: None,
821 };
822
823 let access_token = match generate_access_token(&user) {
825 Some(t) => t,
826 None => {
827 return HttpResponse::InternalServerError().json(serde_json::json!({
828 "success": false,
829 "error": "Failed to generate access token"
830 }))
831 }
832 };
833
834 let new_refresh_token = match generate_refresh_token(&user) {
835 Some(t) => t,
836 None => {
837 return HttpResponse::InternalServerError().json(serde_json::json!({
838 "success": false,
839 "error": "Failed to generate refresh token"
840 }))
841 }
842 };
843
844 HttpResponse::Ok().json(serde_json::json!({
845 "success": true,
846 "data": AuthResponse {
847 user: PublicUser::from(user),
848 access_token,
849 refresh_token: new_refresh_token,
850 expires_in: JWT_EXPIRY_HOURS * 3600,
851 }
852 }))
853}
854
855pub async fn get_profile(
857 app_state: web::Data<crate::api::state::AppState>,
858 auth_user: AuthenticatedUser,
859) -> HttpResponse {
860 if auth_user.user_id.is_empty() {
861 return HttpResponse::Unauthorized().json(serde_json::json!({
862 "success": false,
863 "error": "Not authenticated"
864 }));
865 }
866
867 let db = app_state.db.lock().unwrap();
868
869 let user_result: rusqlite::Result<(
870 String,
871 String,
872 String,
873 String,
874 Option<i64>,
875 i64,
876 i64,
877 i32,
878 Option<String>,
879 )> = db.conn.query_row(
880 "SELECT id, email, display_name, subscription_tier, subscription_expires_at,
881 created_at, updated_at, email_verified, avatar_url
882 FROM users WHERE id = ?1",
883 rusqlite::params![auth_user.user_id],
884 |row| {
885 Ok((
886 row.get(0)?,
887 row.get(1)?,
888 row.get(2)?,
889 row.get(3)?,
890 row.get(4)?,
891 row.get(5)?,
892 row.get(6)?,
893 row.get(7)?,
894 row.get(8)?,
895 ))
896 },
897 );
898
899 match user_result {
900 Ok((
901 id,
902 email,
903 display_name,
904 tier_str,
905 sub_expires,
906 created_at,
907 updated_at,
908 verified,
909 avatar,
910 )) => {
911 let user = User {
912 id,
913 email,
914 display_name,
915 password_hash: String::new(),
916 subscription_tier: SubscriptionTier::from_str(&tier_str).unwrap_or_default(),
917 subscription_expires_at: sub_expires,
918 created_at,
919 updated_at,
920 last_login_at: None,
921 email_verified: verified == 1,
922 avatar_url: avatar,
923 metadata: None,
924 };
925
926 HttpResponse::Ok().json(serde_json::json!({
927 "success": true,
928 "data": PublicUser::from(user)
929 }))
930 }
931 Err(_) => HttpResponse::NotFound().json(serde_json::json!({
932 "success": false,
933 "error": "User not found"
934 })),
935 }
936}
937
938pub async fn update_profile(
940 app_state: web::Data<crate::api::state::AppState>,
941 auth_user: AuthenticatedUser,
942 body: web::Json<UpdateProfileRequest>,
943) -> HttpResponse {
944 if auth_user.user_id.is_empty() {
945 return HttpResponse::Unauthorized().json(serde_json::json!({
946 "success": false,
947 "error": "Not authenticated"
948 }));
949 }
950
951 {
953 let db = app_state.db.lock().unwrap();
954 let now = Utc::now().timestamp();
955
956 let mut updates = Vec::new();
958 let mut params: Vec<Box<dyn rusqlite::ToSql>> = Vec::new();
959
960 if let Some(ref name) = body.display_name {
961 updates.push("display_name = ?");
962 params.push(Box::new(name.clone()));
963 }
964
965 if let Some(ref avatar) = body.avatar_url {
966 updates.push("avatar_url = ?");
967 params.push(Box::new(avatar.clone()));
968 }
969
970 if updates.is_empty() {
971 return HttpResponse::BadRequest().json(serde_json::json!({
972 "success": false,
973 "error": "No fields to update"
974 }));
975 }
976
977 updates.push("updated_at = ?");
978 params.push(Box::new(now));
979
980 let query = format!("UPDATE users SET {} WHERE id = ?", updates.join(", "));
981 params.push(Box::new(auth_user.user_id.clone()));
982
983 let params_refs: Vec<&dyn rusqlite::ToSql> = params.iter().map(|p| p.as_ref()).collect();
984
985 if let Err(e) = db.conn.execute(&query, params_refs.as_slice()) {
986 return HttpResponse::InternalServerError().json(serde_json::json!({
987 "success": false,
988 "error": format!("Failed to update profile: {}", e)
989 }));
990 }
991 } get_profile(app_state, auth_user).await
995}
996
997pub async fn change_password(
999 app_state: web::Data<crate::api::state::AppState>,
1000 auth_user: AuthenticatedUser,
1001 body: web::Json<ChangePasswordRequest>,
1002) -> HttpResponse {
1003 if auth_user.user_id.is_empty() {
1004 return HttpResponse::Unauthorized().json(serde_json::json!({
1005 "success": false,
1006 "error": "Not authenticated"
1007 }));
1008 }
1009
1010 if body.new_password.len() < 8 {
1011 return HttpResponse::BadRequest().json(serde_json::json!({
1012 "success": false,
1013 "error": "New password must be at least 8 characters"
1014 }));
1015 }
1016
1017 let db = app_state.db.lock().unwrap();
1018
1019 let creds: rusqlite::Result<(String, String)> = db.conn.query_row(
1021 "SELECT password_hash, password_salt FROM users WHERE id = ?1",
1022 rusqlite::params![auth_user.user_id],
1023 |row| Ok((row.get(0)?, row.get(1)?)),
1024 );
1025
1026 let (stored_hash, salt) = match creds {
1027 Ok(c) => c,
1028 Err(_) => {
1029 return HttpResponse::NotFound().json(serde_json::json!({
1030 "success": false,
1031 "error": "User not found"
1032 }))
1033 }
1034 };
1035
1036 let current_hash = hash_password(&body.current_password, &salt);
1038 if current_hash != stored_hash {
1039 return HttpResponse::Unauthorized().json(serde_json::json!({
1040 "success": false,
1041 "error": "Current password is incorrect"
1042 }));
1043 }
1044
1045 let new_salt = Uuid::new_v4().to_string();
1047 let new_hash = hash_password(&body.new_password, &new_salt);
1048 let now = Utc::now().timestamp();
1049
1050 if let Err(e) = db.conn.execute(
1051 "UPDATE users SET password_hash = ?1, password_salt = ?2, updated_at = ?3 WHERE id = ?4",
1052 rusqlite::params![new_hash, new_salt, now, auth_user.user_id],
1053 ) {
1054 return HttpResponse::InternalServerError().json(serde_json::json!({
1055 "success": false,
1056 "error": format!("Failed to update password: {}", e)
1057 }));
1058 }
1059
1060 HttpResponse::Ok().json(serde_json::json!({
1061 "success": true,
1062 "message": "Password updated successfully"
1063 }))
1064}
1065
1066pub async fn get_subscription(
1068 app_state: web::Data<crate::api::state::AppState>,
1069 auth_user: AuthenticatedUser,
1070) -> HttpResponse {
1071 if auth_user.user_id.is_empty() {
1072 return HttpResponse::Unauthorized().json(serde_json::json!({
1073 "success": false,
1074 "error": "Not authenticated"
1075 }));
1076 }
1077
1078 let db = app_state.db.lock().unwrap();
1079
1080 let result: rusqlite::Result<(String, Option<i64>)> = db.conn.query_row(
1081 "SELECT subscription_tier, subscription_expires_at FROM users WHERE id = ?1",
1082 rusqlite::params![auth_user.user_id],
1083 |row| Ok((row.get(0)?, row.get(1)?)),
1084 );
1085
1086 match result {
1087 Ok((tier_str, expires_at)) => {
1088 let tier = SubscriptionTier::from_str(&tier_str).unwrap_or_default();
1089 let features = SubscriptionFeatures::for_tier(tier);
1090
1091 HttpResponse::Ok().json(serde_json::json!({
1092 "success": true,
1093 "data": {
1094 "tier": tier_str,
1095 "expiresAt": expires_at,
1096 "features": features,
1097 "isActive": expires_at.map(|exp| exp > Utc::now().timestamp()).unwrap_or(true),
1098 }
1099 }))
1100 }
1101 Err(_) => HttpResponse::NotFound().json(serde_json::json!({
1102 "success": false,
1103 "error": "User not found"
1104 })),
1105 }
1106}
1107
1108pub async fn upgrade_subscription(
1110 app_state: web::Data<crate::api::state::AppState>,
1111 auth_user: AuthenticatedUser,
1112 body: web::Json<UpgradeSubscriptionRequest>,
1113) -> HttpResponse {
1114 if auth_user.user_id.is_empty() {
1115 return HttpResponse::Unauthorized().json(serde_json::json!({
1116 "success": false,
1117 "error": "Not authenticated"
1118 }));
1119 }
1120
1121 let new_tier = match SubscriptionTier::from_str(&body.tier) {
1122 Some(t) => t,
1123 None => {
1124 return HttpResponse::BadRequest().json(serde_json::json!({
1125 "success": false,
1126 "error": "Invalid subscription tier"
1127 }))
1128 }
1129 };
1130
1131 let db = app_state.db.lock().unwrap();
1137 let now = Utc::now().timestamp();
1138 let expires_at = if new_tier == SubscriptionTier::Free {
1139 None
1140 } else {
1141 Some(now + (365 * 24 * 3600))
1143 };
1144
1145 if let Err(e) = db.conn.execute(
1146 "UPDATE users SET subscription_tier = ?1, subscription_expires_at = ?2, updated_at = ?3 WHERE id = ?4",
1147 rusqlite::params![new_tier.as_str(), expires_at, now, auth_user.user_id],
1148 ) {
1149 return HttpResponse::InternalServerError().json(serde_json::json!({
1150 "success": false,
1151 "error": format!("Failed to upgrade subscription: {}", e)
1152 }));
1153 }
1154
1155 let features = SubscriptionFeatures::for_tier(new_tier);
1156
1157 HttpResponse::Ok().json(serde_json::json!({
1158 "success": true,
1159 "data": {
1160 "tier": new_tier.as_str(),
1161 "expiresAt": expires_at,
1162 "features": features,
1163 "message": "Subscription upgraded successfully"
1164 }
1165 }))
1166}
1167
1168pub async fn logout(_auth_user: AuthenticatedUser) -> HttpResponse {
1170 HttpResponse::Ok().json(serde_json::json!({
1176 "success": true,
1177 "message": "Logged out successfully"
1178 }))
1179}
1180
1181pub fn configure_auth_routes(cfg: &mut web::ServiceConfig) {
1186 cfg.service(
1187 web::scope("/auth")
1188 .route("/register", web::post().to(register))
1189 .route("/login", web::post().to(login))
1190 .route("/refresh", web::post().to(refresh_token))
1191 .route("/logout", web::post().to(logout))
1192 .route("/profile", web::get().to(get_profile))
1193 .route("/profile", web::put().to(update_profile))
1194 .route("/password", web::put().to(change_password))
1195 .route("/subscription", web::get().to(get_subscription))
1196 .route(
1197 "/subscription/upgrade",
1198 web::post().to(upgrade_subscription),
1199 ),
1200 );
1201}