1use reqwest::Client;
7use serde::{Deserialize, Serialize};
8use std::sync::Arc;
9use std::sync::RwLock;
10use thiserror::Error;
11
12#[derive(Error, Debug)]
14pub enum AuthError {
15 #[error("API error: {0}")]
16 ApiError(String),
17
18 #[error("Authentication error: {0}")]
19 AuthenticationError(String),
20
21 #[error("Network error: {0}")]
22 NetworkError(#[from] reqwest::Error),
23
24 #[error("JSON serialization error: {0}")]
25 SerializationError(#[from] serde_json::Error),
26
27 #[error("Missing session")]
28 MissingSession,
29
30 #[error("Invalid token: {0}")]
31 InvalidToken(String),
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct User {
37 pub id: String,
38 pub email: Option<String>,
39 pub phone: Option<String>,
40 pub app_metadata: serde_json::Value,
41 pub user_metadata: serde_json::Value,
42 pub created_at: String,
43 pub updated_at: String,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct Session {
49 pub access_token: String,
50 pub refresh_token: String,
51 pub expires_in: i64,
52 pub token_type: String,
53 pub user: User,
54}
55
56#[derive(Debug, Serialize)]
58pub struct SignInCredentials {
59 pub email: String,
60 pub password: String,
61}
62
63#[derive(Debug, Clone)]
65pub struct AuthOptions {
66 pub auto_refresh_token: bool,
67 pub persist_session: bool,
68 pub detect_session_in_url: bool,
69}
70
71impl Default for AuthOptions {
72 fn default() -> Self {
73 Self {
74 auto_refresh_token: true,
75 persist_session: true,
76 detect_session_in_url: true,
77 }
78 }
79}
80
81#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
83pub enum OAuthProvider {
84 Google,
85 Facebook,
86 Twitter,
87 Github,
88 Apple,
89 Discord,
90 Gitlab,
91 Bitbucket,
92 Linkedin,
93 Microsoft,
94 Slack,
95 Spotify,
96}
97
98impl OAuthProvider {
99 fn display(&self) -> &'static str {
100 match self {
101 Self::Google => "google",
102 Self::Facebook => "facebook",
103 Self::Twitter => "twitter",
104 Self::Github => "github",
105 Self::Apple => "apple",
106 Self::Discord => "discord",
107 Self::Gitlab => "gitlab",
108 Self::Bitbucket => "bitbucket",
109 Self::Linkedin => "linkedin",
110 Self::Microsoft => "microsoft",
111 Self::Slack => "slack",
112 Self::Spotify => "spotify",
113 }
114 }
115}
116
117#[derive(Debug, Clone, Serialize, Default)]
119pub struct OAuthSignInOptions {
120 pub redirect_to: Option<String>,
121 pub scopes: Option<String>,
122 pub provider_scope: Option<String>,
123 pub skip_browser_redirect: Option<bool>,
124}
125
126#[derive(Debug, Clone, Serialize, Default)]
128pub struct EmailConfirmOptions {
129 pub redirect_to: Option<String>,
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
134#[serde(rename_all = "lowercase")]
135pub enum MFAFactorType {
136 Totp,
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(rename_all = "lowercase")]
142pub enum MFAFactorStatus {
143 Unverified,
144 Verified,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct MFAFactor {
150 pub id: String,
151 pub friendly_name: Option<String>,
152 #[serde(rename = "factor_type")]
153 pub factor_type: MFAFactorType,
154 pub status: MFAFactorStatus,
155 pub created_at: String,
156 pub updated_at: String,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct MFAChallenge {
162 pub id: String,
163 #[serde(rename = "factor_id")]
164 pub factor_id: String,
165 pub created_at: String,
166 pub expires_at: Option<String>,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct MFAVerifyResponse {
172 pub access_token: String,
173 pub refresh_token: Option<String>,
174 #[serde(rename = "type")]
175 pub token_type: String,
176 pub expires_in: i64,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct TOTPSetupInfo {
182 pub qr_code: String,
183 pub secret: String,
184 pub uri: String,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct PhoneVerificationResponse {
190 pub phone: String,
191 pub verification_id: String,
192 pub expires_at: String,
193}
194
195pub struct Auth {
197 url: String,
198 key: String,
199 http_client: Client,
200 options: AuthOptions,
201 current_session: Arc<RwLock<Option<Session>>>,
202 admin: Option<AdminAuth>,
203}
204
205pub struct AdminAuth {
207 url: String,
208 service_role_key: String,
209 http_client: Client,
210}
211
212impl AdminAuth {
214 pub fn new(url: &str, service_role_key: &str, http_client: Client) -> Self {
216 Self {
217 url: url.to_string(),
218 service_role_key: service_role_key.to_string(),
219 http_client,
220 }
221 }
222
223 pub async fn get_user_by_id(&self, user_id: &str) -> Result<User, AuthError> {
247 let url = format!("{}/admin/users/{}", self.url, user_id);
248
249 let response = self
250 .http_client
251 .get(&url)
252 .header("apikey", &self.service_role_key)
253 .header(
254 "Authorization",
255 format!("Bearer {}", &self.service_role_key),
256 )
257 .send()
258 .await?;
259
260 if !response.status().is_success() {
261 let error_text = response.text().await.unwrap_or_default();
262 return Err(AuthError::ApiError(format!(
263 "Failed to get user: {}",
264 error_text
265 )));
266 }
267
268 let user_data = response.json::<serde_json::Value>().await?;
269
270 match serde_json::from_value::<User>(user_data) {
271 Ok(user) => Ok(user),
272 Err(err) => Err(AuthError::SerializationError(err)),
273 }
274 }
275
276 pub async fn list_users(
300 &self,
301 page: Option<u32>,
302 per_page: Option<u32>,
303 ) -> Result<Vec<User>, AuthError> {
304 let page = page.unwrap_or(1);
305 let per_page = per_page.unwrap_or(50);
306
307 let url = format!(
308 "{}/admin/users?page={}&per_page={}",
309 self.url, page, per_page
310 );
311
312 let response = self
313 .http_client
314 .get(&url)
315 .header("apikey", &self.service_role_key)
316 .header(
317 "Authorization",
318 format!("Bearer {}", &self.service_role_key),
319 )
320 .send()
321 .await?;
322
323 if !response.status().is_success() {
324 let error_text = response.text().await.unwrap_or_default();
325 return Err(AuthError::ApiError(format!(
326 "Failed to list users: {}",
327 error_text
328 )));
329 }
330
331 let users_data = response.json::<serde_json::Value>().await?;
332
333 match serde_json::from_value::<Vec<User>>(users_data) {
334 Ok(users) => Ok(users),
335 Err(err) => Err(AuthError::SerializationError(err)),
336 }
337 }
338
339 pub async fn create_user(
380 &self,
381 email: &str,
382 password: Option<&str>,
383 user_metadata: Option<serde_json::Value>,
384 email_confirm: Option<bool>,
385 ) -> Result<User, AuthError> {
386 let url = format!("{}/admin/users", self.url);
387
388 let mut payload = serde_json::json!({
389 "email": email,
390 "email_confirm": email_confirm.unwrap_or(false)
391 });
392
393 if let Some(pw) = password {
394 payload["password"] = serde_json::Value::String(pw.to_string());
395 }
396
397 if let Some(metadata) = user_metadata {
398 payload["user_metadata"] = metadata;
399 }
400
401 let response = self
402 .http_client
403 .post(&url)
404 .header("apikey", &self.service_role_key)
405 .header(
406 "Authorization",
407 format!("Bearer {}", &self.service_role_key),
408 )
409 .json(&payload)
410 .send()
411 .await?;
412
413 if !response.status().is_success() {
414 let error_text = response.text().await.unwrap_or_default();
415 return Err(AuthError::ApiError(format!(
416 "Failed to create user: {}",
417 error_text
418 )));
419 }
420
421 let user_data = response.json::<serde_json::Value>().await?;
422
423 match serde_json::from_value::<User>(user_data) {
424 Ok(user) => Ok(user),
425 Err(err) => Err(AuthError::SerializationError(err)),
426 }
427 }
428
429 pub async fn delete_user(&self, user_id: &str) -> Result<(), AuthError> {
457 let url = format!("{}/admin/users/{}", self.url, user_id);
458
459 let response = self
460 .http_client
461 .delete(&url)
462 .header("apikey", &self.service_role_key)
463 .header(
464 "Authorization",
465 format!("Bearer {}", &self.service_role_key),
466 )
467 .send()
468 .await?;
469
470 if !response.status().is_success() {
471 let error_text = response.text().await.unwrap_or_default();
472 return Err(AuthError::ApiError(format!(
473 "Failed to delete user: {}",
474 error_text
475 )));
476 }
477
478 Ok(())
479 }
480
481 pub async fn update_user(
522 &self,
523 user_id: &str,
524 attributes: serde_json::Value,
525 ) -> Result<User, AuthError> {
526 let url = format!("{}/admin/users/{}", self.url, user_id);
527
528 let response = self
529 .http_client
530 .put(&url)
531 .header("apikey", &self.service_role_key)
532 .header(
533 "Authorization",
534 format!("Bearer {}", &self.service_role_key),
535 )
536 .json(&attributes)
537 .send()
538 .await?;
539
540 if !response.status().is_success() {
541 let error_text = response.text().await.unwrap_or_default();
542 return Err(AuthError::ApiError(format!(
543 "Failed to update user: {}",
544 error_text
545 )));
546 }
547
548 let user_data = response.json::<serde_json::Value>().await?;
549
550 match serde_json::from_value::<User>(user_data) {
551 Ok(user) => Ok(user),
552 Err(err) => Err(AuthError::SerializationError(err)),
553 }
554 }
555
556 pub async fn invite_user_by_email(
588 &self,
589 email: &str,
590 redirect_to: Option<&str>,
591 ) -> Result<User, AuthError> {
592 let url = format!("{}/admin/users/invite", self.url);
593
594 let mut payload = serde_json::json!({
595 "email": email
596 });
597
598 if let Some(redirect) = redirect_to {
599 payload["redirect_to"] = serde_json::Value::String(redirect.to_string());
600 }
601
602 let response = self
603 .http_client
604 .post(&url)
605 .header("apikey", &self.service_role_key)
606 .header(
607 "Authorization",
608 format!("Bearer {}", &self.service_role_key),
609 )
610 .json(&payload)
611 .send()
612 .await?;
613
614 if !response.status().is_success() {
615 let error_text = response.text().await.unwrap_or_default();
616 return Err(AuthError::ApiError(format!(
617 "Failed to invite user: {}",
618 error_text
619 )));
620 }
621
622 let user_data = response.json::<serde_json::Value>().await?;
623
624 match serde_json::from_value::<User>(user_data) {
625 Ok(user) => Ok(user),
626 Err(err) => Err(AuthError::SerializationError(err)),
627 }
628 }
629
630 pub async fn delete_user_factor(
662 &self,
663 user_id: &str,
664 factor_id: &str,
665 ) -> Result<(), AuthError> {
666 let url = format!("{}/admin/users/{}/factors/{}", self.url, user_id, factor_id);
667
668 let response = self
669 .http_client
670 .delete(&url)
671 .header("apikey", &self.service_role_key)
672 .header(
673 "Authorization",
674 format!("Bearer {}", &self.service_role_key),
675 )
676 .send()
677 .await?;
678
679 if !response.status().is_success() {
680 let error_text = response.text().await.unwrap_or_default();
681 return Err(AuthError::ApiError(format!(
682 "Failed to delete user factor: {}",
683 error_text
684 )));
685 }
686
687 Ok(())
688 }
689
690 pub async fn generate_link(
724 &self,
725 email: &str,
726 link_type: &str,
727 redirect_to: Option<&str>,
728 ) -> Result<String, AuthError> {
729 let url = format!("{}/admin/users/generate_link", self.url);
730
731 let mut payload = serde_json::json!({
732 "email": email,
733 "type": link_type
734 });
735
736 if let Some(redirect) = redirect_to {
737 payload["redirect_to"] = serde_json::Value::String(redirect.to_string());
738 }
739
740 let response = self
741 .http_client
742 .post(&url)
743 .header("apikey", &self.service_role_key)
744 .header(
745 "Authorization",
746 format!("Bearer {}", &self.service_role_key),
747 )
748 .json(&payload)
749 .send()
750 .await?;
751
752 if !response.status().is_success() {
753 let error_text = response.text().await.unwrap_or_default();
754 return Err(AuthError::ApiError(format!(
755 "Failed to generate link: {}",
756 error_text
757 )));
758 }
759
760 let data = response.json::<serde_json::Value>().await?;
761
762 match data.get("action_link") {
763 Some(link) => match link.as_str() {
764 Some(s) => Ok(s.to_string()),
765 None => Err(AuthError::ApiError("Invalid link format".to_string())),
766 },
767 None => Err(AuthError::ApiError("No link returned".to_string())),
768 }
769 }
770}
771
772impl Auth {
773 pub fn new(url: &str, key: &str, http_client: Client, options: AuthOptions) -> Self {
775 Self {
776 url: url.to_string(),
777 key: key.to_string(),
778 http_client: http_client.clone(),
779 options,
780 current_session: Arc::new(RwLock::new(None)),
781 admin: None,
782 }
783 }
784
785 pub fn init_admin(&mut self, service_role_key: &str) -> &Self {
804 self.admin = Some(AdminAuth::new(
805 &self.url,
806 service_role_key,
807 self.http_client.clone(),
808 ));
809 self
810 }
811
812 pub fn admin(&self) -> Option<&AdminAuth> {
831 self.admin.as_ref()
832 }
833
834 pub async fn sign_up(&self, email: &str, password: &str) -> Result<Session, AuthError> {
836 let url = format!("{}/auth/v1/signup", self.url);
837
838 let payload = serde_json::json!({
839 "email": email,
840 "password": password,
841 });
842
843 let response = self
844 .http_client
845 .post(&url)
846 .header("apikey", &self.key)
847 .header("Content-Type", "application/json")
848 .json(&payload)
849 .send()
850 .await?;
851
852 if !response.status().is_success() {
853 let error_text = response.text().await?;
854 return Err(AuthError::ApiError(error_text));
855 }
856
857 let session: Session = response.json().await?;
858
859 if self.options.persist_session {
861 let mut write_guard = self.current_session.write().unwrap();
862 *write_guard = Some(session.clone());
863 }
864
865 Ok(session)
866 }
867
868 pub async fn sign_in_with_password(
870 &self,
871 email: &str,
872 password: &str,
873 ) -> Result<Session, AuthError> {
874 let url = format!("{}/auth/v1/token?grant_type=password", self.url);
875
876 let payload = serde_json::json!({
877 "email": email,
878 "password": password,
879 });
880
881 let response = self
882 .http_client
883 .post(&url)
884 .header("apikey", &self.key)
885 .header("Content-Type", "application/json")
886 .json(&payload)
887 .send()
888 .await?;
889
890 if !response.status().is_success() {
891 let error_text = response.text().await?;
892 return Err(AuthError::ApiError(error_text));
893 }
894
895 let session: Session = response.json().await?;
896
897 if self.options.persist_session {
899 let mut write_guard = self.current_session.write().unwrap();
900 *write_guard = Some(session.clone());
901 }
902
903 Ok(session)
904 }
905
906 pub fn get_session(&self) -> Option<Session> {
908 let read_guard = self.current_session.read().unwrap();
909 read_guard.clone()
910 }
911
912 pub async fn get_user(&self) -> Result<User, AuthError> {
914 let session = self.get_session().ok_or(AuthError::MissingSession)?;
915
916 let url = format!("{}/auth/v1/user", self.url);
917
918 let response = self
919 .http_client
920 .get(&url)
921 .header("apikey", &self.key)
922 .header("Authorization", format!("Bearer {}", session.access_token))
923 .send()
924 .await?;
925
926 if !response.status().is_success() {
927 let error_text = response.text().await?;
928 return Err(AuthError::ApiError(error_text));
929 }
930
931 let user: User = response.json().await?;
932
933 Ok(user)
934 }
935
936 pub async fn refresh_session(&self) -> Result<Session, AuthError> {
938 let session = self.get_session().ok_or(AuthError::MissingSession)?;
939
940 let url = format!("{}/auth/v1/token?grant_type=refresh_token", self.url);
941
942 let payload = serde_json::json!({
943 "refresh_token": session.refresh_token,
944 });
945
946 let response = self
947 .http_client
948 .post(&url)
949 .header("apikey", &self.key)
950 .header("Content-Type", "application/json")
951 .json(&payload)
952 .send()
953 .await?;
954
955 if !response.status().is_success() {
956 let error_text = response.text().await?;
957 return Err(AuthError::ApiError(error_text));
958 }
959
960 let new_session: Session = response.json().await?;
961
962 if self.options.persist_session {
964 let mut write_guard = self.current_session.write().unwrap();
965 *write_guard = Some(new_session.clone());
966 }
967
968 Ok(new_session)
969 }
970
971 pub async fn sign_out(&self) -> Result<(), AuthError> {
973 let session = self.get_session().ok_or(AuthError::MissingSession)?;
974
975 let url = format!("{}/auth/v1/logout", self.url);
976
977 let response = self
978 .http_client
979 .post(&url)
980 .header("apikey", &self.key)
981 .header("Authorization", format!("Bearer {}", session.access_token))
982 .send()
983 .await?;
984
985 if !response.status().is_success() {
986 let error_text = response.text().await?;
987 return Err(AuthError::ApiError(error_text));
988 }
989
990 let mut write_guard = self.current_session.write().unwrap();
992 *write_guard = None;
993
994 Ok(())
995 }
996
997 pub async fn reset_password_for_email(&self, email: &str) -> Result<(), AuthError> {
999 let url = format!("{}/auth/v1/recover", self.url);
1000
1001 let payload = serde_json::json!({
1002 "email": email,
1003 });
1004
1005 let response = self
1006 .http_client
1007 .post(&url)
1008 .header("apikey", &self.key)
1009 .header("Content-Type", "application/json")
1010 .json(&payload)
1011 .send()
1012 .await?;
1013
1014 if !response.status().is_success() {
1015 let error_text = response.text().await?;
1016 return Err(AuthError::ApiError(error_text));
1017 }
1018
1019 Ok(())
1020 }
1021
1022 pub fn get_oauth_sign_in_url(
1024 &self,
1025 provider: OAuthProvider,
1026 options: Option<OAuthSignInOptions>,
1027 ) -> String {
1028 let provider_id = provider.display();
1029 let options = options.unwrap_or_default();
1030
1031 let mut url = format!("{}/auth/v1/authorize?provider={}", self.url, provider_id);
1032
1033 if let Some(redirect_to) = options.redirect_to {
1034 url.push_str(&format!(
1035 "&redirect_to={}",
1036 urlencoding::encode(&redirect_to)
1037 ));
1038 }
1039
1040 if let Some(scopes) = options.scopes {
1041 url.push_str(&format!("&scopes={}", urlencoding::encode(&scopes)));
1042 }
1043
1044 if let Some(provider_scope) = options.provider_scope {
1045 url.push_str(&format!(
1046 "&provider_scope={}",
1047 urlencoding::encode(&provider_scope)
1048 ));
1049 }
1050
1051 url
1052 }
1053
1054 pub async fn sign_in_with_oauth(
1056 &self,
1057 provider: OAuthProvider,
1058 options: Option<OAuthSignInOptions>,
1059 ) -> Result<String, AuthError> {
1060 let url = self.get_oauth_sign_in_url(provider, options.clone());
1062
1063 let skip_browser_redirect = options
1065 .and_then(|opt| opt.skip_browser_redirect)
1066 .unwrap_or(false);
1067
1068 if skip_browser_redirect {
1069 return Ok(url);
1070 }
1071
1072 Ok(url)
1075 }
1076
1077 pub async fn exchange_code_for_session(&self, code: &str) -> Result<Session, AuthError> {
1079 let url = format!("{}/auth/v1/token?grant_type=authorization_code", self.url);
1080
1081 let payload = serde_json::json!({
1082 "code": code,
1083 });
1084
1085 let response = self
1086 .http_client
1087 .post(&url)
1088 .header("apikey", &self.key)
1089 .header("Content-Type", "application/json")
1090 .json(&payload)
1091 .send()
1092 .await?;
1093
1094 if !response.status().is_success() {
1095 let error_text = response.text().await?;
1096 return Err(AuthError::ApiError(error_text));
1097 }
1098
1099 let session: Session = response.json().await?;
1100
1101 if self.options.persist_session {
1103 let mut write_guard = self.current_session.write().unwrap();
1104 *write_guard = Some(session.clone());
1105 }
1106
1107 Ok(session)
1108 }
1109
1110 pub async fn sign_in_with_password_mfa(
1115 &self,
1116 email: &str,
1117 password: &str,
1118 ) -> Result<Result<Session, MFAChallenge>, AuthError> {
1119 let url = format!("{}/auth/v1/token?grant_type=password", self.url);
1120
1121 let payload = serde_json::json!({
1122 "email": email,
1123 "password": password,
1124 });
1125
1126 let response = self
1127 .http_client
1128 .post(&url)
1129 .header("apikey", &self.key)
1130 .header("Content-Type", "application/json")
1131 .json(&payload)
1132 .send()
1133 .await?;
1134
1135 let status = response.status();
1137 let body = response.text().await?;
1138
1139 if status.is_success() {
1140 let session: Session = serde_json::from_str(&body)?;
1142
1143 if self.options.persist_session {
1145 let mut write_guard = self.current_session.write().unwrap();
1146 *write_guard = Some(session.clone());
1147 }
1148
1149 Ok(Ok(session))
1150 } else if status.as_u16() == 401 {
1151 if let Ok(challenge) = serde_json::from_str::<MFAChallenge>(&body) {
1153 Ok(Err(challenge))
1155 } else {
1156 Err(AuthError::ApiError(body))
1158 }
1159 } else {
1160 Err(AuthError::ApiError(body))
1162 }
1163 }
1164
1165 pub async fn verify_mfa_challenge(
1167 &self,
1168 challenge_id: &str,
1169 code: &str,
1170 ) -> Result<Session, AuthError> {
1171 let url = format!("{}/auth/v1/mfa/verify", self.url);
1172
1173 let payload = serde_json::json!({
1174 "challenge_id": challenge_id,
1175 "code": code,
1176 });
1177
1178 let response = self
1179 .http_client
1180 .post(&url)
1181 .header("apikey", &self.key)
1182 .header("Content-Type", "application/json")
1183 .json(&payload)
1184 .send()
1185 .await?;
1186
1187 if !response.status().is_success() {
1188 let error_text = response.text().await?;
1189 return Err(AuthError::ApiError(error_text));
1190 }
1191
1192 let verify_response: MFAVerifyResponse = response.json().await?;
1193
1194 let user = self
1196 .get_user_by_token(&verify_response.access_token)
1197 .await?;
1198
1199 let session = Session {
1200 access_token: verify_response.access_token,
1201 refresh_token: verify_response.refresh_token.unwrap_or_default(),
1202 expires_in: verify_response.expires_in,
1203 token_type: verify_response.token_type,
1204 user,
1205 };
1206
1207 if self.options.persist_session {
1209 let mut write_guard = self.current_session.write().unwrap();
1210 *write_guard = Some(session.clone());
1211 }
1212
1213 Ok(session)
1214 }
1215
1216 pub async fn enroll_totp(&self) -> Result<TOTPSetupInfo, AuthError> {
1218 let session = self.get_session().ok_or(AuthError::MissingSession)?;
1219
1220 let url = format!("{}/auth/v1/mfa/totp", self.url);
1221
1222 let response = self
1223 .http_client
1224 .post(&url)
1225 .header("apikey", &self.key)
1226 .header("Authorization", format!("Bearer {}", session.access_token))
1227 .send()
1228 .await?;
1229
1230 if !response.status().is_success() {
1231 let error_text = response.text().await?;
1232 return Err(AuthError::ApiError(error_text));
1233 }
1234
1235 let setup_info: TOTPSetupInfo = response.json().await?;
1236
1237 Ok(setup_info)
1238 }
1239
1240 pub async fn verify_totp(&self, factor_id: &str, code: &str) -> Result<MFAFactor, AuthError> {
1242 let session = self.get_session().ok_or(AuthError::MissingSession)?;
1243
1244 let url = format!("{}/auth/v1/mfa/totp/verify", self.url);
1245
1246 let payload = serde_json::json!({
1247 "factor_id": factor_id,
1248 "code": code,
1249 });
1250
1251 let response = self
1252 .http_client
1253 .post(&url)
1254 .header("apikey", &self.key)
1255 .header("Authorization", format!("Bearer {}", session.access_token))
1256 .header("Content-Type", "application/json")
1257 .json(&payload)
1258 .send()
1259 .await?;
1260
1261 if !response.status().is_success() {
1262 let error_text = response.text().await?;
1263 return Err(AuthError::ApiError(error_text));
1264 }
1265
1266 let factor: MFAFactor = response.json().await?;
1267
1268 Ok(factor)
1269 }
1270
1271 pub async fn list_factors(&self) -> Result<Vec<MFAFactor>, AuthError> {
1273 let session = self.get_session().ok_or(AuthError::MissingSession)?;
1274
1275 let url = format!("{}/auth/v1/mfa/factors", self.url);
1276
1277 let response = self
1278 .http_client
1279 .get(&url)
1280 .header("apikey", &self.key)
1281 .header("Authorization", format!("Bearer {}", session.access_token))
1282 .send()
1283 .await?;
1284
1285 if !response.status().is_success() {
1286 let error_text = response.text().await?;
1287 return Err(AuthError::ApiError(error_text));
1288 }
1289
1290 let factors: Vec<MFAFactor> = response.json().await?;
1291
1292 Ok(factors)
1293 }
1294
1295 pub async fn unenroll_factor(&self, factor_id: &str) -> Result<(), AuthError> {
1297 let session = self.get_session().ok_or(AuthError::MissingSession)?;
1298
1299 let url = format!("{}/auth/v1/mfa/factors/{}", self.url, factor_id);
1300
1301 let response = self
1302 .http_client
1303 .delete(&url)
1304 .header("apikey", &self.key)
1305 .header("Authorization", format!("Bearer {}", session.access_token))
1306 .send()
1307 .await?;
1308
1309 if !response.status().is_success() {
1310 let error_text = response.text().await?;
1311 return Err(AuthError::ApiError(error_text));
1312 }
1313
1314 Ok(())
1315 }
1316
1317 async fn get_user_by_token(&self, token: &str) -> Result<User, AuthError> {
1319 let url = format!("{}/auth/v1/user", self.url);
1320
1321 let response = self
1322 .http_client
1323 .get(&url)
1324 .header("apikey", &self.key)
1325 .header("Authorization", format!("Bearer {}", token))
1326 .send()
1327 .await?;
1328
1329 if !response.status().is_success() {
1330 let error_text = response.text().await?;
1331 return Err(AuthError::ApiError(error_text));
1332 }
1333
1334 let user: User = response.json().await?;
1335
1336 Ok(user)
1337 }
1338
1339 pub async fn sign_in_anonymously(&self) -> Result<Session, AuthError> {
1341 let endpoint = format!("{}/auth/v1/signup", self.url);
1342
1343 let response = self
1344 .http_client
1345 .post(&endpoint)
1346 .header("apikey", &self.key)
1347 .header("Content-Type", "application/json")
1348 .json(&serde_json::json!({
1349 "data": {}
1350 }))
1351 .send()
1352 .await?;
1353
1354 if !response.status().is_success() {
1355 let error_msg = response.text().await?;
1356 return Err(AuthError::ApiError(error_msg));
1357 }
1358
1359 let session: Session = response.json().await?;
1360
1361 if self.options.persist_session {
1363 let mut writable_session = self.current_session.write().unwrap();
1364 *writable_session = Some(session.clone());
1365 }
1366
1367 Ok(session)
1368 }
1369
1370 pub async fn send_confirm_email_request(
1394 &self,
1395 email: &str,
1396 options: Option<EmailConfirmOptions>,
1397 ) -> Result<(), AuthError> {
1398 let endpoint = format!("{}/auth/v1/signup", self.url);
1399
1400 let mut payload = serde_json::json!({
1401 "email": email,
1402 "data": {}
1403 });
1404
1405 if let Some(opts) = options {
1406 if let Some(redirect_to) = opts.redirect_to {
1407 payload["options"] = serde_json::json!({
1408 "redirect_to": redirect_to
1409 });
1410 }
1411 }
1412
1413 let response = self
1414 .http_client
1415 .post(&endpoint)
1416 .header("apikey", &self.key)
1417 .header("Content-Type", "application/json")
1418 .json(&payload)
1419 .send()
1420 .await?;
1421
1422 if !response.status().is_success() {
1423 let error_msg = response.text().await?;
1424 return Err(AuthError::ApiError(error_msg));
1425 }
1426
1427 Ok(())
1428 }
1429
1430 pub async fn verify_email(&self, token: &str) -> Result<Session, AuthError> {
1450 let endpoint = format!("{}/auth/v1/verify", self.url);
1451
1452 let response = self
1453 .http_client
1454 .post(&endpoint)
1455 .header("apikey", &self.key)
1456 .header("Content-Type", "application/json")
1457 .json(&serde_json::json!({
1458 "type": "signup",
1459 "token": token
1460 }))
1461 .send()
1462 .await?;
1463
1464 if !response.status().is_success() {
1465 let error_msg = response.text().await?;
1466 return Err(AuthError::ApiError(error_msg));
1467 }
1468
1469 let session: Session = response.json().await?;
1470
1471 if self.options.persist_session {
1473 let mut writable_session = self.current_session.write().unwrap();
1474 *writable_session = Some(session.clone());
1475 }
1476
1477 Ok(session)
1478 }
1479
1480 pub async fn verify_password_reset(
1501 &self,
1502 token: &str,
1503 new_password: &str,
1504 ) -> Result<Session, AuthError> {
1505 let endpoint = format!("{}/auth/v1/verify", self.url);
1506
1507 let response = self
1508 .http_client
1509 .post(&endpoint)
1510 .header("apikey", &self.key)
1511 .header("Content-Type", "application/json")
1512 .json(&serde_json::json!({
1513 "type": "recovery",
1514 "token": token,
1515 "password": new_password
1516 }))
1517 .send()
1518 .await?;
1519
1520 if !response.status().is_success() {
1521 let error_msg = response.text().await?;
1522 return Err(AuthError::ApiError(error_msg));
1523 }
1524
1525 let session: Session = response.json().await?;
1526
1527 if self.options.persist_session {
1529 let mut writable_session = self.current_session.write().unwrap();
1530 *writable_session = Some(session.clone());
1531 }
1532
1533 Ok(session)
1534 }
1535
1536 pub async fn send_verification_code(
1537 &self,
1538 phone: &str,
1539 ) -> Result<PhoneVerificationResponse, AuthError> {
1540 let url = format!("{}/auth/v1/otp", self.url);
1541
1542 let payload = serde_json::json!({
1543 "phone": phone,
1544 "channel": "sms"
1545 });
1546
1547 let response = self
1548 .http_client
1549 .post(&url)
1550 .header("apikey", &self.key)
1551 .header("Content-Type", "application/json")
1552 .json(&payload)
1553 .send()
1554 .await?;
1555
1556 if !response.status().is_success() {
1557 let error_text = response.text().await?;
1558 return Err(AuthError::ApiError(error_text));
1559 }
1560
1561 let verification: PhoneVerificationResponse = response.json().await?;
1562 Ok(verification)
1563 }
1564
1565 pub async fn verify_phone_code(
1567 &self,
1568 phone: &str,
1569 verification_id: &str,
1570 code: &str,
1571 ) -> Result<Session, AuthError> {
1572 let url = format!("{}/auth/v1/verify", self.url);
1573
1574 let payload = serde_json::json!({
1575 "phone": phone,
1576 "verification_id": verification_id,
1577 "code": code,
1578 "type": "sms"
1579 });
1580
1581 let response = self
1582 .http_client
1583 .post(&url)
1584 .header("apikey", &self.key)
1585 .header("Content-Type", "application/json")
1586 .json(&payload)
1587 .send()
1588 .await?;
1589
1590 if !response.status().is_success() {
1591 let error_text = response.text().await?;
1592 return Err(AuthError::ApiError(error_text));
1593 }
1594
1595 let session: Session = response.json().await?;
1596
1597 if self.options.persist_session {
1599 let mut write_guard = self.current_session.write().unwrap();
1600 *write_guard = Some(session.clone());
1601 }
1602
1603 Ok(session)
1604 }
1605}
1606
1607#[cfg(test)]
1608mod tests {
1609 use super::*;
1610 use wiremock::matchers::{method, path};
1611 use wiremock::{Mock, MockServer, ResponseTemplate};
1612 #[test]
1615 fn test_sign_up() {
1616 tokio_test::block_on(async {
1617 let mock_server = MockServer::start().await;
1618
1619 let response_body = serde_json::json!({
1620 "access_token": "test_access_token",
1621 "refresh_token": "test_refresh_token",
1622 "expires_in": 3600,
1623 "token_type": "bearer",
1624 "user": {
1625 "id": "test_user_id",
1626 "email": "test@example.com",
1627 "phone": null,
1628 "app_metadata": {},
1629 "user_metadata": {},
1630 "created_at": "2021-01-01T00:00:00Z",
1631 "updated_at": "2021-01-01T00:00:00Z"
1632 }
1633 });
1634
1635 Mock::given(method("POST"))
1636 .and(path("/auth/v1/signup"))
1637 .respond_with(ResponseTemplate::new(200).set_body_json(&response_body))
1638 .mount(&mock_server)
1639 .await;
1640
1641 let http_client = Client::new();
1642 let auth = Auth::new(
1643 &mock_server.uri(),
1644 "test_key",
1645 http_client,
1646 AuthOptions::default(),
1647 );
1648
1649 let result = auth.sign_up("test@example.com", "password123").await;
1650
1651 assert!(result.is_ok());
1652 let session = result.unwrap();
1653 assert_eq!(session.access_token, "test_access_token");
1654 assert_eq!(session.user.email, Some("test@example.com".to_string()));
1655 });
1656 }
1657
1658 #[test]
1659 fn test_oauth_sign_in_url() {
1660 tokio_test::block_on(async {
1661 let client = Client::new();
1662 let auth = Auth::new(
1663 "https://example.supabase.co",
1664 "test-key",
1665 client,
1666 AuthOptions::default(),
1667 );
1668
1669 let url = auth.get_oauth_sign_in_url(super::OAuthProvider::Google, None);
1670 assert!(url.contains("provider=google"));
1671
1672 let options = super::OAuthSignInOptions {
1673 redirect_to: Some("https://example.com/callback".to_string()),
1674 scopes: Some("email profile".to_string()),
1675 ..Default::default()
1676 };
1677
1678 let url_with_options =
1679 auth.get_oauth_sign_in_url(super::OAuthProvider::Github, Some(options));
1680 assert!(url_with_options.contains("provider=github"));
1681 assert!(url_with_options.contains("redirect_to="));
1682 assert!(url_with_options.contains("scopes="));
1683 });
1684 }
1685}