Skip to main content

supabase_client_auth/
types.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_json::Value as JsonValue;
4use std::fmt;
5use std::time::Duration;
6use tokio::sync::broadcast;
7
8/// A user session returned from sign-in, sign-up, or token refresh.
9///
10/// Matches the Supabase GoTrue session object.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Session {
13    pub access_token: String,
14    pub refresh_token: String,
15    pub expires_in: i64,
16    #[serde(default)]
17    pub expires_at: Option<i64>,
18    pub token_type: String,
19    pub user: User,
20}
21
22/// A GoTrue user object.
23///
24/// Matches the Supabase `User` type from the JS/C# client libraries.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct User {
27    pub id: String,
28    #[serde(default)]
29    pub aud: Option<String>,
30    #[serde(default)]
31    pub role: Option<String>,
32    #[serde(default)]
33    pub email: Option<String>,
34    #[serde(default)]
35    pub phone: Option<String>,
36    #[serde(default)]
37    pub email_confirmed_at: Option<DateTime<Utc>>,
38    #[serde(default)]
39    pub phone_confirmed_at: Option<DateTime<Utc>>,
40    #[serde(default)]
41    pub confirmation_sent_at: Option<DateTime<Utc>>,
42    #[serde(default)]
43    pub recovery_sent_at: Option<DateTime<Utc>>,
44    #[serde(default)]
45    pub last_sign_in_at: Option<DateTime<Utc>>,
46    #[serde(default)]
47    pub created_at: Option<DateTime<Utc>>,
48    #[serde(default)]
49    pub updated_at: Option<DateTime<Utc>>,
50    #[serde(default)]
51    pub user_metadata: Option<JsonValue>,
52    #[serde(default)]
53    pub app_metadata: Option<JsonValue>,
54    #[serde(default)]
55    pub identities: Option<Vec<Identity>>,
56    #[serde(default)]
57    pub factors: Option<Vec<Factor>>,
58    #[serde(default)]
59    pub is_anonymous: Option<bool>,
60}
61
62/// A linked auth provider identity.
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct Identity {
65    pub id: String,
66    pub user_id: String,
67    #[serde(default)]
68    pub identity_data: Option<JsonValue>,
69    pub provider: String,
70    #[serde(default)]
71    pub identity_id: Option<String>,
72    #[serde(default)]
73    pub last_sign_in_at: Option<DateTime<Utc>>,
74    #[serde(default)]
75    pub created_at: Option<DateTime<Utc>>,
76    #[serde(default)]
77    pub updated_at: Option<DateTime<Utc>>,
78}
79
80/// An MFA factor.
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct Factor {
83    pub id: String,
84    #[serde(default)]
85    pub friendly_name: Option<String>,
86    pub factor_type: String,
87    pub status: String,
88    #[serde(default)]
89    pub phone: Option<String>,
90    #[serde(default)]
91    pub last_challenged_at: Option<DateTime<Utc>>,
92    #[serde(default)]
93    pub created_at: Option<DateTime<Utc>>,
94    #[serde(default)]
95    pub updated_at: Option<DateTime<Utc>>,
96}
97
98/// MFA factor type.
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
100#[serde(rename_all = "lowercase")]
101pub enum FactorType {
102    Totp,
103    Phone,
104}
105
106impl fmt::Display for FactorType {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        match self {
109            Self::Totp => write!(f, "totp"),
110            Self::Phone => write!(f, "phone"),
111        }
112    }
113}
114
115/// MFA factor status.
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
117#[serde(rename_all = "lowercase")]
118pub enum FactorStatus {
119    Unverified,
120    Verified,
121}
122
123impl fmt::Display for FactorStatus {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        match self {
126            Self::Unverified => write!(f, "unverified"),
127            Self::Verified => write!(f, "verified"),
128        }
129    }
130}
131
132/// Authenticator Assurance Level.
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub enum AuthenticatorAssuranceLevel {
135    /// Single-factor authentication (password, OTP, etc.).
136    Aal1,
137    /// Multi-factor authentication (password + TOTP/phone).
138    Aal2,
139}
140
141impl fmt::Display for AuthenticatorAssuranceLevel {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        match self {
144            Self::Aal1 => write!(f, "aal1"),
145            Self::Aal2 => write!(f, "aal2"),
146        }
147    }
148}
149
150/// Response from MFA enroll.
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct MfaEnrollResponse {
153    pub id: String,
154    #[serde(rename = "type")]
155    pub factor_type: String,
156    #[serde(default)]
157    pub friendly_name: Option<String>,
158    #[serde(default)]
159    pub totp: Option<MfaTotpInfo>,
160    #[serde(default)]
161    pub phone: Option<String>,
162}
163
164/// TOTP-specific info from MFA enroll.
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct MfaTotpInfo {
167    pub qr_code: String,
168    pub secret: String,
169    pub uri: String,
170}
171
172/// Response from MFA challenge.
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct MfaChallengeResponse {
175    pub id: String,
176    #[serde(default, rename = "type")]
177    pub factor_type: Option<String>,
178    #[serde(default)]
179    pub expires_at: Option<i64>,
180}
181
182/// Response from MFA unenroll.
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct MfaUnenrollResponse {
185    pub id: String,
186}
187
188/// Categorized list of enrolled MFA factors.
189#[derive(Debug, Clone)]
190pub struct MfaListFactorsResponse {
191    pub totp: Vec<Factor>,
192    pub phone: Vec<Factor>,
193    pub all: Vec<Factor>,
194}
195
196/// Authenticator assurance level info.
197#[derive(Debug, Clone)]
198pub struct AuthenticatorAssuranceLevelInfo {
199    pub current_level: Option<AuthenticatorAssuranceLevel>,
200    pub next_level: Option<AuthenticatorAssuranceLevel>,
201    pub current_authentication_methods: Vec<AmrEntry>,
202}
203
204/// Authentication Method Reference entry from the JWT.
205#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct AmrEntry {
207    pub method: String,
208    #[serde(default)]
209    pub timestamp: Option<i64>,
210}
211
212/// Response from SSO sign-in.
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct SsoSignInResponse {
215    pub url: String,
216}
217
218/// Response from identity link (returns redirect URL).
219#[derive(Debug, Clone)]
220pub struct LinkIdentityResponse {
221    pub url: String,
222}
223
224/// Response from sign-up and some auth operations.
225///
226/// Mirrors Supabase JS `AuthResponse` — contains an optional session and/or user.
227#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct AuthResponse {
229    #[serde(default)]
230    pub session: Option<Session>,
231    #[serde(default)]
232    pub user: Option<User>,
233}
234
235/// Paginated list of users returned by admin endpoints.
236#[derive(Debug, Clone, Deserialize)]
237pub struct AdminUserListResponse {
238    pub users: Vec<User>,
239    #[serde(default)]
240    pub aud: Option<String>,
241    #[serde(default, rename = "nextPage")]
242    pub next_page: Option<u32>,
243    #[serde(default, rename = "lastPage")]
244    pub last_page: Option<u32>,
245    #[serde(default)]
246    pub total: Option<u64>,
247}
248
249/// Supported OAuth providers.
250///
251/// Matches the provider strings accepted by Supabase GoTrue.
252#[derive(Debug, Clone, PartialEq, Eq)]
253pub enum OAuthProvider {
254    Apple,
255    Azure,
256    Bitbucket,
257    Discord,
258    Facebook,
259    Figma,
260    Fly,
261    GitHub,
262    GitLab,
263    Google,
264    Kakao,
265    Keycloak,
266    LinkedIn,
267    LinkedInOidc,
268    Notion,
269    Slack,
270    SlackOidc,
271    Spotify,
272    Twitch,
273    Twitter,
274    WorkOS,
275    Zoom,
276    Custom(String),
277}
278
279impl fmt::Display for OAuthProvider {
280    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281        match self {
282            Self::Apple => write!(f, "apple"),
283            Self::Azure => write!(f, "azure"),
284            Self::Bitbucket => write!(f, "bitbucket"),
285            Self::Discord => write!(f, "discord"),
286            Self::Facebook => write!(f, "facebook"),
287            Self::Figma => write!(f, "figma"),
288            Self::Fly => write!(f, "fly"),
289            Self::GitHub => write!(f, "github"),
290            Self::GitLab => write!(f, "gitlab"),
291            Self::Google => write!(f, "google"),
292            Self::Kakao => write!(f, "kakao"),
293            Self::Keycloak => write!(f, "keycloak"),
294            Self::LinkedIn => write!(f, "linkedin"),
295            Self::LinkedInOidc => write!(f, "linkedin_oidc"),
296            Self::Notion => write!(f, "notion"),
297            Self::Slack => write!(f, "slack"),
298            Self::SlackOidc => write!(f, "slack_oidc"),
299            Self::Spotify => write!(f, "spotify"),
300            Self::Twitch => write!(f, "twitch"),
301            Self::Twitter => write!(f, "twitter"),
302            Self::WorkOS => write!(f, "workos"),
303            Self::Zoom => write!(f, "zoom"),
304            Self::Custom(s) => write!(f, "{}", s),
305        }
306    }
307}
308
309// ─── OAuth Server Types ──────────────────────────────────────
310
311/// OAuth client grant type.
312#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
313pub enum OAuthClientGrantType {
314    #[serde(rename = "authorization_code")]
315    AuthorizationCode,
316    #[serde(rename = "refresh_token")]
317    RefreshToken,
318}
319
320/// OAuth client response type.
321#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
322pub enum OAuthClientResponseType {
323    #[serde(rename = "code")]
324    Code,
325}
326
327/// OAuth client type (public or confidential).
328#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
329#[serde(rename_all = "lowercase")]
330pub enum OAuthClientType {
331    Public,
332    Confidential,
333}
334
335/// OAuth client registration type.
336#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
337#[serde(rename_all = "lowercase")]
338pub enum OAuthClientRegistrationType {
339    Dynamic,
340    Manual,
341}
342
343/// A registered OAuth client.
344#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct OAuthClient {
346    pub client_id: String,
347    pub client_name: String,
348    #[serde(default)]
349    pub client_secret: Option<String>,
350    pub client_type: OAuthClientType,
351    pub token_endpoint_auth_method: String,
352    pub registration_type: OAuthClientRegistrationType,
353    #[serde(default)]
354    pub client_uri: Option<String>,
355    #[serde(default)]
356    pub logo_uri: Option<String>,
357    pub redirect_uris: Vec<String>,
358    pub grant_types: Vec<OAuthClientGrantType>,
359    pub response_types: Vec<OAuthClientResponseType>,
360    #[serde(default)]
361    pub scope: Option<String>,
362    pub created_at: String,
363    pub updated_at: String,
364}
365
366/// Paginated list of OAuth clients.
367#[derive(Debug, Clone, Deserialize)]
368pub struct OAuthClientListResponse {
369    pub clients: Vec<OAuthClient>,
370    #[serde(default)]
371    pub aud: Option<String>,
372}
373
374/// OAuth authorization client info (subset shown during consent).
375#[derive(Debug, Clone, Serialize, Deserialize)]
376pub struct OAuthAuthorizationClient {
377    pub id: String,
378    pub name: String,
379    #[serde(default)]
380    pub uri: Option<String>,
381    #[serde(default)]
382    pub logo_uri: Option<String>,
383}
384
385/// User info in authorization details.
386#[derive(Debug, Clone, Serialize, Deserialize)]
387pub struct OAuthAuthorizationUser {
388    pub id: String,
389    #[serde(default)]
390    pub email: Option<String>,
391}
392
393/// OAuth authorization details (consent screen data).
394#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct OAuthAuthorizationDetails {
396    pub authorization_id: String,
397    pub redirect_uri: String,
398    pub client: OAuthAuthorizationClient,
399    pub user: OAuthAuthorizationUser,
400    #[serde(default)]
401    pub scope: Option<String>,
402}
403
404/// Redirect response from authorization consent.
405#[derive(Debug, Clone, Serialize, Deserialize)]
406pub struct OAuthRedirect {
407    pub redirect_url: String,
408}
409
410/// Response from getAuthorizationDetails — either details or a redirect.
411#[derive(Debug, Clone, Serialize, Deserialize)]
412#[serde(untagged)]
413pub enum OAuthAuthorizationDetailsResponse {
414    /// User needs to consent — full details provided.
415    Details(OAuthAuthorizationDetails),
416    /// User already consented — redirect URL provided.
417    Redirect(OAuthRedirect),
418}
419
420/// A granted OAuth permission.
421#[derive(Debug, Clone, Serialize, Deserialize)]
422pub struct OAuthGrant {
423    pub client: OAuthAuthorizationClient,
424    pub scopes: Vec<String>,
425    pub granted_at: String,
426}
427
428// ─── OAuth Client-Side Flow Types ────────────────────────────
429
430/// PKCE code verifier (random string, 43-128 unreserved characters).
431#[derive(Debug, Clone)]
432pub struct PkceCodeVerifier(pub(crate) String);
433
434impl PkceCodeVerifier {
435    /// Get the verifier string.
436    pub fn as_str(&self) -> &str {
437        &self.0
438    }
439}
440
441impl fmt::Display for PkceCodeVerifier {
442    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
443        write!(f, "{}", self.0)
444    }
445}
446
447/// PKCE code challenge (S256 hash of the verifier, base64url-encoded).
448#[derive(Debug, Clone)]
449pub struct PkceCodeChallenge(pub(crate) String);
450
451impl PkceCodeChallenge {
452    /// Get the challenge string.
453    pub fn as_str(&self) -> &str {
454        &self.0
455    }
456}
457
458impl fmt::Display for PkceCodeChallenge {
459    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460        write!(f, "{}", self.0)
461    }
462}
463
464/// A PKCE verifier/challenge pair for OAuth 2.1 authorization code flow.
465#[derive(Debug, Clone)]
466pub struct PkcePair {
467    pub verifier: PkceCodeVerifier,
468    pub challenge: PkceCodeChallenge,
469}
470
471/// OAuth token response from the `/oauth/token` endpoint.
472#[derive(Debug, Clone, Serialize, Deserialize)]
473pub struct OAuthTokenResponse {
474    pub access_token: String,
475    pub token_type: String,
476    #[serde(default)]
477    pub expires_in: Option<i64>,
478    #[serde(default)]
479    pub refresh_token: Option<String>,
480    #[serde(default)]
481    pub scope: Option<String>,
482    #[serde(default)]
483    pub id_token: Option<String>,
484}
485
486/// OpenID Connect discovery document.
487///
488/// Fetched from `/.well-known/openid-configuration`.
489#[derive(Debug, Clone, Serialize, Deserialize)]
490pub struct OpenIdConfiguration {
491    pub issuer: String,
492    pub authorization_endpoint: String,
493    pub token_endpoint: String,
494    pub jwks_uri: String,
495    #[serde(default)]
496    pub userinfo_endpoint: Option<String>,
497    #[serde(default)]
498    pub scopes_supported: Vec<String>,
499    #[serde(default)]
500    pub response_types_supported: Vec<String>,
501    #[serde(default)]
502    pub response_modes_supported: Vec<String>,
503    #[serde(default)]
504    pub grant_types_supported: Vec<String>,
505    #[serde(default)]
506    pub subject_types_supported: Vec<String>,
507    #[serde(default)]
508    pub id_token_signing_alg_values_supported: Vec<String>,
509    #[serde(default)]
510    pub token_endpoint_auth_methods_supported: Vec<String>,
511    #[serde(default)]
512    pub claims_supported: Vec<String>,
513    #[serde(default)]
514    pub code_challenge_methods_supported: Vec<String>,
515}
516
517/// JSON Web Key Set response.
518///
519/// Fetched from the `jwks_uri` in the OIDC discovery document.
520#[derive(Debug, Clone, Serialize, Deserialize)]
521pub struct JwksResponse {
522    pub keys: Vec<Jwk>,
523}
524
525/// Individual JSON Web Key.
526#[derive(Debug, Clone, Serialize, Deserialize)]
527pub struct Jwk {
528    /// Key type (e.g., "RSA", "EC").
529    pub kty: String,
530    /// Key ID.
531    #[serde(default)]
532    pub kid: Option<String>,
533    /// Algorithm (e.g., "RS256", "ES256").
534    #[serde(default)]
535    pub alg: Option<String>,
536    /// Public key use (e.g., "sig").
537    #[serde(default, rename = "use")]
538    pub use_: Option<String>,
539    /// Key operations.
540    #[serde(default)]
541    pub key_ops: Option<Vec<String>>,
542    // RSA fields
543    /// RSA modulus.
544    #[serde(default)]
545    pub n: Option<String>,
546    /// RSA exponent.
547    #[serde(default)]
548    pub e: Option<String>,
549    // EC fields
550    /// EC curve name (e.g., "P-256").
551    #[serde(default)]
552    pub crv: Option<String>,
553    /// EC x coordinate.
554    #[serde(default)]
555    pub x: Option<String>,
556    /// EC y coordinate.
557    #[serde(default)]
558    pub y: Option<String>,
559    /// Whether this is an extractable key.
560    #[serde(default)]
561    pub ext: Option<bool>,
562}
563
564/// OTP delivery channel for phone-based OTP.
565#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
566#[serde(rename_all = "lowercase")]
567pub enum OtpChannel {
568    Sms,
569    Whatsapp,
570}
571
572impl fmt::Display for OtpChannel {
573    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
574        match self {
575            Self::Sms => write!(f, "sms"),
576            Self::Whatsapp => write!(f, "whatsapp"),
577        }
578    }
579}
580
581/// Blockchain type for Web3 wallet authentication.
582#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
583#[serde(rename_all = "lowercase")]
584pub enum Web3Chain {
585    Ethereum,
586    Solana,
587}
588
589impl fmt::Display for Web3Chain {
590    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
591        match self {
592            Self::Ethereum => write!(f, "ethereum"),
593            Self::Solana => write!(f, "solana"),
594        }
595    }
596}
597
598/// Parameters for signing in with a Web3 wallet.
599#[derive(Debug, Clone, Serialize)]
600pub struct Web3SignInParams {
601    pub chain: Web3Chain,
602    pub address: String,
603    pub message: String,
604    pub signature: String,
605    pub nonce: String,
606}
607
608impl Web3SignInParams {
609    pub fn new(
610        chain: Web3Chain,
611        address: impl Into<String>,
612        message: impl Into<String>,
613        signature: impl Into<String>,
614        nonce: impl Into<String>,
615    ) -> Self {
616        Self {
617            chain,
618            address: address.into(),
619            message: message.into(),
620            signature: signature.into(),
621            nonce: nonce.into(),
622        }
623    }
624}
625
626/// Scope for sign-out operations.
627///
628/// Matches `SignOutScope` from Supabase JS.
629#[derive(Debug, Clone, Copy, PartialEq, Eq)]
630pub enum SignOutScope {
631    /// Sign out from the current session only.
632    Local,
633    /// Sign out from all other sessions (keep current).
634    Others,
635    /// Sign out from all sessions including current.
636    Global,
637}
638
639impl fmt::Display for SignOutScope {
640    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
641        match self {
642            Self::Local => write!(f, "local"),
643            Self::Others => write!(f, "others"),
644            Self::Global => write!(f, "global"),
645        }
646    }
647}
648
649/// OTP verification type.
650#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
651#[serde(rename_all = "lowercase")]
652pub enum OtpType {
653    Email,
654    Sms,
655    #[serde(rename = "phone_change")]
656    PhoneChange,
657    #[serde(rename = "email_change")]
658    EmailChange,
659    Signup,
660    Recovery,
661    Invite,
662    #[serde(rename = "magiclink")]
663    MagicLink,
664}
665
666impl fmt::Display for OtpType {
667    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
668        match self {
669            Self::Email => write!(f, "email"),
670            Self::Sms => write!(f, "sms"),
671            Self::PhoneChange => write!(f, "phone_change"),
672            Self::EmailChange => write!(f, "email_change"),
673            Self::Signup => write!(f, "signup"),
674            Self::Recovery => write!(f, "recovery"),
675            Self::Invite => write!(f, "invite"),
676            Self::MagicLink => write!(f, "magiclink"),
677        }
678    }
679}
680
681// ─── Auth State Management Types ─────────────────────────────
682
683/// Auth state change event types.
684///
685/// Emitted when the stored session state changes (sign-in, sign-out, refresh, etc.).
686#[derive(Debug, Clone, PartialEq, Eq)]
687pub enum AuthChangeEvent {
688    /// Initial session loaded (e.g., from set_session).
689    InitialSession,
690    /// User signed in (password, OTP, OAuth, anonymous, MFA verify, etc.).
691    SignedIn,
692    /// User signed out.
693    SignedOut,
694    /// Access token was refreshed.
695    TokenRefreshed,
696    /// User attributes were updated.
697    UserUpdated,
698    /// Password recovery flow initiated.
699    PasswordRecovery,
700}
701
702impl fmt::Display for AuthChangeEvent {
703    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
704        match self {
705            Self::InitialSession => write!(f, "INITIAL_SESSION"),
706            Self::SignedIn => write!(f, "SIGNED_IN"),
707            Self::SignedOut => write!(f, "SIGNED_OUT"),
708            Self::TokenRefreshed => write!(f, "TOKEN_REFRESHED"),
709            Self::UserUpdated => write!(f, "USER_UPDATED"),
710            Self::PasswordRecovery => write!(f, "PASSWORD_RECOVERY"),
711        }
712    }
713}
714
715/// An auth state change notification, containing the event type and optional session.
716#[derive(Debug, Clone)]
717pub struct AuthStateChange {
718    pub event: AuthChangeEvent,
719    pub session: Option<Session>,
720}
721
722/// Subscription handle for auth state change events.
723///
724/// Created by [`AuthClient::on_auth_state_change()`](crate::AuthClient::on_auth_state_change). Use [`next()`](AuthSubscription::next)
725/// to await the next event.
726pub struct AuthSubscription {
727    pub(crate) rx: broadcast::Receiver<AuthStateChange>,
728}
729
730impl AuthSubscription {
731    /// Await the next auth state change event.
732    ///
733    /// Returns `None` if the sender has been dropped (client deallocated).
734    /// Skips over lagged messages automatically.
735    pub async fn next(&mut self) -> Option<AuthStateChange> {
736        loop {
737            match self.rx.recv().await {
738                Ok(change) => return Some(change),
739                Err(broadcast::error::RecvError::Lagged(_)) => continue,
740                Err(broadcast::error::RecvError::Closed) => return None,
741            }
742        }
743    }
744}
745
746impl fmt::Debug for AuthSubscription {
747    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
748        f.debug_struct("AuthSubscription").finish()
749    }
750}
751
752/// Configuration for automatic token refresh.
753#[derive(Debug, Clone)]
754pub struct AutoRefreshConfig {
755    /// How often to check if the session needs refreshing (default: 30s).
756    pub check_interval: Duration,
757    /// How far before expiry to trigger a refresh (default: 60s).
758    pub refresh_margin: Duration,
759    /// Maximum consecutive refresh failures before signing out (default: 3).
760    pub max_retries: u32,
761}
762
763impl Default for AutoRefreshConfig {
764    fn default() -> Self {
765        Self {
766            check_interval: Duration::from_secs(30),
767            refresh_margin: Duration::from_secs(60),
768            max_retries: 3,
769        }
770    }
771}
772
773#[cfg(test)]
774mod tests {
775    use super::*;
776
777    #[test]
778    fn factor_type_display() {
779        assert_eq!(FactorType::Totp.to_string(), "totp");
780        assert_eq!(FactorType::Phone.to_string(), "phone");
781    }
782
783    #[test]
784    fn factor_type_serde_roundtrip() {
785        let json = serde_json::to_string(&FactorType::Totp).unwrap();
786        assert_eq!(json, "\"totp\"");
787        let parsed: FactorType = serde_json::from_str(&json).unwrap();
788        assert_eq!(parsed, FactorType::Totp);
789    }
790
791    #[test]
792    fn factor_status_display() {
793        assert_eq!(FactorStatus::Unverified.to_string(), "unverified");
794        assert_eq!(FactorStatus::Verified.to_string(), "verified");
795    }
796
797    #[test]
798    fn authenticator_assurance_level_display() {
799        assert_eq!(AuthenticatorAssuranceLevel::Aal1.to_string(), "aal1");
800        assert_eq!(AuthenticatorAssuranceLevel::Aal2.to_string(), "aal2");
801    }
802
803    #[test]
804    fn mfa_list_factors_categorization() {
805        let factors = vec![
806            Factor {
807                id: "f1".into(),
808                friendly_name: Some("My TOTP".into()),
809                factor_type: "totp".into(),
810                status: "verified".into(),
811                phone: None,
812                last_challenged_at: None,
813                created_at: None,
814                updated_at: None,
815            },
816            Factor {
817                id: "f2".into(),
818                friendly_name: Some("My Phone".into()),
819                factor_type: "phone".into(),
820                status: "unverified".into(),
821                phone: Some("+1234567890".into()),
822                last_challenged_at: None,
823                created_at: None,
824                updated_at: None,
825            },
826        ];
827
828        let totp: Vec<_> = factors.iter().filter(|f| f.factor_type == "totp").collect();
829        let phone: Vec<_> = factors.iter().filter(|f| f.factor_type == "phone").collect();
830
831        assert_eq!(totp.len(), 1);
832        assert_eq!(totp[0].id, "f1");
833        assert_eq!(phone.len(), 1);
834        assert_eq!(phone[0].id, "f2");
835    }
836
837    #[test]
838    fn mfa_enroll_response_deserialize() {
839        let json = r#"{
840            "id": "factor-uuid",
841            "type": "totp",
842            "friendly_name": "My Authenticator",
843            "totp": {
844                "qr_code": "data:image/svg+xml;...",
845                "secret": "BASE32SECRET",
846                "uri": "otpauth://totp/..."
847            }
848        }"#;
849        let resp: MfaEnrollResponse = serde_json::from_str(json).unwrap();
850        assert_eq!(resp.id, "factor-uuid");
851        assert_eq!(resp.factor_type, "totp");
852        assert!(resp.totp.is_some());
853        let totp = resp.totp.unwrap();
854        assert_eq!(totp.secret, "BASE32SECRET");
855    }
856
857    #[test]
858    fn mfa_challenge_response_deserialize() {
859        let json = r#"{"id": "challenge-uuid", "type": "totp", "expires_at": 1700000000}"#;
860        let resp: MfaChallengeResponse = serde_json::from_str(json).unwrap();
861        assert_eq!(resp.id, "challenge-uuid");
862        assert_eq!(resp.factor_type.as_deref(), Some("totp"));
863        assert_eq!(resp.expires_at, Some(1700000000));
864    }
865
866    #[test]
867    fn sso_sign_in_response_deserialize() {
868        let json = r#"{"url": "https://sso.example.com/login"}"#;
869        let resp: SsoSignInResponse = serde_json::from_str(json).unwrap();
870        assert_eq!(resp.url, "https://sso.example.com/login");
871    }
872
873    #[test]
874    fn factor_with_new_fields_deserialize() {
875        let json = r#"{
876            "id": "f1",
877            "factor_type": "phone",
878            "status": "verified",
879            "phone": "+1234567890",
880            "last_challenged_at": "2024-01-01T00:00:00Z"
881        }"#;
882        let factor: Factor = serde_json::from_str(json).unwrap();
883        assert_eq!(factor.phone.as_deref(), Some("+1234567890"));
884        assert!(factor.last_challenged_at.is_some());
885    }
886
887    #[test]
888    fn amr_entry_deserialize() {
889        let json = r#"{"method": "totp", "timestamp": 1700000000}"#;
890        let entry: AmrEntry = serde_json::from_str(json).unwrap();
891        assert_eq!(entry.method, "totp");
892        assert_eq!(entry.timestamp, Some(1700000000));
893    }
894
895    // ─── OAuth Server Type Tests ──────────────────────────────
896
897    #[test]
898    fn oauth_client_type_serde() {
899        let json = serde_json::to_string(&OAuthClientType::Confidential).unwrap();
900        assert_eq!(json, "\"confidential\"");
901        let parsed: OAuthClientType = serde_json::from_str(&json).unwrap();
902        assert_eq!(parsed, OAuthClientType::Confidential);
903
904        let json = serde_json::to_string(&OAuthClientType::Public).unwrap();
905        assert_eq!(json, "\"public\"");
906        let parsed: OAuthClientType = serde_json::from_str(&json).unwrap();
907        assert_eq!(parsed, OAuthClientType::Public);
908    }
909
910    #[test]
911    fn oauth_client_grant_type_serde() {
912        let json = serde_json::to_string(&OAuthClientGrantType::AuthorizationCode).unwrap();
913        assert_eq!(json, "\"authorization_code\"");
914        let parsed: OAuthClientGrantType = serde_json::from_str(&json).unwrap();
915        assert_eq!(parsed, OAuthClientGrantType::AuthorizationCode);
916
917        let json = serde_json::to_string(&OAuthClientGrantType::RefreshToken).unwrap();
918        assert_eq!(json, "\"refresh_token\"");
919        let parsed: OAuthClientGrantType = serde_json::from_str(&json).unwrap();
920        assert_eq!(parsed, OAuthClientGrantType::RefreshToken);
921    }
922
923    #[test]
924    fn oauth_client_deserialize() {
925        let json = r#"{
926            "client_id": "abc-123",
927            "client_name": "My App",
928            "client_secret": "secret-456",
929            "client_type": "confidential",
930            "token_endpoint_auth_method": "client_secret_post",
931            "registration_type": "manual",
932            "client_uri": "https://myapp.com",
933            "logo_uri": "https://myapp.com/logo.png",
934            "redirect_uris": ["https://myapp.com/callback"],
935            "grant_types": ["authorization_code", "refresh_token"],
936            "response_types": ["code"],
937            "scope": "openid profile",
938            "created_at": "2024-01-01T00:00:00Z",
939            "updated_at": "2024-01-01T00:00:00Z"
940        }"#;
941        let client: OAuthClient = serde_json::from_str(json).unwrap();
942        assert_eq!(client.client_id, "abc-123");
943        assert_eq!(client.client_name, "My App");
944        assert_eq!(client.client_secret.as_deref(), Some("secret-456"));
945        assert_eq!(client.client_type, OAuthClientType::Confidential);
946        assert_eq!(client.registration_type, OAuthClientRegistrationType::Manual);
947        assert_eq!(client.redirect_uris.len(), 1);
948        assert_eq!(client.grant_types.len(), 2);
949        assert_eq!(client.response_types.len(), 1);
950        assert_eq!(client.scope.as_deref(), Some("openid profile"));
951    }
952
953    #[test]
954    fn oauth_client_list_response_deserialize() {
955        let json = r#"{
956            "clients": [{
957                "client_id": "abc",
958                "client_name": "App",
959                "client_type": "public",
960                "token_endpoint_auth_method": "none",
961                "registration_type": "dynamic",
962                "redirect_uris": [],
963                "grant_types": ["authorization_code"],
964                "response_types": ["code"],
965                "created_at": "2024-01-01T00:00:00Z",
966                "updated_at": "2024-01-01T00:00:00Z"
967            }],
968            "aud": "authenticated"
969        }"#;
970        let resp: OAuthClientListResponse = serde_json::from_str(json).unwrap();
971        assert_eq!(resp.clients.len(), 1);
972        assert_eq!(resp.clients[0].client_id, "abc");
973        assert_eq!(resp.aud.as_deref(), Some("authenticated"));
974    }
975
976    #[test]
977    fn oauth_authorization_details_deserialize() {
978        let json = r#"{
979            "authorization_id": "auth-123",
980            "redirect_uri": "https://myapp.com/callback",
981            "client": {
982                "id": "client-456",
983                "name": "My App",
984                "uri": "https://myapp.com",
985                "logo_uri": "https://myapp.com/logo.png"
986            },
987            "user": {
988                "id": "user-789",
989                "email": "user@example.com"
990            },
991            "scope": "openid"
992        }"#;
993        let details: OAuthAuthorizationDetails = serde_json::from_str(json).unwrap();
994        assert_eq!(details.authorization_id, "auth-123");
995        assert_eq!(details.client.id, "client-456");
996        assert_eq!(details.user.id, "user-789");
997        assert_eq!(details.scope.as_deref(), Some("openid"));
998    }
999
1000    #[test]
1001    fn oauth_authorization_details_response_details_variant() {
1002        let json = r#"{
1003            "authorization_id": "auth-123",
1004            "redirect_uri": "https://myapp.com/callback",
1005            "client": { "id": "c1", "name": "App" },
1006            "user": { "id": "u1" }
1007        }"#;
1008        let resp: OAuthAuthorizationDetailsResponse = serde_json::from_str(json).unwrap();
1009        match resp {
1010            OAuthAuthorizationDetailsResponse::Details(d) => {
1011                assert_eq!(d.authorization_id, "auth-123");
1012            }
1013            _ => panic!("Expected Details variant"),
1014        }
1015    }
1016
1017    #[test]
1018    fn oauth_authorization_details_response_redirect_variant() {
1019        let json = r#"{"redirect_url": "https://myapp.com/callback?code=abc"}"#;
1020        let resp: OAuthAuthorizationDetailsResponse = serde_json::from_str(json).unwrap();
1021        match resp {
1022            OAuthAuthorizationDetailsResponse::Redirect(r) => {
1023                assert_eq!(r.redirect_url, "https://myapp.com/callback?code=abc");
1024            }
1025            _ => panic!("Expected Redirect variant"),
1026        }
1027    }
1028
1029    #[test]
1030    fn oauth_grant_deserialize() {
1031        let json = r#"{
1032            "client": { "id": "c1", "name": "My App" },
1033            "scopes": ["openid", "profile"],
1034            "granted_at": "2024-01-01T00:00:00Z"
1035        }"#;
1036        let grant: OAuthGrant = serde_json::from_str(json).unwrap();
1037        assert_eq!(grant.client.id, "c1");
1038        assert_eq!(grant.scopes.len(), 2);
1039        assert_eq!(grant.scopes[0], "openid");
1040    }
1041
1042    #[test]
1043    fn oauth_redirect_deserialize() {
1044        let json = r#"{"redirect_url": "https://example.com/auth?code=xyz"}"#;
1045        let redirect: OAuthRedirect = serde_json::from_str(json).unwrap();
1046        assert_eq!(redirect.redirect_url, "https://example.com/auth?code=xyz");
1047    }
1048
1049    // ─── OAuth Client-Side Flow Type Tests ───────────────────
1050
1051    #[test]
1052    fn pkce_code_verifier_display_and_as_str() {
1053        let verifier = PkceCodeVerifier("test-verifier-string".to_string());
1054        assert_eq!(verifier.as_str(), "test-verifier-string");
1055        assert_eq!(verifier.to_string(), "test-verifier-string");
1056    }
1057
1058    #[test]
1059    fn pkce_code_challenge_display_and_as_str() {
1060        let challenge = PkceCodeChallenge("test-challenge-string".to_string());
1061        assert_eq!(challenge.as_str(), "test-challenge-string");
1062        assert_eq!(challenge.to_string(), "test-challenge-string");
1063    }
1064
1065    #[test]
1066    fn oauth_token_response_deserialize() {
1067        let json = r#"{
1068            "access_token": "eyJ...",
1069            "token_type": "bearer",
1070            "expires_in": 3600,
1071            "refresh_token": "refresh-abc",
1072            "scope": "openid profile",
1073            "id_token": "id-token-xyz"
1074        }"#;
1075        let resp: OAuthTokenResponse = serde_json::from_str(json).unwrap();
1076        assert_eq!(resp.access_token, "eyJ...");
1077        assert_eq!(resp.token_type, "bearer");
1078        assert_eq!(resp.expires_in, Some(3600));
1079        assert_eq!(resp.refresh_token.as_deref(), Some("refresh-abc"));
1080        assert_eq!(resp.scope.as_deref(), Some("openid profile"));
1081        assert_eq!(resp.id_token.as_deref(), Some("id-token-xyz"));
1082    }
1083
1084    #[test]
1085    fn oauth_token_response_minimal_deserialize() {
1086        let json = r#"{
1087            "access_token": "tok",
1088            "token_type": "bearer"
1089        }"#;
1090        let resp: OAuthTokenResponse = serde_json::from_str(json).unwrap();
1091        assert_eq!(resp.access_token, "tok");
1092        assert!(resp.expires_in.is_none());
1093        assert!(resp.refresh_token.is_none());
1094        assert!(resp.scope.is_none());
1095        assert!(resp.id_token.is_none());
1096    }
1097
1098    #[test]
1099    fn openid_configuration_deserialize() {
1100        let json = r#"{
1101            "issuer": "http://localhost:64321/auth/v1",
1102            "authorization_endpoint": "http://localhost:64321/auth/v1/authorize",
1103            "token_endpoint": "http://localhost:64321/auth/v1/oauth/token",
1104            "jwks_uri": "http://localhost:64321/auth/v1/.well-known/jwks.json",
1105            "userinfo_endpoint": "http://localhost:64321/auth/v1/oauth/userinfo",
1106            "scopes_supported": ["openid"],
1107            "response_types_supported": ["code"],
1108            "grant_types_supported": ["authorization_code", "refresh_token"],
1109            "subject_types_supported": ["public"],
1110            "id_token_signing_alg_values_supported": ["ES256"],
1111            "code_challenge_methods_supported": ["S256", "plain"]
1112        }"#;
1113        let config: OpenIdConfiguration = serde_json::from_str(json).unwrap();
1114        assert_eq!(config.issuer, "http://localhost:64321/auth/v1");
1115        assert!(config.jwks_uri.contains("jwks.json"));
1116        assert!(config.scopes_supported.contains(&"openid".to_string()));
1117        assert!(config.code_challenge_methods_supported.contains(&"S256".to_string()));
1118    }
1119
1120    #[test]
1121    fn jwks_response_deserialize() {
1122        let json = r#"{
1123            "keys": [{
1124                "kty": "EC",
1125                "kid": "key-1",
1126                "alg": "ES256",
1127                "use": "sig",
1128                "crv": "P-256",
1129                "x": "abc123",
1130                "y": "def456"
1131            }]
1132        }"#;
1133        let jwks: JwksResponse = serde_json::from_str(json).unwrap();
1134        assert_eq!(jwks.keys.len(), 1);
1135        let key = &jwks.keys[0];
1136        assert_eq!(key.kty, "EC");
1137        assert_eq!(key.kid.as_deref(), Some("key-1"));
1138        assert_eq!(key.alg.as_deref(), Some("ES256"));
1139        assert_eq!(key.use_.as_deref(), Some("sig"));
1140        assert_eq!(key.crv.as_deref(), Some("P-256"));
1141        assert_eq!(key.x.as_deref(), Some("abc123"));
1142        assert_eq!(key.y.as_deref(), Some("def456"));
1143        // RSA fields should be None
1144        assert!(key.n.is_none());
1145        assert!(key.e.is_none());
1146    }
1147
1148    #[test]
1149    fn jwk_rsa_deserialize() {
1150        let json = r#"{
1151            "kty": "RSA",
1152            "kid": "rsa-key",
1153            "alg": "RS256",
1154            "use": "sig",
1155            "n": "modulus-base64",
1156            "e": "AQAB"
1157        }"#;
1158        let key: Jwk = serde_json::from_str(json).unwrap();
1159        assert_eq!(key.kty, "RSA");
1160        assert_eq!(key.n.as_deref(), Some("modulus-base64"));
1161        assert_eq!(key.e.as_deref(), Some("AQAB"));
1162        // EC fields should be None
1163        assert!(key.crv.is_none());
1164        assert!(key.x.is_none());
1165        assert!(key.y.is_none());
1166    }
1167
1168    // ─── Auth State Management Type Tests ────────────────────
1169
1170    #[test]
1171    fn auth_change_event_display() {
1172        assert_eq!(AuthChangeEvent::InitialSession.to_string(), "INITIAL_SESSION");
1173        assert_eq!(AuthChangeEvent::SignedIn.to_string(), "SIGNED_IN");
1174        assert_eq!(AuthChangeEvent::SignedOut.to_string(), "SIGNED_OUT");
1175        assert_eq!(AuthChangeEvent::TokenRefreshed.to_string(), "TOKEN_REFRESHED");
1176        assert_eq!(AuthChangeEvent::UserUpdated.to_string(), "USER_UPDATED");
1177        assert_eq!(AuthChangeEvent::PasswordRecovery.to_string(), "PASSWORD_RECOVERY");
1178    }
1179
1180    #[test]
1181    fn auth_change_event_equality() {
1182        assert_eq!(AuthChangeEvent::SignedIn, AuthChangeEvent::SignedIn);
1183        assert_ne!(AuthChangeEvent::SignedIn, AuthChangeEvent::SignedOut);
1184    }
1185
1186    #[test]
1187    fn auto_refresh_config_default() {
1188        let config = AutoRefreshConfig::default();
1189        assert_eq!(config.check_interval, Duration::from_secs(30));
1190        assert_eq!(config.refresh_margin, Duration::from_secs(60));
1191        assert_eq!(config.max_retries, 3);
1192    }
1193
1194    #[test]
1195    fn auto_refresh_config_custom() {
1196        let config = AutoRefreshConfig {
1197            check_interval: Duration::from_secs(10),
1198            refresh_margin: Duration::from_secs(120),
1199            max_retries: 5,
1200        };
1201        assert_eq!(config.check_interval, Duration::from_secs(10));
1202        assert_eq!(config.refresh_margin, Duration::from_secs(120));
1203        assert_eq!(config.max_retries, 5);
1204    }
1205}