supabase/
auth.rs

1//! Authentication module for Supabase client
2//!
3//! This module provides comprehensive authentication functionality including:
4//! - Email/password authentication
5//! - OAuth providers (Google, GitHub, Discord, Apple, etc.)
6//! - Phone/SMS authentication
7//! - Magic link authentication
8//! - Anonymous authentication
9//! - Session management and token refresh
10//! - Auth state change events
11
12use crate::{
13    error::{Error, Result},
14    types::{SupabaseConfig, Timestamp},
15};
16use chrono::Utc;
17use reqwest::Client as HttpClient;
18use serde::{Deserialize, Serialize};
19use std::collections::HashMap;
20use std::sync::{Arc, RwLock, Weak};
21use tracing::{debug, info, warn};
22use uuid::Uuid;
23
24// MFA imports
25use base32;
26use phonenumber::Mode;
27use qrcode::QrCode;
28use totp_rs::{Algorithm, TOTP};
29
30/// Authentication state event types
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum AuthEvent {
33    /// User signed in
34    SignedIn,
35    /// User signed out
36    SignedOut,
37    /// Session refreshed
38    TokenRefreshed,
39    /// User information updated
40    UserUpdated,
41    /// Password reset initiated
42    PasswordReset,
43    /// MFA challenge required
44    MfaChallengeRequired,
45    /// MFA challenge completed
46    MfaChallengeCompleted,
47    /// MFA enabled for user
48    MfaEnabled,
49    /// MFA disabled for user
50    MfaDisabled,
51}
52
53/// Multi-factor authentication method types
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub enum MfaMethod {
56    /// Time-based One-Time Password (TOTP) - Google Authenticator, Authy, etc.
57    #[serde(rename = "totp")]
58    Totp,
59    /// SMS-based verification
60    #[serde(rename = "sms")]
61    Sms,
62    /// Email-based verification (future)
63    #[serde(rename = "email")]
64    Email,
65}
66
67impl std::fmt::Display for MfaMethod {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        match self {
70            MfaMethod::Totp => write!(f, "TOTP"),
71            MfaMethod::Sms => write!(f, "SMS"),
72            MfaMethod::Email => write!(f, "Email"),
73        }
74    }
75}
76
77/// MFA challenge status
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
79pub enum MfaChallengeStatus {
80    /// Challenge is pending user input
81    #[serde(rename = "pending")]
82    Pending,
83    /// Challenge was completed successfully
84    #[serde(rename = "completed")]
85    Completed,
86    /// Challenge expired
87    #[serde(rename = "expired")]
88    Expired,
89    /// Challenge was cancelled
90    #[serde(rename = "cancelled")]
91    Cancelled,
92}
93
94/// MFA factor configuration
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct MfaFactor {
97    pub id: Uuid,
98    pub factor_type: MfaMethod,
99    pub friendly_name: String,
100    pub status: String, // "verified", "unverified"
101    pub created_at: Timestamp,
102    pub updated_at: Timestamp,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub phone: Option<String>,
105}
106
107/// TOTP setup response containing QR code data
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct TotpSetupResponse {
110    pub secret: String,
111    pub qr_code: String,
112    pub uri: String,
113    pub factor_id: Uuid,
114}
115
116/// MFA challenge information
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct MfaChallenge {
119    pub id: Uuid,
120    pub factor_id: Uuid,
121    pub status: MfaChallengeStatus,
122    pub challenge_type: MfaMethod,
123    pub expires_at: Timestamp,
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub masked_phone: Option<String>,
126}
127
128/// MFA verification request
129#[derive(Debug, Serialize)]
130pub struct MfaVerificationRequest {
131    pub factor_id: Uuid,
132    pub challenge_id: Uuid,
133    pub code: String,
134}
135
136/// Enhanced phone number with country code support
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct EnhancedPhoneNumber {
139    pub raw: String,
140    pub formatted: String,
141    pub country_code: String,
142    pub national_number: String,
143    pub is_valid: bool,
144}
145
146impl EnhancedPhoneNumber {
147    /// Create enhanced phone number from raw input
148    pub fn new(phone: &str, _default_region: Option<&str>) -> Result<Self> {
149        // For now, let's use a simplified approach
150        // In production, this should be properly implemented with phonenumber crate
151        let parsed = phonenumber::parse(None, phone)
152            .map_err(|e| Error::auth(format!("Invalid phone number: {}", e)))?;
153
154        let formatted = phonenumber::format(&parsed)
155            .mode(Mode::International)
156            .to_string();
157
158        // Extract basic info
159        let country_code = parsed.code().value().to_string();
160        let national_number = parsed.national().to_string();
161
162        Ok(Self {
163            raw: phone.to_string(),
164            formatted,
165            country_code,
166            national_number,
167            is_valid: phonenumber::is_valid(&parsed),
168        })
169    }
170}
171
172/// OAuth token metadata for advanced management
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct TokenMetadata {
175    pub issued_at: Timestamp,
176    pub expires_at: Timestamp,
177    pub refresh_count: u32,
178    pub last_refresh_at: Option<Timestamp>,
179    pub scopes: Vec<String>,
180    pub device_id: Option<String>,
181}
182
183/// Enhanced session with MFA and advanced token info
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct EnhancedSession {
186    pub access_token: String,
187    pub refresh_token: String,
188    pub expires_in: i64,
189    pub expires_at: Timestamp,
190    pub token_type: String,
191    pub user: User,
192    pub token_metadata: Option<TokenMetadata>,
193    pub mfa_verified: bool,
194    pub active_factors: Vec<MfaFactor>,
195}
196
197/// Authentication state change callback
198pub type AuthStateCallback = Box<dyn Fn(AuthEvent, Option<Session>) + Send + Sync + 'static>;
199
200/// Authentication event listener handle
201#[derive(Debug, Clone)]
202pub struct AuthEventHandle {
203    id: Uuid,
204    auth: Weak<Auth>,
205}
206
207impl AuthEventHandle {
208    /// Remove this event listener
209    pub fn remove(&self) {
210        if let Some(auth) = self.auth.upgrade() {
211            auth.remove_auth_listener(self.id);
212        }
213    }
214}
215
216/// Supported OAuth providers
217#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
218pub enum OAuthProvider {
219    /// Google OAuth
220    #[serde(rename = "google")]
221    Google,
222    /// GitHub OAuth
223    #[serde(rename = "github")]
224    GitHub,
225    /// Discord OAuth
226    #[serde(rename = "discord")]
227    Discord,
228    /// Apple OAuth
229    #[serde(rename = "apple")]
230    Apple,
231    /// Twitter OAuth
232    #[serde(rename = "twitter")]
233    Twitter,
234    /// Facebook OAuth
235    #[serde(rename = "facebook")]
236    Facebook,
237    /// Microsoft OAuth
238    #[serde(rename = "azure")]
239    Microsoft,
240    /// LinkedIn OAuth
241    #[serde(rename = "linkedin_oidc")]
242    LinkedIn,
243}
244
245impl OAuthProvider {
246    /// Get provider name as string
247    pub fn as_str(&self) -> &'static str {
248        match self {
249            OAuthProvider::Google => "google",
250            OAuthProvider::GitHub => "github",
251            OAuthProvider::Discord => "discord",
252            OAuthProvider::Apple => "apple",
253            OAuthProvider::Twitter => "twitter",
254            OAuthProvider::Facebook => "facebook",
255            OAuthProvider::Microsoft => "azure",
256            OAuthProvider::LinkedIn => "linkedin_oidc",
257        }
258    }
259}
260
261/// OAuth sign-in options
262#[derive(Debug, Clone, Default)]
263pub struct OAuthOptions {
264    /// Custom redirect URL after sign-in
265    pub redirect_to: Option<String>,
266    /// Additional scopes to request
267    pub scopes: Option<Vec<String>>,
268    /// Additional provider-specific options
269    pub query_params: Option<std::collections::HashMap<String, String>>,
270}
271
272/// OAuth response with authorization URL
273#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct OAuthResponse {
275    /// Authorization URL to redirect user to
276    pub url: String,
277}
278
279/// Phone authentication request
280#[derive(Debug, Serialize)]
281struct PhoneSignUpRequest {
282    phone: String,
283    password: String,
284    #[serde(skip_serializing_if = "Option::is_none")]
285    data: Option<serde_json::Value>,
286}
287
288/// Phone authentication sign-in request
289#[derive(Debug, Serialize)]
290struct PhoneSignInRequest {
291    phone: String,
292    password: String,
293}
294
295/// OTP verification request
296#[derive(Debug, Serialize)]
297struct OTPVerificationRequest {
298    phone: String,
299    token: String,
300    #[serde(rename = "type")]
301    verification_type: String,
302}
303
304/// Magic link request
305#[derive(Debug, Serialize)]
306struct MagicLinkRequest {
307    email: String,
308    #[serde(skip_serializing_if = "Option::is_none")]
309    redirect_to: Option<String>,
310    #[serde(skip_serializing_if = "Option::is_none")]
311    data: Option<serde_json::Value>,
312}
313
314/// Anonymous sign-in request
315#[derive(Debug, Serialize)]
316struct AnonymousSignInRequest {
317    #[serde(skip_serializing_if = "Option::is_none")]
318    data: Option<serde_json::Value>,
319}
320
321/// Authentication client for handling user sessions and JWT tokens
322pub struct Auth {
323    http_client: Arc<HttpClient>,
324    config: Arc<SupabaseConfig>,
325    session: Arc<RwLock<Option<Session>>>,
326    event_listeners: Arc<RwLock<HashMap<Uuid, AuthStateCallback>>>,
327}
328
329impl Clone for Auth {
330    fn clone(&self) -> Self {
331        Self {
332            http_client: self.http_client.clone(),
333            config: self.config.clone(),
334            session: self.session.clone(),
335            event_listeners: Arc::new(RwLock::new(HashMap::new())),
336        }
337    }
338}
339
340impl std::fmt::Debug for Auth {
341    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
342        f.debug_struct("Auth")
343            .field("http_client", &"HttpClient")
344            .field("config", &self.config)
345            .field("session", &self.session)
346            .field(
347                "event_listeners",
348                &format!(
349                    "HashMap<Uuid, Callback> with {} listeners",
350                    self.event_listeners.read().map(|l| l.len()).unwrap_or(0)
351                ),
352            )
353            .finish()
354    }
355}
356
357/// User information from Supabase Auth
358#[derive(Debug, Clone, Serialize, Deserialize)]
359pub struct User {
360    pub id: Uuid,
361    pub email: Option<String>,
362    pub phone: Option<String>,
363    pub email_confirmed_at: Option<Timestamp>,
364    pub phone_confirmed_at: Option<Timestamp>,
365    pub created_at: Timestamp,
366    pub updated_at: Timestamp,
367    pub last_sign_in_at: Option<Timestamp>,
368    pub app_metadata: serde_json::Value,
369    pub user_metadata: serde_json::Value,
370    pub aud: String,
371    pub role: Option<String>,
372}
373
374/// Authentication session containing user and tokens
375#[derive(Debug, Clone, Serialize, Deserialize)]
376pub struct Session {
377    pub access_token: String,
378    pub refresh_token: String,
379    pub expires_in: i64,
380    #[serde(with = "chrono::serde::ts_seconds")]
381    pub expires_at: Timestamp,
382    pub token_type: String,
383    pub user: User,
384}
385
386/// Response from authentication operations
387#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct AuthResponse {
389    pub user: Option<User>,
390    pub session: Option<Session>,
391}
392
393/// Sign up request payload
394#[derive(Debug, Serialize)]
395struct SignUpRequest {
396    email: String,
397    password: String,
398    #[serde(skip_serializing_if = "Option::is_none")]
399    data: Option<serde_json::Value>,
400    #[serde(skip_serializing_if = "Option::is_none")]
401    redirect_to: Option<String>,
402}
403
404/// Sign in request payload
405#[derive(Debug, Serialize)]
406struct SignInRequest {
407    email: String,
408    password: String,
409}
410
411/// Password reset request payload
412#[derive(Debug, Serialize)]
413struct PasswordResetRequest {
414    email: String,
415    #[serde(skip_serializing_if = "Option::is_none")]
416    redirect_to: Option<String>,
417}
418
419/// Token refresh request payload
420#[derive(Debug, Serialize)]
421struct RefreshTokenRequest {
422    refresh_token: String,
423}
424
425/// Update user request payload
426#[derive(Debug, Serialize)]
427struct UpdateUserRequest {
428    #[serde(skip_serializing_if = "Option::is_none")]
429    email: Option<String>,
430    #[serde(skip_serializing_if = "Option::is_none")]
431    password: Option<String>,
432    #[serde(skip_serializing_if = "Option::is_none")]
433    data: Option<serde_json::Value>,
434}
435
436impl Auth {
437    /// Create a new Auth instance
438    pub fn new(config: Arc<SupabaseConfig>, http_client: Arc<HttpClient>) -> Result<Self> {
439        debug!("Initializing Auth module");
440
441        Ok(Self {
442            http_client,
443            config,
444            session: Arc::new(RwLock::new(None)),
445            event_listeners: Arc::new(RwLock::new(HashMap::new())),
446        })
447    }
448
449    /// Sign up a new user with email and password
450    pub async fn sign_up_with_email_and_password(
451        &self,
452        email: &str,
453        password: &str,
454    ) -> Result<AuthResponse> {
455        self.sign_up_with_email_password_and_data(email, password, None, None)
456            .await
457    }
458
459    /// Sign up a new user with email, password, and optional metadata
460    pub async fn sign_up_with_email_password_and_data(
461        &self,
462        email: &str,
463        password: &str,
464        data: Option<serde_json::Value>,
465        redirect_to: Option<String>,
466    ) -> Result<AuthResponse> {
467        debug!("Signing up user with email: {}", email);
468
469        let payload = SignUpRequest {
470            email: email.to_string(),
471            password: password.to_string(),
472            data,
473            redirect_to,
474        };
475
476        let response = self
477            .http_client
478            .post(format!("{}/auth/v1/signup", self.config.url))
479            .json(&payload)
480            .send()
481            .await?;
482
483        if !response.status().is_success() {
484            let status = response.status();
485            let error_msg = match response.text().await {
486                Ok(text) => text,
487                Err(_) => format!("Sign up failed with status: {}", status),
488            };
489            return Err(Error::auth(error_msg));
490        }
491
492        let auth_response_body = response.text().await?;
493
494        let mut auth_response = serde_json::from_str::<AuthResponse>(auth_response_body.as_str())?;
495        auth_response.session = serde_json::from_str::<Session>(auth_response_body.as_str())
496            .inspect_err(|err| warn!("No session: {}", err.to_string()))
497            .ok();
498
499        if let Some(ref session) = auth_response.session {
500            self.set_session(session.clone()).await?;
501            self.trigger_auth_event(AuthEvent::SignedIn);
502            info!("User signed up successfully");
503        }
504
505        Ok(auth_response)
506    }
507
508    /// Sign in with email and password
509    pub async fn sign_in_with_email_and_password(
510        &self,
511        email: &str,
512        password: &str,
513    ) -> Result<AuthResponse> {
514        debug!("Signing in user with email: {}", email);
515
516        let payload = SignInRequest {
517            email: email.to_string(),
518            password: password.to_string(),
519        };
520
521        let response = self
522            .http_client
523            .post(format!(
524                "{}/auth/v1/token?grant_type=password",
525                self.config.url
526            ))
527            .json(&payload)
528            .send()
529            .await?;
530
531        if !response.status().is_success() {
532            let status = response.status();
533            let error_msg = match response.text().await {
534                Ok(text) => text,
535                Err(_) => format!("Sign in failed with status: {}", status),
536            };
537            return Err(Error::auth(error_msg));
538        }
539
540        let auth_response_body = response.text().await?;
541
542        let mut auth_response = serde_json::from_str::<AuthResponse>(auth_response_body.as_str())?;
543        auth_response.session = serde_json::from_str::<Session>(auth_response_body.as_str())
544            .inspect_err(|err| warn!("No session: {}", err.to_string()))
545            .ok();
546
547        if let Some(ref session) = auth_response.session {
548            self.set_session(session.clone()).await?;
549            self.trigger_auth_event(AuthEvent::SignedIn);
550            info!("User signed in successfully");
551        }
552
553        Ok(auth_response)
554    }
555
556    /// Sign out the current user
557    pub async fn sign_out(&self) -> Result<()> {
558        debug!("Signing out user");
559
560        let session = self.get_session()?;
561
562        let response = self
563            .http_client
564            .post(format!("{}/auth/v1/logout", self.config.url))
565            .header("Authorization", format!("Bearer {}", session.access_token))
566            .send()
567            .await?;
568
569        if !response.status().is_success() {
570            warn!("Sign out request failed with status: {}", response.status());
571        }
572
573        self.clear_session().await?;
574        self.trigger_auth_event(AuthEvent::SignedOut);
575        info!("User signed out successfully");
576
577        Ok(())
578    }
579
580    /// Reset password via email
581    pub async fn reset_password_for_email(&self, email: &str) -> Result<()> {
582        self.reset_password_for_email_with_redirect(email, None)
583            .await
584    }
585
586    /// Reset password via email with optional redirect URL
587    pub async fn reset_password_for_email_with_redirect(
588        &self,
589        email: &str,
590        redirect_to: Option<String>,
591    ) -> Result<()> {
592        debug!("Requesting password reset for email: {}", email);
593
594        let payload = PasswordResetRequest {
595            email: email.to_string(),
596            redirect_to,
597        };
598
599        let response = self
600            .http_client
601            .post(format!("{}/auth/v1/recover", self.config.url))
602            .json(&payload)
603            .send()
604            .await?;
605
606        if !response.status().is_success() {
607            let status = response.status();
608            let error_msg = match response.text().await {
609                Ok(text) => text,
610                Err(_) => format!("Password reset failed with status: {}", status),
611            };
612            return Err(Error::auth(error_msg));
613        }
614
615        info!("Password reset email sent successfully");
616        Ok(())
617    }
618
619    /// Update the current user's information
620    pub async fn update_user(
621        &self,
622        email: Option<String>,
623        password: Option<String>,
624        data: Option<serde_json::Value>,
625    ) -> Result<AuthResponse> {
626        debug!("Updating user information");
627
628        let session = self.get_session()?;
629
630        let payload = UpdateUserRequest {
631            email,
632            password,
633            data,
634        };
635
636        let response = self
637            .http_client
638            .put(format!("{}/auth/v1/user", self.config.url))
639            .header("Authorization", format!("Bearer {}", session.access_token))
640            .json(&payload)
641            .send()
642            .await?;
643
644        if !response.status().is_success() {
645            let status = response.status();
646            let error_msg = match response.text().await {
647                Ok(text) => text,
648                Err(_) => format!("User update failed with status: {}", status),
649            };
650            return Err(Error::auth(error_msg));
651        }
652
653        let auth_response: AuthResponse = response.json().await?;
654
655        if let Some(ref session) = auth_response.session {
656            self.set_session(session.clone()).await?;
657        }
658
659        info!("User updated successfully");
660        Ok(auth_response)
661    }
662
663    /// Refresh the current session token
664    pub async fn refresh_session(&self) -> Result<AuthResponse> {
665        debug!("Refreshing session token");
666
667        let current_session = self.get_session()?;
668
669        let payload = RefreshTokenRequest {
670            refresh_token: current_session.refresh_token.clone(),
671        };
672
673        let response = self
674            .http_client
675            .post(format!(
676                "{}/auth/v1/token?grant_type=refresh_token",
677                self.config.url
678            ))
679            .json(&payload)
680            .send()
681            .await?;
682
683        if !response.status().is_success() {
684            let status = response.status();
685            let error_msg = match response.text().await {
686                Ok(text) => text,
687                Err(_) => format!("Token refresh failed with status: {}", status),
688            };
689            return Err(Error::auth(error_msg));
690        }
691
692        let auth_response_body = response.text().await?;
693
694        let mut auth_response = serde_json::from_str::<AuthResponse>(auth_response_body.as_str())?;
695        auth_response.session = serde_json::from_str::<Session>(auth_response_body.as_str())
696            .inspect_err(|err| warn!("No session: {}", err.to_string()))
697            .ok();
698
699        if let Some(ref session) = auth_response.session {
700            self.set_session(session.clone()).await?;
701            self.trigger_auth_event(AuthEvent::TokenRefreshed);
702            info!("Session refreshed successfully");
703        }
704
705        Ok(auth_response)
706    }
707
708    /// Get the current user information
709    pub async fn current_user(&self) -> Result<Option<User>> {
710        let session_guard = self
711            .session
712            .read()
713            .map_err(|_| Error::auth("Failed to read session"))?;
714        Ok(session_guard.as_ref().map(|s| s.user.clone()))
715    }
716
717    /// Get the current session
718    pub fn get_session(&self) -> Result<Session> {
719        let session_guard = self
720            .session
721            .read()
722            .map_err(|_| Error::auth("Failed to read session"))?;
723        session_guard
724            .as_ref()
725            .cloned()
726            .ok_or_else(|| Error::auth("No active session"))
727    }
728
729    /// Set a new session
730    pub async fn set_session(&self, session: Session) -> Result<()> {
731        let mut session_guard = self
732            .session
733            .write()
734            .map_err(|_| Error::auth("Failed to write session"))?;
735        *session_guard = Some(session);
736        Ok(())
737    }
738
739    /// Set session from JWT token
740    pub async fn set_session_token(&self, token: &str) -> Result<()> {
741        debug!("Setting session from token");
742
743        let user_response = self
744            .http_client
745            .get(format!("{}/auth/v1/user", self.config.url))
746            .header("Authorization", format!("Bearer {}", token))
747            .send()
748            .await?;
749
750        if !user_response.status().is_success() {
751            return Err(Error::auth("Invalid token"));
752        }
753
754        let user: User = user_response.json().await?;
755
756        let session = Session {
757            access_token: token.to_string(),
758            refresh_token: String::new(),
759            expires_in: 3600,
760            expires_at: Utc::now() + chrono::Duration::seconds(3600),
761            token_type: "bearer".to_string(),
762            user,
763        };
764
765        self.set_session(session).await?;
766        Ok(())
767    }
768
769    /// Clear the current session
770    pub async fn clear_session(&self) -> Result<()> {
771        let mut session_guard = self
772            .session
773            .write()
774            .map_err(|_| Error::auth("Failed to write session"))?;
775        *session_guard = None;
776        Ok(())
777    }
778
779    /// Check if the user is authenticated
780    pub fn is_authenticated(&self) -> bool {
781        let session_guard = self.session.read().unwrap_or_else(|_| {
782            warn!("Failed to read session lock");
783            self.session.read().unwrap()
784        });
785
786        match session_guard.as_ref() {
787            Some(session) => {
788                let now = Utc::now();
789                session.expires_at > now
790            }
791            None => false,
792        }
793    }
794
795    /// Check if the current token needs refresh
796    pub fn needs_refresh(&self) -> bool {
797        let session_guard = match self.session.read() {
798            Ok(guard) => guard,
799            Err(_) => {
800                warn!("Failed to read session lock");
801                return false;
802            }
803        };
804
805        match session_guard.as_ref() {
806            Some(session) => {
807                let now = Utc::now();
808                let buffer = chrono::Duration::minutes(5); // Refresh 5 minutes before expiry
809                session.expires_at < (now + buffer)
810            }
811            None => false,
812        }
813    }
814
815    /// Sign in with OAuth provider
816    ///
817    /// Returns a URL that the user should be redirected to for authentication.
818    /// After successful authentication, the user will be redirected back with the session.
819    ///
820    /// # Example
821    ///
822    /// ```rust
823    /// use supabase::auth::{OAuthProvider, OAuthOptions};
824    ///
825    /// # async fn example() -> supabase::Result<()> {
826    /// let client = supabase::Client::new("url", "key")?;
827    ///
828    /// let options = OAuthOptions {
829    ///     redirect_to: Some("https://myapp.com/callback".to_string()),
830    ///     scopes: Some(vec!["email".to_string(), "profile".to_string()]),
831    ///     ..Default::default()
832    /// };
833    ///
834    /// let response = client.auth().sign_in_with_oauth(OAuthProvider::Google, Some(options)).await?;
835    /// println!("Redirect to: {}", response.url);
836    /// # Ok(())
837    /// # }
838    /// ```
839    pub async fn sign_in_with_oauth(
840        &self,
841        provider: OAuthProvider,
842        options: Option<OAuthOptions>,
843    ) -> Result<OAuthResponse> {
844        debug!("Initiating OAuth sign-in with provider: {:?}", provider);
845
846        let mut url = format!(
847            "{}/auth/v1/authorize?provider={}",
848            self.config.url,
849            provider.as_str()
850        );
851
852        if let Some(opts) = options {
853            if let Some(redirect_to) = opts.redirect_to {
854                url.push_str(&format!(
855                    "&redirect_to={}",
856                    urlencoding::encode(&redirect_to)
857                ));
858            }
859
860            if let Some(scopes) = opts.scopes {
861                let scope_str = scopes.join(" ");
862                url.push_str(&format!("&scope={}", urlencoding::encode(&scope_str)));
863            }
864
865            if let Some(query_params) = opts.query_params {
866                for (key, value) in query_params {
867                    url.push_str(&format!(
868                        "&{}={}",
869                        urlencoding::encode(&key),
870                        urlencoding::encode(&value)
871                    ));
872                }
873            }
874        }
875
876        Ok(OAuthResponse { url })
877    }
878
879    /// Sign up with phone number
880    ///
881    /// # Example
882    ///
883    /// ```rust
884    /// # async fn example() -> supabase::Result<()> {
885    /// let client = supabase::Client::new("url", "key")?;
886    ///
887    /// let response = client.auth()
888    ///     .sign_up_with_phone("+1234567890", "securepassword", None)
889    ///     .await?;
890    ///
891    /// if let Some(user) = response.user {
892    ///     println!("User created: {:?}", user.phone);
893    /// }
894    /// # Ok(())
895    /// # }
896    /// ```
897    pub async fn sign_up_with_phone(
898        &self,
899        phone: &str,
900        password: &str,
901        data: Option<serde_json::Value>,
902    ) -> Result<AuthResponse> {
903        debug!("Signing up user with phone: {}", phone);
904
905        let payload = PhoneSignUpRequest {
906            phone: phone.to_string(),
907            password: password.to_string(),
908            data,
909        };
910
911        let response = self
912            .http_client
913            .post(format!("{}/auth/v1/signup", self.config.url))
914            .json(&payload)
915            .send()
916            .await?;
917
918        if !response.status().is_success() {
919            let status = response.status();
920            let error_msg = match response.text().await {
921                Ok(text) => text,
922                Err(_) => format!("Phone sign up failed with status: {}", status),
923            };
924            return Err(Error::auth(error_msg));
925        }
926
927        let auth_response: AuthResponse = response.json().await?;
928
929        if let Some(ref session) = auth_response.session {
930            self.set_session(session.clone()).await?;
931            self.trigger_auth_event(AuthEvent::SignedIn);
932            info!("User signed up with phone successfully");
933        }
934
935        Ok(auth_response)
936    }
937
938    /// Sign in with phone number
939    ///
940    /// # Example
941    ///
942    /// ```rust
943    /// # async fn example() -> supabase::Result<()> {
944    /// let client = supabase::Client::new("url", "key")?;
945    ///
946    /// let response = client.auth()
947    ///     .sign_in_with_phone("+1234567890", "securepassword")
948    ///     .await?;
949    ///
950    /// if let Some(user) = response.user {
951    ///     println!("User signed in: {:?}", user.phone);
952    /// }
953    /// # Ok(())
954    /// # }
955    /// ```
956    pub async fn sign_in_with_phone(&self, phone: &str, password: &str) -> Result<AuthResponse> {
957        debug!("Signing in user with phone: {}", phone);
958
959        let payload = PhoneSignInRequest {
960            phone: phone.to_string(),
961            password: password.to_string(),
962        };
963
964        let response = self
965            .http_client
966            .post(format!(
967                "{}/auth/v1/token?grant_type=password",
968                self.config.url
969            ))
970            .json(&payload)
971            .send()
972            .await?;
973
974        if !response.status().is_success() {
975            let status = response.status();
976            let error_msg = match response.text().await {
977                Ok(text) => text,
978                Err(_) => format!("Phone sign in failed with status: {}", status),
979            };
980            return Err(Error::auth(error_msg));
981        }
982
983        let auth_response_body = response.text().await?;
984
985        let mut auth_response = serde_json::from_str::<AuthResponse>(auth_response_body.as_str())?;
986        auth_response.session = serde_json::from_str::<Session>(auth_response_body.as_str())
987            .inspect_err(|err| warn!("No session: {}", err.to_string()))
988            .ok();
989
990        if let Some(ref session) = auth_response.session {
991            self.set_session(session.clone()).await?;
992            self.trigger_auth_event(AuthEvent::SignedIn);
993            info!("User signed in with phone successfully");
994        }
995
996        Ok(auth_response)
997    }
998
999    /// Verify OTP token
1000    ///
1001    /// # Example
1002    ///
1003    /// ```rust
1004    /// # async fn example() -> supabase::Result<()> {
1005    /// let client = supabase::Client::new("url", "key")?;
1006    ///
1007    /// let response = client.auth()
1008    ///     .verify_otp("+1234567890", "123456", "sms")
1009    ///     .await?;
1010    ///
1011    /// if let Some(session) = response.session {
1012    ///     println!("OTP verified, user signed in");
1013    /// }
1014    /// # Ok(())
1015    /// # }
1016    /// ```
1017    pub async fn verify_otp(
1018        &self,
1019        phone: &str,
1020        token: &str,
1021        verification_type: &str,
1022    ) -> Result<AuthResponse> {
1023        debug!("Verifying OTP for phone: {}", phone);
1024
1025        let payload = OTPVerificationRequest {
1026            phone: phone.to_string(),
1027            token: token.to_string(),
1028            verification_type: verification_type.to_string(),
1029        };
1030
1031        let response = self
1032            .http_client
1033            .post(format!("{}/auth/v1/verify", self.config.url))
1034            .json(&payload)
1035            .send()
1036            .await?;
1037
1038        if !response.status().is_success() {
1039            let status = response.status();
1040            let error_msg = match response.text().await {
1041                Ok(text) => text,
1042                Err(_) => format!("OTP verification failed with status: {}", status),
1043            };
1044            return Err(Error::auth(error_msg));
1045        }
1046
1047        let auth_response: AuthResponse = response.json().await?;
1048
1049        if let Some(ref session) = auth_response.session {
1050            self.set_session(session.clone()).await?;
1051            self.trigger_auth_event(AuthEvent::SignedIn);
1052            info!("OTP verified successfully");
1053        }
1054
1055        Ok(auth_response)
1056    }
1057
1058    /// Send magic link for passwordless authentication
1059    ///
1060    /// # Example
1061    ///
1062    /// ```rust
1063    /// # async fn example() -> supabase::Result<()> {
1064    /// let client = supabase::Client::new("url", "key")?;
1065    ///
1066    /// client.auth()
1067    ///     .sign_in_with_magic_link("user@example.com", Some("https://myapp.com/callback".to_string()), None)
1068    ///     .await?;
1069    ///
1070    /// println!("Magic link sent to email");
1071    /// # Ok(())
1072    /// # }
1073    /// ```
1074    pub async fn sign_in_with_magic_link(
1075        &self,
1076        email: &str,
1077        redirect_to: Option<String>,
1078        data: Option<serde_json::Value>,
1079    ) -> Result<()> {
1080        debug!("Sending magic link to email: {}", email);
1081
1082        let payload = MagicLinkRequest {
1083            email: email.to_string(),
1084            redirect_to,
1085            data,
1086        };
1087
1088        let response = self
1089            .http_client
1090            .post(format!("{}/auth/v1/magiclink", self.config.url))
1091            .json(&payload)
1092            .send()
1093            .await?;
1094
1095        if !response.status().is_success() {
1096            let status = response.status();
1097            let error_msg = match response.text().await {
1098                Ok(text) => text,
1099                Err(_) => format!("Magic link request failed with status: {}", status),
1100            };
1101            return Err(Error::auth(error_msg));
1102        }
1103
1104        info!("Magic link sent successfully");
1105        Ok(())
1106    }
1107
1108    /// Sign in anonymously
1109    ///
1110    /// Creates a temporary anonymous user session that can be converted to a permanent account later.
1111    ///
1112    /// # Example
1113    ///
1114    /// ```rust
1115    /// # async fn example() -> supabase::Result<()> {
1116    /// let client = supabase::Client::new("url", "key")?;
1117    ///
1118    /// let response = client.auth()
1119    ///     .sign_in_anonymously(None)
1120    ///     .await?;
1121    ///
1122    /// if let Some(user) = response.user {
1123    ///     println!("Anonymous user created: {}", user.id);
1124    /// }
1125    /// # Ok(())
1126    /// # }
1127    /// ```
1128    pub async fn sign_in_anonymously(
1129        &self,
1130        data: Option<serde_json::Value>,
1131    ) -> Result<AuthResponse> {
1132        debug!("Creating anonymous user session");
1133
1134        let payload = AnonymousSignInRequest { data };
1135
1136        let response = self
1137            .http_client
1138            .post(format!("{}/auth/v1/signup", self.config.url))
1139            .header("Authorization", format!("Bearer {}", self.config.key))
1140            .json(&payload)
1141            .send()
1142            .await?;
1143
1144        if !response.status().is_success() {
1145            let status = response.status();
1146            let error_msg = match response.text().await {
1147                Ok(text) => text,
1148                Err(_) => format!("Anonymous sign in failed with status: {}", status),
1149            };
1150            return Err(Error::auth(error_msg));
1151        }
1152
1153        let auth_response: AuthResponse = response.json().await?;
1154
1155        if let Some(ref session) = auth_response.session {
1156            self.set_session(session.clone()).await?;
1157            self.trigger_auth_event(AuthEvent::SignedIn);
1158            info!("Anonymous user session created successfully");
1159        }
1160
1161        Ok(auth_response)
1162    }
1163
1164    /// Enhanced password recovery with custom redirect and options
1165    ///
1166    /// # Example
1167    ///
1168    /// ```rust
1169    /// # async fn example() -> supabase::Result<()> {
1170    /// let client = supabase::Client::new("url", "key")?;
1171    ///
1172    /// client.auth()
1173    ///     .reset_password_for_email_enhanced("user@example.com", Some("https://myapp.com/reset".to_string()))
1174    ///     .await?;
1175    ///
1176    /// println!("Password reset email sent");
1177    /// # Ok(())
1178    /// # }
1179    /// ```
1180    pub async fn reset_password_for_email_enhanced(
1181        &self,
1182        email: &str,
1183        redirect_to: Option<String>,
1184    ) -> Result<()> {
1185        debug!("Initiating enhanced password recovery for email: {}", email);
1186
1187        let payload = PasswordResetRequest {
1188            email: email.to_string(),
1189            redirect_to,
1190        };
1191
1192        let response = self
1193            .http_client
1194            .post(format!("{}/auth/v1/recover", self.config.url))
1195            .json(&payload)
1196            .send()
1197            .await?;
1198
1199        if !response.status().is_success() {
1200            let status = response.status();
1201            let error_msg = match response.text().await {
1202                Ok(text) => text,
1203                Err(_) => format!("Enhanced password recovery failed with status: {}", status),
1204            };
1205            return Err(Error::auth(error_msg));
1206        }
1207
1208        self.trigger_auth_event(AuthEvent::PasswordReset);
1209        info!("Enhanced password recovery email sent successfully");
1210        Ok(())
1211    }
1212
1213    /// Subscribe to authentication state changes
1214    ///
1215    /// Returns a handle that can be used to remove the listener later.
1216    ///
1217    /// # Example
1218    ///
1219    /// ```rust
1220    /// use supabase::auth::AuthEvent;
1221    ///
1222    /// # async fn example() -> supabase::Result<()> {
1223    /// let client = supabase::Client::new("url", "key")?;
1224    ///
1225    /// let handle = client.auth().on_auth_state_change(|event, session| {
1226    ///     match event {
1227    ///         AuthEvent::SignedIn => {
1228    ///             if let Some(session) = session {
1229    ///                 println!("User signed in: {}", session.user.email.unwrap_or_default());
1230    ///             }
1231    ///         }
1232    ///         AuthEvent::SignedOut => println!("User signed out"),
1233    ///         AuthEvent::TokenRefreshed => println!("Token refreshed"),
1234    ///         _ => {}
1235    ///     }
1236    /// });
1237    ///
1238    /// // Later remove the listener
1239    /// handle.remove();
1240    /// # Ok(())
1241    /// # }
1242    /// ```
1243    pub fn on_auth_state_change<F>(&self, callback: F) -> AuthEventHandle
1244    where
1245        F: Fn(AuthEvent, Option<Session>) + Send + Sync + 'static,
1246    {
1247        let id = Uuid::new_v4();
1248        let callback = Box::new(callback);
1249
1250        if let Ok(mut listeners) = self.event_listeners.write() {
1251            listeners.insert(id, callback);
1252        }
1253
1254        AuthEventHandle {
1255            id,
1256            auth: Arc::downgrade(&Arc::new(self.clone())),
1257        }
1258    }
1259
1260    /// Remove an authentication state listener
1261    pub fn remove_auth_listener(&self, id: Uuid) {
1262        if let Ok(mut listeners) = self.event_listeners.write() {
1263            listeners.remove(&id);
1264        }
1265    }
1266
1267    /// Trigger authentication state change event
1268    fn trigger_auth_event(&self, event: AuthEvent) {
1269        let session = match self.session.read() {
1270            Ok(guard) => guard.clone(),
1271            Err(_) => {
1272                warn!("Failed to read session for event trigger");
1273                return;
1274            }
1275        };
1276
1277        let listeners = match self.event_listeners.read() {
1278            Ok(guard) => guard,
1279            Err(_) => {
1280                warn!("Failed to read event listeners");
1281                return;
1282            }
1283        };
1284
1285        for callback in listeners.values() {
1286            callback(event.clone(), session.clone());
1287        }
1288    }
1289
1290    // ==== MFA (Multi-Factor Authentication) Methods ====
1291
1292    /// List all MFA factors for the current user
1293    ///
1294    /// # Examples
1295    ///
1296    /// ```rust,no_run
1297    /// # use supabase::Client;
1298    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1299    /// let client = Client::new("https://example.supabase.co", "your-anon-key")?;
1300    ///
1301    /// // List MFA factors
1302    /// let factors = client.auth().list_mfa_factors().await?;
1303    /// println!("User has {} MFA factors configured", factors.len());
1304    /// # Ok(())
1305    /// # }
1306    /// ```
1307    pub async fn list_mfa_factors(&self) -> Result<Vec<MfaFactor>> {
1308        debug!("Listing MFA factors for user");
1309
1310        let session = self.get_session()?;
1311        let response = self
1312            .http_client
1313            .get(format!("{}/auth/v1/factors", self.config.url))
1314            .header("Authorization", format!("Bearer {}", session.access_token))
1315            .send()
1316            .await?;
1317
1318        if !response.status().is_success() {
1319            return Err(Error::auth("Failed to list MFA factors"));
1320        }
1321
1322        let factors: Vec<MfaFactor> = response.json().await?;
1323        Ok(factors)
1324    }
1325
1326    /// Setup TOTP (Time-based One-Time Password) authentication
1327    ///
1328    /// # Examples
1329    ///
1330    /// ```rust,no_run
1331    /// # use supabase::Client;
1332    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1333    /// let client = Client::new("https://example.supabase.co", "your-anon-key")?;
1334    ///
1335    /// // Setup TOTP
1336    /// let totp_setup = client.auth()
1337    ///     .setup_totp("My Authenticator")
1338    ///     .await?;
1339    ///
1340    /// println!("Scan QR code: {}", totp_setup.qr_code);
1341    /// println!("Or enter secret manually: {}", totp_setup.secret);
1342    /// # Ok(())
1343    /// # }
1344    /// ```
1345    pub async fn setup_totp(&self, friendly_name: &str) -> Result<TotpSetupResponse> {
1346        debug!("Setting up TOTP factor: {}", friendly_name);
1347
1348        let session = self.get_session()?;
1349
1350        let request_body = serde_json::json!({
1351            "friendly_name": friendly_name,
1352            "factor_type": "totp"
1353        });
1354
1355        let response = self
1356            .http_client
1357            .post(format!("{}/auth/v1/factors", self.config.url))
1358            .header("Authorization", format!("Bearer {}", session.access_token))
1359            .json(&request_body)
1360            .send()
1361            .await?;
1362
1363        if !response.status().is_success() {
1364            return Err(Error::auth("Failed to setup TOTP"));
1365        }
1366
1367        let setup_response: TotpSetupResponse = response.json().await?;
1368
1369        // Generate QR code for the TOTP URI
1370        let qr = QrCode::new(&setup_response.uri)
1371            .map_err(|e| Error::auth(format!("Failed to generate QR code: {}", e)))?;
1372
1373        // Convert QR code to string representation (for console display)
1374        let qr_string = qr
1375            .render::<char>()
1376            .quiet_zone(false)
1377            .module_dimensions(2, 1)
1378            .build();
1379
1380        Ok(TotpSetupResponse {
1381            secret: setup_response.secret,
1382            qr_code: qr_string,
1383            uri: setup_response.uri,
1384            factor_id: setup_response.factor_id,
1385        })
1386    }
1387
1388    /// Setup SMS-based MFA with international phone number support
1389    ///
1390    /// # Examples
1391    ///
1392    /// ```rust,no_run
1393    /// # use supabase::Client;
1394    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1395    /// let client = Client::new("https://example.supabase.co", "your-anon-key")?;
1396    ///
1397    /// // Setup SMS MFA with international number
1398    /// let factor = client.auth()
1399    ///     .setup_sms_mfa("+1-555-123-4567", "My Phone", Some("US"))
1400    ///     .await?;
1401    ///
1402    /// println!("SMS MFA configured for: {}", factor.phone.unwrap());
1403    /// # Ok(())
1404    /// # }
1405    /// ```
1406    pub async fn setup_sms_mfa(
1407        &self,
1408        phone: &str,
1409        friendly_name: &str,
1410        default_region: Option<&str>,
1411    ) -> Result<MfaFactor> {
1412        debug!("Setting up SMS MFA factor: {} for {}", friendly_name, phone);
1413
1414        // Validate and format phone number
1415        let enhanced_phone = EnhancedPhoneNumber::new(phone, default_region)?;
1416
1417        if !enhanced_phone.is_valid {
1418            return Err(Error::auth("Invalid phone number provided"));
1419        }
1420
1421        let session = self.get_session()?;
1422
1423        let request_body = serde_json::json!({
1424            "friendly_name": friendly_name,
1425            "factor_type": "sms",
1426            "phone": enhanced_phone.formatted
1427        });
1428
1429        let response = self
1430            .http_client
1431            .post(format!("{}/auth/v1/factors", self.config.url))
1432            .header("Authorization", format!("Bearer {}", session.access_token))
1433            .json(&request_body)
1434            .send()
1435            .await?;
1436
1437        if !response.status().is_success() {
1438            return Err(Error::auth("Failed to setup SMS MFA"));
1439        }
1440
1441        let factor: MfaFactor = response.json().await?;
1442        self.trigger_auth_event(AuthEvent::MfaEnabled);
1443
1444        Ok(factor)
1445    }
1446
1447    /// Create MFA challenge for a specific factor
1448    ///
1449    /// # Examples
1450    ///
1451    /// ```rust,no_run
1452    /// # use supabase::Client;
1453    /// # use uuid::Uuid;
1454    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1455    /// let client = Client::new("https://example.supabase.co", "your-anon-key")?;
1456    ///
1457    /// // Get factors and create challenge
1458    /// let factors = client.auth().list_mfa_factors().await?;
1459    /// if let Some(factor) = factors.first() {
1460    ///     let challenge = client.auth().create_mfa_challenge(factor.id).await?;
1461    ///     println!("Challenge created: {}", challenge.id);
1462    /// }
1463    /// # Ok(())
1464    /// # }
1465    /// ```
1466    pub async fn create_mfa_challenge(&self, factor_id: Uuid) -> Result<MfaChallenge> {
1467        debug!("Creating MFA challenge for factor: {}", factor_id);
1468
1469        let session = self.get_session()?;
1470
1471        let request_body = serde_json::json!({
1472            "factor_id": factor_id
1473        });
1474
1475        let response = self
1476            .http_client
1477            .post(format!(
1478                "{}/auth/v1/factors/{}/challenge",
1479                self.config.url, factor_id
1480            ))
1481            .header("Authorization", format!("Bearer {}", session.access_token))
1482            .json(&request_body)
1483            .send()
1484            .await?;
1485
1486        if !response.status().is_success() {
1487            return Err(Error::auth("Failed to create MFA challenge"));
1488        }
1489
1490        let challenge: MfaChallenge = response.json().await?;
1491        self.trigger_auth_event(AuthEvent::MfaChallengeRequired);
1492
1493        Ok(challenge)
1494    }
1495
1496    /// Verify MFA challenge with user-provided code
1497    ///
1498    /// # Examples
1499    ///
1500    /// ```rust,no_run
1501    /// # use supabase::Client;
1502    /// # use uuid::Uuid;
1503    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1504    /// let client = Client::new("https://example.supabase.co", "your-anon-key")?;
1505    ///
1506    /// // Verify MFA code
1507    /// let factor_id = Uuid::new_v4(); // Your factor ID
1508    /// let challenge_id = Uuid::new_v4(); // Your challenge ID
1509    ///
1510    /// let result = client.auth()
1511    ///     .verify_mfa_challenge(factor_id, challenge_id, "123456")
1512    ///     .await?;
1513    ///
1514    /// println!("MFA verified successfully!");
1515    /// # Ok(())
1516    /// # }
1517    /// ```
1518    pub async fn verify_mfa_challenge(
1519        &self,
1520        factor_id: Uuid,
1521        challenge_id: Uuid,
1522        code: &str,
1523    ) -> Result<AuthResponse> {
1524        debug!("Verifying MFA challenge: {}", challenge_id);
1525
1526        let session = self.get_session()?;
1527
1528        let request_body = serde_json::json!({
1529            "factor_id": factor_id,
1530            "challenge_id": challenge_id,
1531            "code": code
1532        });
1533
1534        let response = self
1535            .http_client
1536            .post(format!(
1537                "{}/auth/v1/factors/{}/verify",
1538                self.config.url, factor_id
1539            ))
1540            .header("Authorization", format!("Bearer {}", session.access_token))
1541            .json(&request_body)
1542            .send()
1543            .await?;
1544
1545        if !response.status().is_success() {
1546            return Err(Error::auth("Failed to verify MFA challenge"));
1547        }
1548
1549        let auth_response: AuthResponse = response.json().await?;
1550
1551        // Update session if new token provided
1552        if let Some(session) = &auth_response.session {
1553            self.set_session(session.clone()).await?;
1554        }
1555
1556        self.trigger_auth_event(AuthEvent::MfaChallengeCompleted);
1557        info!("MFA verification successful");
1558
1559        Ok(auth_response)
1560    }
1561
1562    /// Delete an MFA factor
1563    ///
1564    /// # Examples
1565    ///
1566    /// ```rust,no_run
1567    /// # use supabase::Client;
1568    /// # use uuid::Uuid;
1569    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1570    /// let client = Client::new("https://example.supabase.co", "your-anon-key")?;
1571    ///
1572    /// // Remove MFA factor
1573    /// let factor_id = Uuid::new_v4(); // Your factor ID
1574    /// client.auth().delete_mfa_factor(factor_id).await?;
1575    /// println!("MFA factor removed successfully!");
1576    /// # Ok(())
1577    /// # }
1578    /// ```
1579    pub async fn delete_mfa_factor(&self, factor_id: Uuid) -> Result<()> {
1580        debug!("Deleting MFA factor: {}", factor_id);
1581
1582        let session = self.get_session()?;
1583
1584        let response = self
1585            .http_client
1586            .delete(format!("{}/auth/v1/factors/{}", self.config.url, factor_id))
1587            .header("Authorization", format!("Bearer {}", session.access_token))
1588            .send()
1589            .await?;
1590
1591        if !response.status().is_success() {
1592            return Err(Error::auth("Failed to delete MFA factor"));
1593        }
1594
1595        self.trigger_auth_event(AuthEvent::MfaDisabled);
1596        info!("MFA factor deleted successfully");
1597
1598        Ok(())
1599    }
1600
1601    /// Generate TOTP code for testing purposes (development only)
1602    ///
1603    /// This method is primarily for testing and development. In production,
1604    /// users should use their authenticator apps.
1605    ///
1606    /// # Examples
1607    ///
1608    /// ```rust,no_run
1609    /// # use supabase::Client;
1610    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1611    /// let client = Client::new("https://example.supabase.co", "your-anon-key")?;
1612    ///
1613    /// // For testing - generate TOTP code from secret
1614    /// let secret = "JBSWY3DPEHPK3PXP"; // Base32 encoded secret
1615    /// let code = client.auth().generate_totp_code(secret)?;
1616    /// println!("Generated TOTP code: {}", code);
1617    /// # Ok(())
1618    /// # }
1619    /// ```
1620    pub fn generate_totp_code(&self, secret: &str) -> Result<String> {
1621        debug!("Generating TOTP code for testing");
1622
1623        // Decode base32 secret
1624        let decoded_secret = base32::decode(base32::Alphabet::Rfc4648 { padding: true }, secret)
1625            .ok_or_else(|| Error::auth("Invalid base32 secret"))?;
1626
1627        // Create TOTP
1628        let totp = TOTP::new(
1629            Algorithm::SHA1,
1630            6,  // digits
1631            1,  // skew (allow 1 step tolerance)
1632            30, // step (30 seconds)
1633            decoded_secret,
1634        )
1635        .map_err(|e| Error::auth(format!("Failed to create TOTP: {}", e)))?;
1636
1637        // Generate current code
1638        let code = totp
1639            .generate_current()
1640            .map_err(|e| Error::auth(format!("Failed to generate TOTP code: {}", e)))?;
1641
1642        Ok(code)
1643    }
1644
1645    // ==== Advanced OAuth Token Management ====
1646
1647    /// Get current token metadata with advanced information
1648    ///
1649    /// # Examples
1650    ///
1651    /// ```rust,no_run
1652    /// # use supabase::Client;
1653    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1654    /// let client = Client::new("https://example.supabase.co", "your-anon-key")?;
1655    ///
1656    /// // Get detailed token metadata
1657    /// if let Some(metadata) = client.auth().get_token_metadata()? {
1658    ///     println!("Token expires at: {}", metadata.expires_at);
1659    ///     println!("Refresh count: {}", metadata.refresh_count);
1660    ///     println!("Scopes: {:?}", metadata.scopes);
1661    /// }
1662    /// # Ok(())
1663    /// # }
1664    /// ```
1665    pub fn get_token_metadata(&self) -> Result<Option<TokenMetadata>> {
1666        debug!("Getting token metadata");
1667
1668        let session = self
1669            .session
1670            .read()
1671            .map_err(|_| Error::auth("Failed to read session"))?;
1672
1673        if let Some(session) = session.as_ref() {
1674            // Create metadata from current session
1675            let metadata = TokenMetadata {
1676                issued_at: Utc::now() - chrono::Duration::seconds(session.expires_in),
1677                expires_at: session.expires_at,
1678                refresh_count: 0, // TODO: Track this in enhanced session
1679                last_refresh_at: None,
1680                scopes: vec![],  // TODO: Extract from JWT
1681                device_id: None, // TODO: Add device tracking
1682            };
1683
1684            Ok(Some(metadata))
1685        } else {
1686            Ok(None)
1687        }
1688    }
1689
1690    /// Refresh token with advanced error handling and retry logic
1691    ///
1692    /// # Examples
1693    ///
1694    /// ```rust,no_run
1695    /// # use supabase::Client;
1696    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1697    /// let client = Client::new("https://example.supabase.co", "your-anon-key")?;
1698    ///
1699    /// // Refresh token with advanced handling
1700    /// match client.auth().refresh_token_advanced().await {
1701    ///     Ok(session) => {
1702    ///         println!("Token refreshed successfully!");
1703    ///         println!("New expiry: {}", session.expires_at);
1704    ///     }
1705    ///     Err(e) => {
1706    ///         if e.is_retryable() {
1707    ///             // Handle retryable error
1708    ///             println!("Retryable error: {}", e);
1709    ///         } else {
1710    ///             // Handle non-retryable error - require re-login
1711    ///             println!("Re-authentication required: {}", e);
1712    ///         }
1713    ///     }
1714    /// }
1715    /// # Ok(())
1716    /// # }
1717    /// ```
1718    pub async fn refresh_token_advanced(&self) -> Result<Session> {
1719        debug!("Refreshing token with advanced handling");
1720
1721        let current_session = self
1722            .session
1723            .read()
1724            .map_err(|_| Error::auth("Failed to read session"))?
1725            .clone();
1726
1727        let session = match current_session {
1728            Some(session) => session,
1729            None => return Err(Error::auth("No active session to refresh")),
1730        };
1731
1732        let request_body = serde_json::json!({
1733            "refresh_token": session.refresh_token
1734        });
1735
1736        let response = self
1737            .http_client
1738            .post(format!(
1739                "{}/auth/v1/token?grant_type=refresh_token",
1740                self.config.url
1741            ))
1742            .header("apikey", &self.config.key)
1743            .header("Authorization", format!("Bearer {}", &self.config.key))
1744            .json(&request_body)
1745            .send()
1746            .await;
1747
1748        match response {
1749            Ok(response) => {
1750                if response.status().is_success() {
1751                    let auth_response_body = response.text().await?;
1752
1753                    let mut auth_response =
1754                        serde_json::from_str::<AuthResponse>(auth_response_body.as_str())?;
1755                    auth_response.session =
1756                        serde_json::from_str::<Session>(auth_response_body.as_str())
1757                            .inspect_err(|err| warn!("No session: {}", err.to_string()))
1758                            .ok();
1759
1760                    if let Some(new_session) = auth_response.session {
1761                        self.set_session(new_session.clone()).await?;
1762                        self.trigger_auth_event(AuthEvent::TokenRefreshed);
1763                        info!("Token refreshed successfully");
1764                        Ok(new_session)
1765                    } else {
1766                        Err(Error::auth("No session in refresh response"))
1767                    }
1768                } else {
1769                    let status = response.status();
1770                    let error_text = response.text().await.unwrap_or_default();
1771
1772                    // Provide specific error context
1773                    let context = crate::error::ErrorContext {
1774                        platform: Some(crate::error::detect_platform_context()),
1775                        http: Some(crate::error::HttpErrorContext {
1776                            status_code: Some(status.as_u16()),
1777                            headers: None,
1778                            response_body: Some(error_text.clone()),
1779                            url: Some(format!("{}/auth/v1/token", self.config.url)),
1780                            method: Some("POST".to_string()),
1781                        }),
1782                        retry: if status.is_server_error() {
1783                            Some(crate::error::RetryInfo {
1784                                retryable: true,
1785                                retry_after: Some(60), // 1 minute
1786                                attempts: 0,
1787                            })
1788                        } else {
1789                            None
1790                        },
1791                        metadata: std::collections::HashMap::new(),
1792                        timestamp: chrono::Utc::now(),
1793                    };
1794
1795                    Err(Error::auth_with_context(
1796                        format!("Token refresh failed: {} - {}", status, error_text),
1797                        context,
1798                    ))
1799                }
1800            }
1801            Err(e) => {
1802                let context = crate::error::ErrorContext {
1803                    platform: Some(crate::error::detect_platform_context()),
1804                    http: Some(crate::error::HttpErrorContext {
1805                        status_code: None,
1806                        headers: None,
1807                        response_body: None,
1808                        url: Some(format!("{}/auth/v1/token", self.config.url)),
1809                        method: Some("POST".to_string()),
1810                    }),
1811                    retry: Some(crate::error::RetryInfo {
1812                        retryable: true,
1813                        retry_after: Some(30),
1814                        attempts: 0,
1815                    }),
1816                    metadata: std::collections::HashMap::new(),
1817                    timestamp: chrono::Utc::now(),
1818                };
1819
1820                Err(Error::auth_with_context(
1821                    format!("Network error during token refresh: {}", e),
1822                    context,
1823                ))
1824            }
1825        }
1826    }
1827
1828    /// Check if current token needs refresh with buffer time
1829    ///
1830    /// # Examples
1831    ///
1832    /// ```rust,no_run
1833    /// # use supabase::Client;
1834    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1835    /// let client = Client::new("https://example.supabase.co", "your-anon-key")?;
1836    ///
1837    /// // Check if refresh is needed with 5-minute buffer
1838    /// if client.auth().needs_refresh_with_buffer(300)? {
1839    ///     println!("Token refresh recommended");
1840    ///     client.auth().refresh_token_advanced().await?;
1841    /// }
1842    /// # Ok(())
1843    /// # }
1844    /// ```
1845    pub fn needs_refresh_with_buffer(&self, buffer_seconds: i64) -> Result<bool> {
1846        let session_guard = self
1847            .session
1848            .read()
1849            .map_err(|_| Error::auth("Failed to read session"))?;
1850
1851        match session_guard.as_ref() {
1852            Some(session) => {
1853                let now = Utc::now();
1854                let refresh_threshold =
1855                    session.expires_at - chrono::Duration::seconds(buffer_seconds);
1856                Ok(now >= refresh_threshold)
1857            }
1858            None => Ok(false), // No session, no need to refresh
1859        }
1860    }
1861
1862    /// Get time until token expiry in seconds
1863    ///
1864    /// # Examples
1865    ///
1866    /// ```rust,no_run
1867    /// # use supabase::Client;
1868    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1869    /// let client = Client::new("https://example.supabase.co", "your-anon-key")?;
1870    ///
1871    /// match client.auth().time_until_expiry()? {
1872    ///     Some(seconds) => {
1873    ///         println!("Token expires in {} seconds", seconds);
1874    ///         if seconds < 300 {
1875    ///             println!("Consider refreshing token soon!");
1876    ///         }
1877    ///     }
1878    ///     None => println!("No active session"),
1879    /// }
1880    /// # Ok(())
1881    /// # }
1882    /// ```
1883    pub fn time_until_expiry(&self) -> Result<Option<i64>> {
1884        let session_guard = self
1885            .session
1886            .read()
1887            .map_err(|_| Error::auth("Failed to read session"))?;
1888
1889        match session_guard.as_ref() {
1890            Some(session) => {
1891                let now = Utc::now();
1892                let duration = session.expires_at.signed_duration_since(now);
1893                Ok(Some(duration.num_seconds()))
1894            }
1895            None => Ok(None),
1896        }
1897    }
1898
1899    /// Validate current token without making API call (local validation)
1900    ///
1901    /// # Examples
1902    ///
1903    /// ```rust,no_run
1904    /// # use supabase::Client;
1905    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1906    /// let client = Client::new("https://example.supabase.co", "your-anon-key")?;
1907    ///
1908    /// match client.auth().validate_token_local() {
1909    ///     Ok(true) => println!("Token is valid locally"),
1910    ///     Ok(false) => println!("Token is expired or invalid"),
1911    ///     Err(e) => println!("Validation error: {}", e),
1912    /// }
1913    /// # Ok(())
1914    /// # }
1915    /// ```
1916    pub fn validate_token_local(&self) -> Result<bool> {
1917        let session_guard = self
1918            .session
1919            .read()
1920            .map_err(|_| Error::auth("Failed to read session"))?;
1921
1922        match session_guard.as_ref() {
1923            Some(session) => {
1924                let now = Utc::now();
1925                Ok(session.expires_at > now && !session.access_token.is_empty())
1926            }
1927            None => Ok(false),
1928        }
1929    }
1930}
1931
1932#[cfg(test)]
1933mod tests {
1934    use super::*;
1935    use crate::types::SupabaseConfig;
1936    use std::sync::Arc;
1937
1938    fn mock_config() -> Arc<SupabaseConfig> {
1939        Arc::new(SupabaseConfig {
1940            url: "https://test.supabase.co".to_string(),
1941            key: "test-key".to_string(),
1942            service_role_key: None,
1943            http_config: crate::types::HttpConfig::default(),
1944            auth_config: crate::types::AuthConfig::default(),
1945            database_config: crate::types::DatabaseConfig::default(),
1946            storage_config: crate::types::StorageConfig::default(),
1947        })
1948    }
1949
1950    #[test]
1951    fn test_enhanced_phone_number_creation() {
1952        let phone = EnhancedPhoneNumber::new("+1-555-123-4567", Some("US"));
1953        assert!(phone.is_ok());
1954
1955        let phone = phone.unwrap();
1956        assert!(!phone.raw.is_empty());
1957        assert!(!phone.formatted.is_empty());
1958        assert!(!phone.country_code.is_empty());
1959    }
1960
1961    #[test]
1962    fn test_mfa_method_serialization() {
1963        let totp = MfaMethod::Totp;
1964        let sms = MfaMethod::Sms;
1965        let email = MfaMethod::Email;
1966
1967        let totp_json = serde_json::to_string(&totp).unwrap();
1968        let sms_json = serde_json::to_string(&sms).unwrap();
1969        let email_json = serde_json::to_string(&email).unwrap();
1970
1971        assert_eq!(totp_json, r#""totp""#);
1972        assert_eq!(sms_json, r#""sms""#);
1973        assert_eq!(email_json, r#""email""#);
1974    }
1975
1976    #[test]
1977    fn test_mfa_challenge_status() {
1978        let pending = MfaChallengeStatus::Pending;
1979        let completed = MfaChallengeStatus::Completed;
1980        let expired = MfaChallengeStatus::Expired;
1981        let cancelled = MfaChallengeStatus::Cancelled;
1982
1983        assert_eq!(pending, MfaChallengeStatus::Pending);
1984        assert_eq!(completed, MfaChallengeStatus::Completed);
1985        assert_eq!(expired, MfaChallengeStatus::Expired);
1986        assert_eq!(cancelled, MfaChallengeStatus::Cancelled);
1987    }
1988
1989    #[test]
1990    fn test_auth_event_variants() {
1991        let events = vec![
1992            AuthEvent::SignedIn,
1993            AuthEvent::SignedOut,
1994            AuthEvent::TokenRefreshed,
1995            AuthEvent::UserUpdated,
1996            AuthEvent::PasswordReset,
1997            AuthEvent::MfaChallengeRequired,
1998            AuthEvent::MfaChallengeCompleted,
1999            AuthEvent::MfaEnabled,
2000            AuthEvent::MfaDisabled,
2001        ];
2002
2003        assert_eq!(events.len(), 9);
2004
2005        // Test cloning
2006        let cloned_events: Vec<AuthEvent> = events.to_vec();
2007        assert_eq!(cloned_events, events);
2008    }
2009
2010    #[test]
2011    fn test_parsing_auth_from_response() {
2012        let json_body = r#"{
2013    "access_token": "eyJhbGciOiJIUzI1NiIsImtpZCI6IkhxWTFsZ3pmbGhOQUx3NTAiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3Fiamx4cWFyb2l0eHNvdml3bmRsLnN1cGFiYXNlLmNvL2F1dGgvdjEiLCJzdWIiOiIzMTgxNWM1NS1mNTUzLTQxZjQtYjU0Zi0xNGQ2YWM2MGRlMTYiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzYwMDQ1MTk1LCJpYXQiOjE3NjAwNDE1OTUsImVtYWlsIjoic29tZW9uZUBlbWFpbC5jb20iLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIiwicHJvdmlkZXJzIjpbImVtYWlsIl19LCJ1c2VyX21ldGFkYXRhIjp7ImVtYWlsIjoic29tZW9uZUBlbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwicGhvbmVfdmVyaWZpZWQiOmZhbHNlLCJzdWIiOiIzMTgxNWM1NS1mNTUzLTQxZjQtYjU0Zi0xNGQ2YWM2MGRlMTYifSwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MTc2MDA0MTU5NX1dLCJzZXNzaW9uX2lkIjoiMzc0OTc0OGUtMmUyMy00Nzk0LTllNmQtNjg0MzU5ZDc3M2RjIiwiaXNfYW5vbnltb3VzIjpmYWxzZX0.HCNEZQjnpzBkfEJ_6gJwxcfubRDGez8SRlM6Ni63X_k",
2014    "token_type": "bearer",
2015    "expires_in": 3600,
2016    "expires_at": 1760045195,
2017    "refresh_token": "pwou3cigit5u",
2018    "user": {
2019        "id": "31815c55-f553-41f4-b54f-14d6ac60de16",
2020        "aud": "authenticated",
2021        "role": "authenticated",
2022        "email": "someone@email.com",
2023        "email_confirmed_at": "2025-10-09T14:53:09.962028Z",
2024        "phone": "",
2025        "confirmed_at": "2025-10-09T14:53:09.962028Z",
2026        "last_sign_in_at": "2025-10-09T20:26:35.136870325Z",
2027        "app_metadata": {
2028            "provider": "email",
2029            "providers": ["email"]
2030        },
2031        "user_metadata": {
2032            "email": "someone@email.com",
2033            "email_verified": true,
2034            "phone_verified": false,
2035            "sub": "31815c55-f553-41f4-b54f-14d6ac60de16"
2036        },
2037        "identities": [{
2038            "identity_id": "5fe7caa2-1dc3-449b-b910-33bd7df0d616",
2039            "id": "31815c55-f553-41f4-b54f-14d6ac60de16",
2040            "user_id": "31815c55-f553-41f4-b54f-14d6ac60de16",
2041            "identity_data": {
2042                "email": "someone@email.com",
2043                "email_verified": false,
2044                "phone_verified": false,
2045                "sub": "31815c55-f553-41f4-b54f-14d6ac60de16"
2046            },
2047            "provider": "email",
2048            "last_sign_in_at": "2025-10-09T14:53:09.958178Z",
2049            "created_at": "2025-10-09T14:53:09.958225Z",
2050            "updated_at": "2025-10-09T14:53:09.958225Z",
2051            "email": "someone@email.com"
2052        }],
2053        "created_at": "2025-10-09T14:53:09.953727Z",
2054        "updated_at": "2025-10-09T20:26:35.13964Z",
2055        "is_anonymous": false
2056    },
2057    "weak_password": null
2058}"#;
2059        let mut auth = serde_json::from_str::<AuthResponse>(json_body).unwrap();
2060        assert!(auth.session.is_none());
2061
2062        auth.session = serde_json::from_str::<Session>(json_body).ok();
2063        assert!(auth.session.is_some());
2064    }
2065
2066    #[test]
2067    fn test_parsing_auth_with_missing_session() {
2068        let json_body = r#"{
2069    "user": {
2070        "id": "31815c55-f553-41f4-b54f-14d6ac60de16",
2071        "aud": "authenticated",
2072        "role": "authenticated",
2073        "email": "someone@email.com",
2074        "email_confirmed_at": "2025-10-09T14:53:09.962028Z",
2075        "phone": "",
2076        "confirmed_at": "2025-10-09T14:53:09.962028Z",
2077        "last_sign_in_at": "2025-10-09T20:26:35.136870325Z",
2078        "app_metadata": {
2079            "provider": "email",
2080            "providers": ["email"]
2081        },
2082        "user_metadata": {
2083            "email": "someone@email.com",
2084            "email_verified": true,
2085            "phone_verified": false,
2086            "sub": "31815c55-f553-41f4-b54f-14d6ac60de16"
2087        },
2088        "identities": [{
2089            "identity_id": "5fe7caa2-1dc3-449b-b910-33bd7df0d616",
2090            "id": "31815c55-f553-41f4-b54f-14d6ac60de16",
2091            "user_id": "31815c55-f553-41f4-b54f-14d6ac60de16",
2092            "identity_data": {
2093                "email": "someone@email.com",
2094                "email_verified": false,
2095                "phone_verified": false,
2096                "sub": "31815c55-f553-41f4-b54f-14d6ac60de16"
2097            },
2098            "provider": "email",
2099            "last_sign_in_at": "2025-10-09T14:53:09.958178Z",
2100            "created_at": "2025-10-09T14:53:09.958225Z",
2101            "updated_at": "2025-10-09T14:53:09.958225Z",
2102            "email": "someone@email.com"
2103        }],
2104        "created_at": "2025-10-09T14:53:09.953727Z",
2105        "updated_at": "2025-10-09T20:26:35.13964Z",
2106        "is_anonymous": false
2107    },
2108    "weak_password": null
2109}"#;
2110        let mut auth = serde_json::from_str::<AuthResponse>(json_body).unwrap();
2111        assert!(auth.session.is_none());
2112
2113        auth.session = serde_json::from_str::<Session>(json_body).ok();
2114        assert!(auth.session.is_none());
2115    }
2116
2117    #[tokio::test]
2118    async fn test_auth_creation() {
2119        let config = mock_config();
2120        let http_client = Arc::new(reqwest::Client::new());
2121
2122        let auth = Auth::new(config, http_client);
2123        assert!(auth.is_ok());
2124
2125        let auth = auth.unwrap();
2126        assert!(!auth.is_authenticated());
2127    }
2128
2129    #[test]
2130    fn test_totp_code_generation() {
2131        let config = mock_config();
2132        let http_client = Arc::new(reqwest::Client::new());
2133        let auth = Auth::new(config, http_client).unwrap();
2134
2135        // Test with a known base32 secret
2136        let secret = "JBSWY3DPEHPK3PXP"; // "Hello" in base32
2137        let result = auth.generate_totp_code(secret);
2138
2139        match &result {
2140            Ok(code) => {
2141                println!("Generated TOTP code: {}", code);
2142                assert_eq!(code.len(), 6);
2143                assert!(code.chars().all(|c| c.is_ascii_digit()));
2144            }
2145            Err(e) => {
2146                println!("TOTP generation error: {}", e);
2147                // For now, just check that error is reasonable
2148                assert!(e.to_string().contains("base32") || e.to_string().contains("TOTP"));
2149            }
2150        }
2151    }
2152
2153    #[test]
2154    fn test_totp_code_generation_invalid_secret() {
2155        let config = mock_config();
2156        let http_client = Arc::new(reqwest::Client::new());
2157        let auth = Auth::new(config, http_client).unwrap();
2158
2159        // Test with invalid base32 secret
2160        let result = auth.generate_totp_code("invalid-secret");
2161        assert!(result.is_err());
2162    }
2163
2164    #[tokio::test]
2165    async fn test_token_validation_no_session() {
2166        let config = mock_config();
2167        let http_client = Arc::new(reqwest::Client::new());
2168        let auth = Auth::new(config, http_client).unwrap();
2169
2170        // No session should return false
2171        let is_valid = auth.validate_token_local().unwrap();
2172        assert!(!is_valid);
2173    }
2174
2175    #[test]
2176    fn test_time_until_expiry_no_session() {
2177        let config = mock_config();
2178        let http_client = Arc::new(reqwest::Client::new());
2179        let auth = Auth::new(config, http_client).unwrap();
2180
2181        // No session should return None
2182        let time = auth.time_until_expiry().unwrap();
2183        assert!(time.is_none());
2184    }
2185
2186    #[test]
2187    fn test_needs_refresh_no_session() {
2188        let config = mock_config();
2189        let http_client = Arc::new(reqwest::Client::new());
2190        let auth = Auth::new(config, http_client).unwrap();
2191
2192        // No session should return false
2193        let needs_refresh = auth.needs_refresh_with_buffer(300).unwrap();
2194        assert!(!needs_refresh);
2195    }
2196
2197    #[test]
2198    fn test_get_token_metadata_no_session() {
2199        let config = mock_config();
2200        let http_client = Arc::new(reqwest::Client::new());
2201        let auth = Auth::new(config, http_client).unwrap();
2202
2203        // No session should return None
2204        let metadata = auth.get_token_metadata().unwrap();
2205        assert!(metadata.is_none());
2206    }
2207
2208    #[test]
2209    fn test_token_metadata_structure() {
2210        let metadata = TokenMetadata {
2211            issued_at: Utc::now(),
2212            expires_at: Utc::now() + chrono::Duration::hours(1),
2213            refresh_count: 5,
2214            last_refresh_at: Some(Utc::now()),
2215            scopes: vec!["read".to_string(), "write".to_string()],
2216            device_id: Some("device-123".to_string()),
2217        };
2218
2219        assert_eq!(metadata.refresh_count, 5);
2220        assert!(metadata.last_refresh_at.is_some());
2221        assert_eq!(metadata.scopes.len(), 2);
2222        assert!(metadata.device_id.is_some());
2223    }
2224
2225    #[test]
2226    fn test_mfa_factor_structure() {
2227        let factor = MfaFactor {
2228            id: uuid::Uuid::new_v4(),
2229            factor_type: MfaMethod::Totp,
2230            friendly_name: "My Authenticator".to_string(),
2231            status: "verified".to_string(),
2232            created_at: Utc::now(),
2233            updated_at: Utc::now(),
2234            phone: None,
2235        };
2236
2237        assert_eq!(factor.factor_type, MfaMethod::Totp);
2238        assert_eq!(factor.friendly_name, "My Authenticator");
2239        assert_eq!(factor.status, "verified");
2240        assert!(factor.phone.is_none());
2241    }
2242
2243    #[test]
2244    fn test_enhanced_session_structure() {
2245        let user = User {
2246            id: uuid::Uuid::new_v4(),
2247            email: Some("user@example.com".to_string()),
2248            phone: None,
2249            email_confirmed_at: Some(Utc::now()),
2250            phone_confirmed_at: None,
2251            created_at: Utc::now(),
2252            updated_at: Utc::now(),
2253            last_sign_in_at: Some(Utc::now()),
2254            app_metadata: serde_json::json!({}),
2255            user_metadata: serde_json::json!({}),
2256            aud: "authenticated".to_string(),
2257            role: Some("authenticated".to_string()),
2258        };
2259
2260        let enhanced_session = EnhancedSession {
2261            access_token: "access-token".to_string(),
2262            refresh_token: "refresh-token".to_string(),
2263            expires_in: 3600,
2264            expires_at: Utc::now() + chrono::Duration::hours(1),
2265            token_type: "bearer".to_string(),
2266            user,
2267            token_metadata: None,
2268            mfa_verified: true,
2269            active_factors: vec![],
2270        };
2271
2272        assert!(enhanced_session.mfa_verified);
2273        assert_eq!(enhanced_session.active_factors.len(), 0);
2274        assert_eq!(enhanced_session.token_type, "bearer");
2275    }
2276}