1use serde::{Deserialize, Serialize};
2use serde_json::Value as JsonValue;
3
4use crate::types::{FactorType, OAuthClientGrantType, OAuthClientResponseType, OtpChannel, OtpType};
5
6#[derive(Debug, Default, Clone, Serialize, Deserialize)]
10pub struct UpdateUserParams {
11 #[serde(skip_serializing_if = "Option::is_none")]
12 pub email: Option<String>,
13 #[serde(skip_serializing_if = "Option::is_none")]
14 pub phone: Option<String>,
15 #[serde(skip_serializing_if = "Option::is_none")]
16 pub password: Option<String>,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 pub data: Option<JsonValue>,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub nonce: Option<String>,
21}
22
23#[derive(Debug, Clone, Serialize)]
27pub struct VerifyOtpParams {
28 pub token: String,
29 #[serde(rename = "type")]
30 pub otp_type: OtpType,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub email: Option<String>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub phone: Option<String>,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub token_hash: Option<String>,
37}
38
39#[derive(Debug, Default, Clone, Serialize, Deserialize)]
43pub struct AdminCreateUserParams {
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub email: Option<String>,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub phone: Option<String>,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub password: Option<String>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub email_confirm: Option<bool>,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub phone_confirm: Option<bool>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub user_metadata: Option<JsonValue>,
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub app_metadata: Option<JsonValue>,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub ban_duration: Option<String>,
60}
61
62#[derive(Debug, Default, Clone, Serialize, Deserialize)]
66pub struct AdminUpdateUserParams {
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub email: Option<String>,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub phone: Option<String>,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub password: Option<String>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub email_confirm: Option<bool>,
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub phone_confirm: Option<bool>,
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub user_metadata: Option<JsonValue>,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 pub app_metadata: Option<JsonValue>,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub ban_duration: Option<String>,
83}
84
85#[derive(Debug, Clone, Serialize)]
87pub struct GenerateLinkParams {
88 #[serde(rename = "type")]
89 pub link_type: GenerateLinkType,
90 pub email: String,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub password: Option<String>,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub data: Option<JsonValue>,
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub redirect_to: Option<String>,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
101#[serde(rename_all = "lowercase")]
102pub enum GenerateLinkType {
103 Signup,
104 Invite,
105 #[serde(rename = "magiclink")]
106 MagicLink,
107 Recovery,
108 #[serde(rename = "email_change_new")]
109 EmailChangeNew,
110 #[serde(rename = "email_change_current")]
111 EmailChangeCurrent,
112}
113
114#[derive(Debug, Clone, Serialize)]
120pub struct MfaEnrollParams {
121 pub factor_type: FactorType,
122 #[serde(skip_serializing_if = "Option::is_none")]
123 pub friendly_name: Option<String>,
124 #[serde(skip_serializing_if = "Option::is_none")]
125 pub issuer: Option<String>,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub phone: Option<String>,
128}
129
130impl MfaEnrollParams {
131 pub fn totp() -> Self {
133 Self {
134 factor_type: FactorType::Totp,
135 friendly_name: None,
136 issuer: None,
137 phone: None,
138 }
139 }
140
141 pub fn phone(number: &str) -> Self {
143 Self {
144 factor_type: FactorType::Phone,
145 friendly_name: None,
146 issuer: None,
147 phone: Some(number.to_string()),
148 }
149 }
150
151 pub fn friendly_name(mut self, name: &str) -> Self {
153 self.friendly_name = Some(name.to_string());
154 self
155 }
156
157 pub fn issuer(mut self, issuer: &str) -> Self {
159 self.issuer = Some(issuer.to_string());
160 self
161 }
162}
163
164#[derive(Debug, Clone, Serialize)]
166pub struct MfaVerifyParams {
167 pub challenge_id: String,
168 pub code: String,
169}
170
171impl MfaVerifyParams {
172 pub fn new(challenge_id: &str, code: &str) -> Self {
174 Self {
175 challenge_id: challenge_id.to_string(),
176 code: code.to_string(),
177 }
178 }
179}
180
181#[derive(Debug, Clone, Default, Serialize)]
183pub struct MfaChallengeParams {
184 #[serde(skip_serializing_if = "Option::is_none")]
186 pub channel: Option<OtpChannel>,
187}
188
189#[derive(Debug, Clone, Serialize)]
195pub struct SsoSignInParams {
196 #[serde(skip_serializing_if = "Option::is_none")]
197 pub domain: Option<String>,
198 #[serde(skip_serializing_if = "Option::is_none")]
199 pub provider_id: Option<String>,
200 #[serde(skip_serializing_if = "Option::is_none")]
201 pub redirect_to: Option<String>,
202 pub skip_http_redirect: bool,
204 #[serde(skip_serializing_if = "Option::is_none")]
205 pub code_challenge: Option<String>,
206 #[serde(skip_serializing_if = "Option::is_none")]
207 pub code_challenge_method: Option<String>,
208}
209
210impl SsoSignInParams {
211 pub fn domain(domain: &str) -> Self {
213 Self {
214 domain: Some(domain.to_string()),
215 provider_id: None,
216 redirect_to: None,
217 skip_http_redirect: true,
218 code_challenge: None,
219 code_challenge_method: None,
220 }
221 }
222
223 pub fn provider_id(id: &str) -> Self {
225 Self {
226 domain: None,
227 provider_id: Some(id.to_string()),
228 redirect_to: None,
229 skip_http_redirect: true,
230 code_challenge: None,
231 code_challenge_method: None,
232 }
233 }
234
235 pub fn redirect_to(mut self, url: &str) -> Self {
237 self.redirect_to = Some(url.to_string());
238 self
239 }
240
241 pub fn code_challenge(mut self, challenge: &str, method: &str) -> Self {
243 self.code_challenge = Some(challenge.to_string());
244 self.code_challenge_method = Some(method.to_string());
245 self
246 }
247}
248
249#[derive(Debug, Clone, Serialize)]
255pub struct SignInWithIdTokenParams {
256 pub provider: String,
257 #[serde(rename = "id_token")]
258 pub id_token: String,
259 #[serde(skip_serializing_if = "Option::is_none")]
260 pub access_token: Option<String>,
261 #[serde(skip_serializing_if = "Option::is_none")]
262 pub nonce: Option<String>,
263}
264
265impl SignInWithIdTokenParams {
266 pub fn new(provider: &str, id_token: &str) -> Self {
268 Self {
269 provider: provider.to_string(),
270 id_token: id_token.to_string(),
271 access_token: None,
272 nonce: None,
273 }
274 }
275
276 pub fn nonce(mut self, nonce: &str) -> Self {
278 self.nonce = Some(nonce.to_string());
279 self
280 }
281
282 pub fn access_token(mut self, token: &str) -> Self {
284 self.access_token = Some(token.to_string());
285 self
286 }
287}
288
289#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
293#[serde(rename_all = "snake_case")]
294pub enum ResendType {
295 Signup,
296 EmailChange,
297 Sms,
298 PhoneChange,
299}
300
301impl std::fmt::Display for ResendType {
302 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303 match self {
304 Self::Signup => write!(f, "signup"),
305 Self::EmailChange => write!(f, "email_change"),
306 Self::Sms => write!(f, "sms"),
307 Self::PhoneChange => write!(f, "phone_change"),
308 }
309 }
310}
311
312#[derive(Debug, Clone, Serialize)]
314pub struct ResendParams {
315 #[serde(rename = "type")]
316 pub resend_type: ResendType,
317 #[serde(skip_serializing_if = "Option::is_none")]
318 pub email: Option<String>,
319 #[serde(skip_serializing_if = "Option::is_none")]
320 pub phone: Option<String>,
321}
322
323impl ResendParams {
324 pub fn email(email: &str, resend_type: ResendType) -> Self {
326 Self {
327 resend_type,
328 email: Some(email.to_string()),
329 phone: None,
330 }
331 }
332
333 pub fn phone(phone: &str, resend_type: ResendType) -> Self {
335 Self {
336 resend_type,
337 email: None,
338 phone: Some(phone.to_string()),
339 }
340 }
341}
342
343#[derive(Debug, Clone, Serialize)]
347pub struct CreateOAuthClientParams {
348 pub client_name: String,
349 pub redirect_uris: Vec<String>,
350 #[serde(skip_serializing_if = "Option::is_none")]
351 pub client_uri: Option<String>,
352 #[serde(skip_serializing_if = "Option::is_none")]
353 pub grant_types: Option<Vec<OAuthClientGrantType>>,
354 #[serde(skip_serializing_if = "Option::is_none")]
355 pub response_types: Option<Vec<OAuthClientResponseType>>,
356 #[serde(skip_serializing_if = "Option::is_none")]
357 pub scope: Option<String>,
358}
359
360impl CreateOAuthClientParams {
361 pub fn new(client_name: &str, redirect_uris: Vec<String>) -> Self {
363 Self {
364 client_name: client_name.to_string(),
365 redirect_uris,
366 client_uri: None,
367 grant_types: None,
368 response_types: None,
369 scope: None,
370 }
371 }
372
373 pub fn client_uri(mut self, uri: &str) -> Self {
375 self.client_uri = Some(uri.to_string());
376 self
377 }
378
379 pub fn grant_types(mut self, types: Vec<OAuthClientGrantType>) -> Self {
381 self.grant_types = Some(types);
382 self
383 }
384
385 pub fn response_types(mut self, types: Vec<OAuthClientResponseType>) -> Self {
387 self.response_types = Some(types);
388 self
389 }
390
391 pub fn scope(mut self, scope: &str) -> Self {
393 self.scope = Some(scope.to_string());
394 self
395 }
396}
397
398#[derive(Debug, Clone, Default, Serialize)]
400pub struct UpdateOAuthClientParams {
401 #[serde(skip_serializing_if = "Option::is_none")]
402 pub client_name: Option<String>,
403 #[serde(skip_serializing_if = "Option::is_none")]
404 pub client_uri: Option<String>,
405 #[serde(skip_serializing_if = "Option::is_none")]
406 pub logo_uri: Option<String>,
407 #[serde(skip_serializing_if = "Option::is_none")]
408 pub redirect_uris: Option<Vec<String>>,
409 #[serde(skip_serializing_if = "Option::is_none")]
410 pub grant_types: Option<Vec<OAuthClientGrantType>>,
411}
412
413impl UpdateOAuthClientParams {
414 pub fn new() -> Self {
416 Self::default()
417 }
418
419 pub fn client_name(mut self, name: &str) -> Self {
421 self.client_name = Some(name.to_string());
422 self
423 }
424
425 pub fn client_uri(mut self, uri: &str) -> Self {
427 self.client_uri = Some(uri.to_string());
428 self
429 }
430
431 pub fn logo_uri(mut self, uri: &str) -> Self {
433 self.logo_uri = Some(uri.to_string());
434 self
435 }
436
437 pub fn redirect_uris(mut self, uris: Vec<String>) -> Self {
439 self.redirect_uris = Some(uris);
440 self
441 }
442
443 pub fn grant_types(mut self, types: Vec<OAuthClientGrantType>) -> Self {
445 self.grant_types = Some(types);
446 self
447 }
448}
449
450#[derive(Debug, Clone)]
454pub struct OAuthAuthorizeUrlParams {
455 pub client_id: String,
456 pub redirect_uri: String,
457 pub scope: Option<String>,
458 pub state: Option<String>,
459 pub code_challenge: Option<String>,
460 pub code_challenge_method: Option<String>,
461}
462
463impl OAuthAuthorizeUrlParams {
464 pub fn new(client_id: &str, redirect_uri: &str) -> Self {
466 Self {
467 client_id: client_id.to_string(),
468 redirect_uri: redirect_uri.to_string(),
469 scope: None,
470 state: None,
471 code_challenge: None,
472 code_challenge_method: None,
473 }
474 }
475
476 pub fn scope(mut self, scope: &str) -> Self {
478 self.scope = Some(scope.to_string());
479 self
480 }
481
482 pub fn state(mut self, state: &str) -> Self {
484 self.state = Some(state.to_string());
485 self
486 }
487
488 pub fn pkce(mut self, challenge: &crate::types::PkceCodeChallenge) -> Self {
490 self.code_challenge = Some(challenge.as_str().to_string());
491 self.code_challenge_method = Some("S256".to_string());
492 self
493 }
494
495 pub fn code_challenge(mut self, challenge: &str, method: &str) -> Self {
497 self.code_challenge = Some(challenge.to_string());
498 self.code_challenge_method = Some(method.to_string());
499 self
500 }
501}
502
503#[derive(Debug, Clone)]
505pub struct OAuthTokenExchangeParams {
506 pub code: String,
507 pub redirect_uri: String,
508 pub client_id: String,
509 pub client_secret: Option<String>,
510 pub code_verifier: Option<String>,
511}
512
513impl OAuthTokenExchangeParams {
514 pub fn new(code: &str, redirect_uri: &str, client_id: &str) -> Self {
516 Self {
517 code: code.to_string(),
518 redirect_uri: redirect_uri.to_string(),
519 client_id: client_id.to_string(),
520 client_secret: None,
521 code_verifier: None,
522 }
523 }
524
525 pub fn client_secret(mut self, secret: &str) -> Self {
527 self.client_secret = Some(secret.to_string());
528 self
529 }
530
531 pub fn code_verifier(mut self, verifier: &str) -> Self {
533 self.code_verifier = Some(verifier.to_string());
534 self
535 }
536
537 pub fn pkce_verifier(mut self, verifier: &crate::types::PkceCodeVerifier) -> Self {
539 self.code_verifier = Some(verifier.as_str().to_string());
540 self
541 }
542}
543
544#[cfg(test)]
545mod tests {
546 use super::*;
547
548 #[test]
549 fn mfa_enroll_params_totp_builder() {
550 let params = MfaEnrollParams::totp()
551 .friendly_name("My Auth")
552 .issuer("MyApp");
553 assert_eq!(params.factor_type, FactorType::Totp);
554 assert_eq!(params.friendly_name.as_deref(), Some("My Auth"));
555 assert_eq!(params.issuer.as_deref(), Some("MyApp"));
556 assert!(params.phone.is_none());
557 }
558
559 #[test]
560 fn mfa_enroll_params_phone_builder() {
561 let params = MfaEnrollParams::phone("+1234567890")
562 .friendly_name("My Phone");
563 assert_eq!(params.factor_type, FactorType::Phone);
564 assert_eq!(params.phone.as_deref(), Some("+1234567890"));
565 assert_eq!(params.friendly_name.as_deref(), Some("My Phone"));
566 }
567
568 #[test]
569 fn mfa_enroll_params_totp_serialization() {
570 let params = MfaEnrollParams::totp().friendly_name("Test");
571 let json = serde_json::to_value(¶ms).unwrap();
572 assert_eq!(json["factor_type"], "totp");
573 assert_eq!(json["friendly_name"], "Test");
574 assert!(json.get("phone").is_none());
575 assert!(json.get("issuer").is_none());
576 }
577
578 #[test]
579 fn mfa_verify_params_new() {
580 let params = MfaVerifyParams::new("challenge-id", "123456");
581 assert_eq!(params.challenge_id, "challenge-id");
582 assert_eq!(params.code, "123456");
583 }
584
585 #[test]
586 fn sso_sign_in_params_domain_builder() {
587 let params = SsoSignInParams::domain("company.com")
588 .redirect_to("https://app.com/callback");
589 assert_eq!(params.domain.as_deref(), Some("company.com"));
590 assert!(params.provider_id.is_none());
591 assert_eq!(params.redirect_to.as_deref(), Some("https://app.com/callback"));
592 assert!(params.skip_http_redirect);
593 }
594
595 #[test]
596 fn sso_sign_in_params_provider_id_builder() {
597 let params = SsoSignInParams::provider_id("uuid-123");
598 assert!(params.domain.is_none());
599 assert_eq!(params.provider_id.as_deref(), Some("uuid-123"));
600 assert!(params.skip_http_redirect);
601 }
602
603 #[test]
604 fn sign_in_with_id_token_params_builder() {
605 let params = SignInWithIdTokenParams::new("google", "eyJ...")
606 .nonce("random-nonce")
607 .access_token("goog-access");
608 assert_eq!(params.provider, "google");
609 assert_eq!(params.id_token, "eyJ...");
610 assert_eq!(params.nonce.as_deref(), Some("random-nonce"));
611 assert_eq!(params.access_token.as_deref(), Some("goog-access"));
612 }
613
614 #[test]
615 fn sign_in_with_id_token_serialization() {
616 let params = SignInWithIdTokenParams::new("apple", "token123");
617 let json = serde_json::to_value(¶ms).unwrap();
618 assert_eq!(json["provider"], "apple");
619 assert_eq!(json["id_token"], "token123");
620 assert!(json.get("nonce").is_none());
621 }
622
623 #[test]
624 fn resend_params_email_builder() {
625 let params = ResendParams::email("user@example.com", ResendType::Signup);
626 assert_eq!(params.resend_type, ResendType::Signup);
627 assert_eq!(params.email.as_deref(), Some("user@example.com"));
628 assert!(params.phone.is_none());
629 }
630
631 #[test]
632 fn resend_params_phone_builder() {
633 let params = ResendParams::phone("+1234567890", ResendType::PhoneChange);
634 assert_eq!(params.resend_type, ResendType::PhoneChange);
635 assert_eq!(params.phone.as_deref(), Some("+1234567890"));
636 assert!(params.email.is_none());
637 }
638
639 #[test]
640 fn resend_type_display() {
641 assert_eq!(ResendType::Signup.to_string(), "signup");
642 assert_eq!(ResendType::EmailChange.to_string(), "email_change");
643 assert_eq!(ResendType::Sms.to_string(), "sms");
644 assert_eq!(ResendType::PhoneChange.to_string(), "phone_change");
645 }
646
647 #[test]
648 fn resend_params_serialization() {
649 let params = ResendParams::email("test@example.com", ResendType::Signup);
650 let json = serde_json::to_value(¶ms).unwrap();
651 assert_eq!(json["type"], "signup");
652 assert_eq!(json["email"], "test@example.com");
653 assert!(json.get("phone").is_none());
654 }
655
656 #[test]
659 fn create_oauth_client_params_builder() {
660 let params = CreateOAuthClientParams::new(
661 "My App",
662 vec!["https://myapp.com/callback".to_string()],
663 )
664 .client_uri("https://myapp.com")
665 .scope("openid profile");
666
667 assert_eq!(params.client_name, "My App");
668 assert_eq!(params.redirect_uris.len(), 1);
669 assert_eq!(params.client_uri.as_deref(), Some("https://myapp.com"));
670 assert_eq!(params.scope.as_deref(), Some("openid profile"));
671 assert!(params.grant_types.is_none());
672 assert!(params.response_types.is_none());
673 }
674
675 #[test]
676 fn create_oauth_client_params_serialization() {
677 let params = CreateOAuthClientParams::new(
678 "Test App",
679 vec!["https://test.com/cb".to_string()],
680 );
681 let json = serde_json::to_value(¶ms).unwrap();
682 assert_eq!(json["client_name"], "Test App");
683 assert_eq!(json["redirect_uris"][0], "https://test.com/cb");
684 assert!(json.get("client_uri").is_none());
686 assert!(json.get("grant_types").is_none());
687 assert!(json.get("scope").is_none());
688 }
689
690 #[test]
691 fn update_oauth_client_params_builder() {
692 let params = UpdateOAuthClientParams::new()
693 .client_name("Updated App")
694 .logo_uri("https://app.com/logo.png")
695 .redirect_uris(vec!["https://app.com/new-cb".to_string()]);
696
697 assert_eq!(params.client_name.as_deref(), Some("Updated App"));
698 assert_eq!(params.logo_uri.as_deref(), Some("https://app.com/logo.png"));
699 assert!(params.redirect_uris.is_some());
700 assert!(params.client_uri.is_none());
701 assert!(params.grant_types.is_none());
702 }
703
704 #[test]
705 fn update_oauth_client_params_serialization() {
706 let params = UpdateOAuthClientParams::new()
707 .client_name("New Name");
708 let json = serde_json::to_value(¶ms).unwrap();
709 assert_eq!(json["client_name"], "New Name");
710 assert!(json.get("client_uri").is_none());
712 assert!(json.get("logo_uri").is_none());
713 assert!(json.get("redirect_uris").is_none());
714 assert!(json.get("grant_types").is_none());
715 }
716
717 #[test]
720 fn oauth_authorize_url_params_builder() {
721 let params = OAuthAuthorizeUrlParams::new("client-123", "https://app.com/cb")
722 .scope("openid profile")
723 .state("random-state");
724 assert_eq!(params.client_id, "client-123");
725 assert_eq!(params.redirect_uri, "https://app.com/cb");
726 assert_eq!(params.scope.as_deref(), Some("openid profile"));
727 assert_eq!(params.state.as_deref(), Some("random-state"));
728 assert!(params.code_challenge.is_none());
729 }
730
731 #[test]
732 fn oauth_authorize_url_params_with_pkce() {
733 let params = OAuthAuthorizeUrlParams::new("client-123", "https://app.com/cb")
734 .code_challenge("challenge-abc", "S256");
735 assert_eq!(params.code_challenge.as_deref(), Some("challenge-abc"));
736 assert_eq!(params.code_challenge_method.as_deref(), Some("S256"));
737 }
738
739 #[test]
740 fn oauth_token_exchange_params_builder() {
741 let params = OAuthTokenExchangeParams::new("code-abc", "https://app.com/cb", "client-123")
742 .client_secret("secret-456")
743 .code_verifier("verifier-789");
744 assert_eq!(params.code, "code-abc");
745 assert_eq!(params.redirect_uri, "https://app.com/cb");
746 assert_eq!(params.client_id, "client-123");
747 assert_eq!(params.client_secret.as_deref(), Some("secret-456"));
748 assert_eq!(params.code_verifier.as_deref(), Some("verifier-789"));
749 }
750
751 #[test]
752 fn oauth_token_exchange_params_minimal() {
753 let params = OAuthTokenExchangeParams::new("code-abc", "https://app.com/cb", "client-123");
754 assert!(params.client_secret.is_none());
755 assert!(params.code_verifier.is_none());
756 }
757}