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#[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#[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#[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#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub enum AuthenticatorAssuranceLevel {
135 Aal1,
137 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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct MfaTotpInfo {
167 pub qr_code: String,
168 pub secret: String,
169 pub uri: String,
170}
171
172#[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#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct MfaUnenrollResponse {
185 pub id: String,
186}
187
188#[derive(Debug, Clone)]
190pub struct MfaListFactorsResponse {
191 pub totp: Vec<Factor>,
192 pub phone: Vec<Factor>,
193 pub all: Vec<Factor>,
194}
195
196#[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#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct AmrEntry {
207 pub method: String,
208 #[serde(default)]
209 pub timestamp: Option<i64>,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct SsoSignInResponse {
215 pub url: String,
216}
217
218#[derive(Debug, Clone)]
220pub struct LinkIdentityResponse {
221 pub url: String,
222}
223
224#[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#[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#[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#[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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
322pub enum OAuthClientResponseType {
323 #[serde(rename = "code")]
324 Code,
325}
326
327#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
329#[serde(rename_all = "lowercase")]
330pub enum OAuthClientType {
331 Public,
332 Confidential,
333}
334
335#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
337#[serde(rename_all = "lowercase")]
338pub enum OAuthClientRegistrationType {
339 Dynamic,
340 Manual,
341}
342
343#[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#[derive(Debug, Clone, Deserialize)]
368pub struct OAuthClientListResponse {
369 pub clients: Vec<OAuthClient>,
370 #[serde(default)]
371 pub aud: Option<String>,
372}
373
374#[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#[derive(Debug, Clone, Serialize, Deserialize)]
387pub struct OAuthAuthorizationUser {
388 pub id: String,
389 #[serde(default)]
390 pub email: Option<String>,
391}
392
393#[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#[derive(Debug, Clone, Serialize, Deserialize)]
406pub struct OAuthRedirect {
407 pub redirect_url: String,
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize)]
412#[serde(untagged)]
413pub enum OAuthAuthorizationDetailsResponse {
414 Details(OAuthAuthorizationDetails),
416 Redirect(OAuthRedirect),
418}
419
420#[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#[derive(Debug, Clone)]
432pub struct PkceCodeVerifier(pub(crate) String);
433
434impl PkceCodeVerifier {
435 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#[derive(Debug, Clone)]
449pub struct PkceCodeChallenge(pub(crate) String);
450
451impl PkceCodeChallenge {
452 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#[derive(Debug, Clone)]
466pub struct PkcePair {
467 pub verifier: PkceCodeVerifier,
468 pub challenge: PkceCodeChallenge,
469}
470
471#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
521pub struct JwksResponse {
522 pub keys: Vec<Jwk>,
523}
524
525#[derive(Debug, Clone, Serialize, Deserialize)]
527pub struct Jwk {
528 pub kty: String,
530 #[serde(default)]
532 pub kid: Option<String>,
533 #[serde(default)]
535 pub alg: Option<String>,
536 #[serde(default, rename = "use")]
538 pub use_: Option<String>,
539 #[serde(default)]
541 pub key_ops: Option<Vec<String>>,
542 #[serde(default)]
545 pub n: Option<String>,
546 #[serde(default)]
548 pub e: Option<String>,
549 #[serde(default)]
552 pub crv: Option<String>,
553 #[serde(default)]
555 pub x: Option<String>,
556 #[serde(default)]
558 pub y: Option<String>,
559 #[serde(default)]
561 pub ext: Option<bool>,
562}
563
564#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
630pub enum SignOutScope {
631 Local,
633 Others,
635 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#[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#[derive(Debug, Clone, PartialEq, Eq)]
687pub enum AuthChangeEvent {
688 InitialSession,
690 SignedIn,
692 SignedOut,
694 TokenRefreshed,
696 UserUpdated,
698 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#[derive(Debug, Clone)]
717pub struct AuthStateChange {
718 pub event: AuthChangeEvent,
719 pub session: Option<Session>,
720}
721
722pub struct AuthSubscription {
727 pub(crate) rx: broadcast::Receiver<AuthStateChange>,
728}
729
730impl AuthSubscription {
731 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#[derive(Debug, Clone)]
754pub struct AutoRefreshConfig {
755 pub check_interval: Duration,
757 pub refresh_margin: Duration,
759 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 #[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 #[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 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 assert!(key.crv.is_none());
1164 assert!(key.x.is_none());
1165 assert!(key.y.is_none());
1166 }
1167
1168 #[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}