supabase_auth/
client.rs

1/*!
2The `client` module provides a comprehensive interface for interacting with Supabase Authentication services.
3
4This module enables user authentication, session management, user administration, and server health monitoring
5through the [`AuthClient`] struct.
6
7# Notes
8
9- Some features require Supabase Pro plan subscription
10- OAuth and SSO require configuration in Supabase dashboard
11- Rate limiting may apply to authentication operations
12- Always use HTTPS in production environments
13- Properly handle token expiration and refresh cycles
14*/
15
16use std::env;
17
18use reqwest::{
19    header::{self, HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE},
20    Client, Url,
21};
22use serde_json::{from_str, Value};
23
24use crate::{
25    error::{
26        Error::{self, AuthError},
27        SupabaseHTTPError,
28    },
29    models::{
30        AuthClient, AuthServerHealth, AuthServerSettings, EmailSignUpConfirmation,
31        EmailSignUpResult, ExchangeCodeForSessionPayload, IdTokenCredentials, InviteParams,
32        LoginAnonymouslyOptions, LoginAnonymouslyPayload, LoginEmailOtpParams,
33        LoginWithEmailAndPasswordPayload, LoginWithEmailOtpPayload, LoginWithOAuthOptions,
34        LoginWithPhoneAndPasswordPayload, LoginWithSSO, LogoutScope, OAuthResponse, OTPResponse,
35        Provider, RefreshSessionPayload, RequestMagicLinkPayload, ResendParams,
36        ResetPasswordForEmailPayload, ResetPasswordOptions, SendSMSOtpPayload, Session,
37        SignUpWithEmailAndPasswordPayload, SignUpWithPasswordOptions,
38        SignUpWithPhoneAndPasswordPayload, UpdatedUser, User, VerifyOtpParams, AUTH_V1,
39    },
40};
41
42impl AuthClient {
43    /// Create a new Auth Client
44    /// You can find your project url and keys at `https://supabase.com/dashboard/project/YOUR_PROJECT_ID/settings/api`
45    /// # Example
46    /// ```
47    /// let auth_client = AuthClient::new(project_url, api_key, jwt_secret).unwrap();
48    /// ```
49    pub fn new(
50        project_url: impl Into<String>,
51        api_key: impl Into<String>,
52        jwt_secret: impl Into<String>,
53    ) -> Self {
54        AuthClient {
55            client: Client::new(),
56            project_url: project_url.into(),
57            api_key: api_key.into(),
58            jwt_secret: jwt_secret.into(),
59        }
60    }
61
62    /// Create a new AuthClient from environment variables
63    /// Requires `SUPABASE_URL`, `SUPABASE_API_KEY`, and `SUPABASE_JWT_SECRET` environment variables
64    /// # Example
65    /// ```
66    /// let auth_client = AuthClient::new_from_env().unwrap();
67    ///
68    /// assert!(auth_client.project_url == env::var("SUPABASE_URL").unwrap())
69    /// ```
70    pub fn new_from_env() -> Result<AuthClient, Error> {
71        let project_url = env::var("SUPABASE_URL")?;
72        let api_key = env::var("SUPABASE_API_KEY")?;
73        let jwt_secret = env::var("SUPABASE_JWT_SECRET")?;
74
75        Ok(AuthClient {
76            client: Client::new(),
77            project_url,
78            api_key,
79            jwt_secret,
80        })
81    }
82
83    /// Sign in a user with an email and password
84    /// # Example
85    /// ```
86    /// let session = auth_client
87    ///     .login_with_email(demo_email, demo_password)
88    ///     .await
89    ///     .unwrap();
90    ///
91    /// assert!(session.user.email == demo_email)
92    /// ```
93    pub async fn login_with_email(&self, email: &str, password: &str) -> Result<Session, Error> {
94        let payload = LoginWithEmailAndPasswordPayload { email, password };
95
96        let mut headers = header::HeaderMap::new();
97        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
98        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
99        let body = serde_json::to_string(&payload)?;
100
101        let response = self
102            .client
103            .post(format!(
104                "{}{}/token?grant_type=password",
105                self.project_url, AUTH_V1
106            ))
107            .headers(headers)
108            .body(body)
109            .send()
110            .await?;
111
112        let res_status = response.status();
113        let res_body = response.text().await?;
114
115        if let Ok(session) = from_str(&res_body) {
116            return Ok(session);
117        }
118
119        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
120            return Err(Error::AuthError {
121                status: res_status,
122                message: error.message,
123            });
124        }
125
126        // Fallback: return raw error
127        Err(Error::AuthError {
128            status: res_status,
129            message: res_body,
130        })
131    }
132
133    /// Sign in a user with phone number and password
134    /// # Example
135    /// ```
136    /// let session = auth_client
137    ///     .login_with_phone(demo_phone, demo_password)
138    ///     .await
139    ///     .unwrap();
140    ///
141    /// assert!(session.user.phone == demo_phone)
142    /// ```
143    pub async fn login_with_phone(&self, phone: &str, password: &str) -> Result<Session, Error> {
144        let payload = LoginWithPhoneAndPasswordPayload { phone, password };
145
146        let mut headers = header::HeaderMap::new();
147        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
148        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
149
150        let body = serde_json::to_string(&payload)?;
151
152        let response = self
153            .client
154            .post(format!(
155                "{}{}/token?grant_type=password",
156                self.project_url, AUTH_V1
157            ))
158            .headers(headers)
159            .body(body)
160            .send()
161            .await?;
162
163        let res_status = response.status();
164        let res_body = response.text().await?;
165
166        if let Ok(session) = from_str(&res_body) {
167            return Ok(session);
168        }
169
170        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
171            return Err(Error::AuthError {
172                status: res_status,
173                message: error.message,
174            });
175        }
176
177        // Fallback: return raw error
178        Err(Error::AuthError {
179            status: res_status,
180            message: res_body,
181        })
182    }
183
184    /// Sign up a new user with an email and password
185    /// # Example
186    /// ```
187    /// let result = auth_client
188    ///     .sign_up_with_email_and_password(demo_email, demo_password)
189    ///     .await
190    ///     .unwrap();
191    ///
192    /// assert!(result.session.user.email == demo_email)
193    ///```
194    pub async fn sign_up_with_email_and_password(
195        &self,
196        email: &str,
197        password: &str,
198        options: Option<SignUpWithPasswordOptions>,
199    ) -> Result<EmailSignUpResult, Error> {
200        let redirect_to = options
201            .as_ref()
202            .and_then(|o| o.email_redirect_to.as_deref().map(str::to_owned));
203
204        let payload = SignUpWithEmailAndPasswordPayload {
205            email,
206            password,
207            options,
208        };
209
210        let mut headers = header::HeaderMap::new();
211        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
212        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
213
214        let body = serde_json::to_string(&payload)?;
215
216        let response = self
217            .client
218            .post(format!("{}{}/signup", self.project_url, AUTH_V1))
219            .query(&[("redirect_to", redirect_to.as_deref())])
220            .headers(headers)
221            .body(body)
222            .send()
223            .await?;
224
225        let res_status = response.status();
226        let res_body = response.text().await?;
227
228        if let Ok(session) = from_str::<Session>(&res_body) {
229            return Ok(EmailSignUpResult::SessionResult(session));
230        }
231
232        if let Ok(result) = from_str::<EmailSignUpConfirmation>(&res_body) {
233            return Ok(EmailSignUpResult::ConfirmationResult(result));
234        }
235
236        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
237            return Err(Error::AuthError {
238                status: res_status,
239                message: error.message,
240            });
241        }
242
243        // Fallback: return raw error
244        Err(Error::AuthError {
245            status: res_status,
246            message: res_body,
247        })
248    }
249
250    /// Sign up a new user with an email and password
251    /// # Example
252    /// ```
253    /// let session = auth_client
254    ///     .sign_up_with_phone_and_password(demo_phone, demo_password)
255    ///     .await
256    ///     .unwrap();
257    ///
258    /// assert!(session.user.phone == demo_phone)
259    ///```
260    pub async fn sign_up_with_phone_and_password(
261        &self,
262        phone: &str,
263        password: &str,
264        options: Option<SignUpWithPasswordOptions>,
265    ) -> Result<Session, Error> {
266        let redirect_to = options
267            .as_ref()
268            .and_then(|o| o.email_redirect_to.as_deref().map(str::to_owned));
269
270        let payload = SignUpWithPhoneAndPasswordPayload {
271            phone,
272            password,
273            options,
274        };
275
276        let mut headers = header::HeaderMap::new();
277        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
278        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
279
280        let body = serde_json::to_string(&payload)?;
281
282        let response = self
283            .client
284            .post(format!("{}{}/signup", self.project_url, AUTH_V1))
285            .query(&[("email_redirect_to", redirect_to.as_deref())])
286            .headers(headers)
287            .body(body)
288            .send()
289            .await?;
290
291        let res_status = response.status();
292        let res_body = response.text().await?;
293
294        if let Ok(session) = from_str(&res_body) {
295            return Ok(session);
296        }
297
298        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
299            return Err(Error::AuthError {
300                status: res_status,
301                message: error.message,
302            });
303        }
304
305        // Fallback: return raw error
306        Err(Error::AuthError {
307            status: res_status,
308            message: res_body,
309        })
310    }
311
312    /// Sign in a new user anonymously. This actually signs up a user, but it's
313    /// called "sign in" by Supabase in their own client, so that's why it's
314    /// named like this here. You can also pass in the same signup options
315    /// that work for the other `sign_up_*` methods, but that's not required.
316    ///
317    /// This method requires anonymous sign in to be enabled in your dashboard.
318    ///
319    /// # Example
320    /// ```
321    /// let session = auth_client
322    ///     .login_anonymously(demo_options)
323    ///     .await
324    ///     .unwrap();
325    ///
326    /// assert!(session.user.user_metadata.display_name == demo_options.data.display_name)
327    /// ```
328    pub async fn login_anonymously(
329        &self,
330        options: Option<LoginAnonymouslyOptions>,
331    ) -> Result<Session, Error> {
332        let payload = LoginAnonymouslyPayload { options };
333
334        let mut headers = header::HeaderMap::new();
335        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
336        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
337
338        let body = serde_json::to_string(&payload)?;
339
340        let response = self
341            .client
342            .post(format!("{}{}/signup", self.project_url, AUTH_V1))
343            .headers(headers)
344            .body(body)
345            .send()
346            .await?;
347
348        let res_status = response.status();
349        let res_body = response.text().await?;
350
351        if let Ok(session) = from_str(&res_body) {
352            return Ok(session);
353        }
354
355        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
356            return Err(Error::AuthError {
357                status: res_status,
358                message: error.message,
359            });
360        }
361
362        // Fallback: return raw error
363        Err(Error::AuthError {
364            status: res_status,
365            message: res_body,
366        })
367    }
368
369    /// Sends a login email containing a magic link
370    /// # Example
371    /// ```
372    /// let _response = auth_client
373    ///     .send_login_email_with_magic_link(demo_email)
374    ///    .await
375    ///    .unwrap();
376    ///```
377    pub async fn send_login_email_with_magic_link(&self, email: &str) -> Result<(), Error> {
378        let payload = RequestMagicLinkPayload { email };
379
380        let mut headers = header::HeaderMap::new();
381        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
382        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
383
384        let body = serde_json::to_string(&payload)?;
385
386        let response = self
387            .client
388            .post(format!("{}{}/magiclink", self.project_url, AUTH_V1))
389            .headers(headers)
390            .body(body)
391            .send()
392            .await?;
393
394        let res_status = response.status();
395        let res_body = response.text().await?;
396
397        if res_status.is_success() {
398            Ok(())
399        } else {
400            if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
401                return Err(AuthError {
402                    status: res_status,
403                    message: error.message,
404                });
405            }
406
407            // Fallback: return raw error
408            return Err(AuthError {
409                status: res_status,
410                message: res_body,
411            });
412        }
413    }
414
415    /// Send a Login OTP via SMS
416    ///
417    /// # Example
418    /// ```
419    /// let response = auth_client.send_sms_with_otp(demo_phone).await;
420    /// ```
421    pub async fn send_sms_with_otp(&self, phone: &str) -> Result<OTPResponse, Error> {
422        let payload = SendSMSOtpPayload { phone };
423
424        let mut headers = header::HeaderMap::new();
425        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
426        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
427
428        let body = serde_json::to_string(&payload)?;
429
430        let response = self
431            .client
432            .post(format!("{}{}/otp", self.project_url, AUTH_V1))
433            .headers(headers)
434            .body(body)
435            .send()
436            .await?;
437
438        let res_status = response.status();
439        let res_body = response.text().await?;
440
441        if res_status.is_success() {
442            let message = serde_json::from_str(&res_body)?;
443            Ok(message)
444        } else {
445            if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
446                return Err(AuthError {
447                    status: res_status,
448                    message: error.message,
449                });
450            }
451
452            // Fallback: return raw error
453            return Err(AuthError {
454                status: res_status,
455                message: res_body,
456            });
457        }
458    }
459
460    /// Send a Login OTP via email
461    ///
462    /// Returns an OTPResponse on success
463    /// # Example
464    /// ```
465    /// let send = auth_client.send_sms_with_otp(demo_phone).await.unwrap();
466    /// ```
467    pub async fn send_email_with_otp(
468        &self,
469        email: &str,
470        options: Option<LoginEmailOtpParams>,
471    ) -> Result<OTPResponse, Error> {
472        let payload = LoginWithEmailOtpPayload { email, options };
473
474        let mut headers = header::HeaderMap::new();
475        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
476        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
477
478        let body = serde_json::to_string(&payload)?;
479
480        let response = self
481            .client
482            .post(format!("{}{}/otp", self.project_url, AUTH_V1))
483            .headers(headers)
484            .body(body)
485            .send()
486            .await?;
487
488        let res_status = response.status();
489        let res_body = response.text().await?;
490
491        if res_status.is_success() {
492            let message = serde_json::from_str(&res_body)?;
493            Ok(message)
494        } else {
495            if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
496                return Err(AuthError {
497                    status: res_status,
498                    message: error.message,
499                });
500            }
501
502            // Fallback: return raw error
503            return Err(AuthError {
504                status: res_status,
505                message: res_body,
506            });
507        }
508    }
509
510    /// Sign in a user using an OAuth provider.
511    /// # Example
512    /// ```
513    /// // You can add custom parameters using a HashMap<String, String>
514    /// let mut params = HashMap::new();
515    /// params.insert("key".to_string(), "value".to_string());
516    ///
517    /// let options = LoginWithOAuthOptions {
518    ///     query_params: Some(params),
519    ///     redirect_to: Some("localhost".to_string()),
520    ///     scopes: Some("repo gist notifications".to_string()),
521    ///     skip_browser_redirect: Some(true),
522    /// };
523    ///
524    /// let response = auth_client
525    ///     .login_with_oauth(supabase_auth::models::Provider::Github, Some(options))
526    ///     .unwrap();
527    /// ```
528    pub fn login_with_oauth(
529        &self,
530        provider: Provider,
531        options: Option<LoginWithOAuthOptions>,
532    ) -> Result<OAuthResponse, Error> {
533        let query_params = options.as_ref().map_or_else(
534            || vec![("provider", provider.to_string())],
535            |o| {
536                let mut params = vec![("provider", provider.to_string())];
537
538                if let Some(ref redirect) = o.redirect_to {
539                    params.push(("redirect_to", redirect.to_string()));
540                }
541
542                if let Some(ref extra) = o.query_params {
543                    params.extend(extra.iter().map(|(k, v)| (k.as_str(), v.to_string())));
544                }
545
546                params
547            },
548        );
549
550        let url = Url::parse_with_params(
551            format!("{}{}/authorize", self.project_url, AUTH_V1).as_str(),
552            query_params,
553        )
554        .map_err(|_| Error::ParseUrlError)?;
555
556        Ok(OAuthResponse { url, provider })
557    }
558
559    /// Sign up a user using an OAuth provider.
560    /// # Example
561    /// ```
562    /// // You can add custom parameters using a HashMap<String, String>
563    /// let mut params = HashMap::new();
564    /// params.insert("key".to_string(), "value".to_string());
565    ///
566    /// let options = LoginWithOAuthOptions {
567    ///     query_params: Some(params),
568    ///     redirect_to: Some("localhost".to_string()),
569    ///     scopes: Some("repo gist notifications".to_string()),
570    ///     skip_browser_redirect: Some(true),
571    /// };
572    ///
573    /// let response = auth_client
574    ///     .sign_up_with_oauth(supabase_auth::models::Provider::Github, Some(options))
575    ///     .unwrap();
576    /// ```
577    pub fn sign_up_with_oauth(
578        &self,
579        provider: Provider,
580        options: Option<LoginWithOAuthOptions>,
581    ) -> Result<OAuthResponse, Error> {
582        self.login_with_oauth(provider, options)
583    }
584
585    /// Return the signed in User
586    /// # Example
587    /// ```
588    /// let user = auth_client
589    ///     .get_user(session.unwrap().access_token)
590    ///     .await
591    ///     .unwrap();
592    ///
593    /// assert!(user.email == demo_email)
594    /// ```
595    pub async fn get_user(&self, bearer_token: &str) -> Result<User, Error> {
596        let mut headers = header::HeaderMap::new();
597        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
598        headers.insert(
599            AUTHORIZATION,
600            HeaderValue::from_str(&format!("Bearer {}", bearer_token))?,
601        );
602
603        let response = self
604            .client
605            .get(format!("{}{}/user", self.project_url, AUTH_V1))
606            .headers(headers)
607            .send()
608            .await?;
609
610        let res_status = response.status();
611        let res_body = response.text().await?;
612
613        if let Ok(user) = from_str(&res_body) {
614            return Ok(user);
615        }
616
617        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
618            return Err(Error::AuthError {
619                status: res_status,
620                message: error.message,
621            });
622        }
623
624        // Fallback: return raw error
625        Err(Error::AuthError {
626            status: res_status,
627            message: res_body,
628        })
629    }
630
631    /// Update the user, such as changing email or password. Each field (email, password, and data) is optional
632    /// # Example
633    /// ```
634    /// let updated_user_data = UpdateUserPayload {
635    ///     email: Some("demo@demo.com".to_string()),
636    ///     password: Some("demo_password".to_string()),
637    ///     data: None, // This field can hold any valid JSON value
638    /// };
639    ///
640    /// let user = auth_client
641    ///     .update_user(updated_user_data, access_token)
642    ///     .await
643    ///     .unwrap();
644    /// ```
645    pub async fn update_user(
646        &self,
647        updated_user: UpdatedUser,
648        bearer_token: &str,
649    ) -> Result<User, Error> {
650        let mut headers = header::HeaderMap::new();
651        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
652        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
653        headers.insert(
654            AUTHORIZATION,
655            HeaderValue::from_str(&format!("Bearer {}", bearer_token))?,
656        );
657
658        let body = serde_json::to_string::<UpdatedUser>(&updated_user)?;
659
660        let response = self
661            .client
662            .put(format!("{}{}/user", self.project_url, AUTH_V1))
663            .headers(headers)
664            .body(body)
665            .send()
666            .await?;
667
668        let res_status = response.status();
669        let res_body = response.text().await?;
670
671        if let Ok(user) = from_str(&res_body) {
672            return Ok(user);
673        }
674
675        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
676            return Err(Error::AuthError {
677                status: res_status,
678                message: error.message,
679            });
680        }
681
682        // Fallback: return raw error
683        Err(Error::AuthError {
684            status: res_status,
685            message: res_body,
686        })
687    }
688
689    /// Allows signing in with an OIDC ID token. The authentication provider used should be enabled and configured.
690    /// # Example
691    /// ```
692    /// let credentials = IdTokenCredentials {
693    ///     provider: Provider::Github,
694    ///     token: "<id-token-from-auth-provider>",
695    /// };
696    ///
697    /// let session = auth_client
698    ///     .login_with_id_token(credentials)
699    ///     .await
700    ///     .unwrap();
701    /// ```
702    pub async fn login_with_id_token(
703        &self,
704        credentials: IdTokenCredentials,
705    ) -> Result<Session, Error> {
706        let mut headers = HeaderMap::new();
707        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
708        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
709
710        let body = serde_json::to_string(&credentials)?;
711
712        let response = self
713            .client
714            .post(format!(
715                "{}{}/token?grant_type=id_token",
716                self.project_url, AUTH_V1
717            ))
718            .headers(headers)
719            .body(body)
720            .send()
721            .await?;
722
723        let res_status = response.status();
724        let res_body = response.text().await?;
725
726        if let Ok(session) = from_str(&res_body) {
727            return Ok(session);
728        }
729
730        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
731            return Err(Error::AuthError {
732                status: res_status,
733                message: error.message,
734            });
735        }
736
737        // Fallback: return raw error
738        Err(Error::AuthError {
739            status: res_status,
740            message: res_body,
741        })
742    }
743
744    /// Sends an invite link to an email address.
745    /// Requires admin permissions to issue invites
746    ///
747    /// The data field corresponds to the `raw_user_meta_data` User field
748    /// # Example
749    /// ```
750    /// let demo_email = env::var("DEMO_INVITE").unwrap();
751    ///
752    /// let user = auth_client
753    ///     .invite_user_by_email(&demo_email, None, auth_client.api_key())
754    ///     .await
755    ///     .unwrap();
756    ///```
757    pub async fn invite_user_by_email(
758        &self,
759        email: &str,
760        data: Option<Value>,
761        bearer_token: &str,
762    ) -> Result<User, Error> {
763        let mut headers = HeaderMap::new();
764        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
765        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
766        headers.insert(
767            AUTHORIZATION,
768            HeaderValue::from_str(&format!("Bearer {}", bearer_token))?,
769        );
770
771        let invite_payload = InviteParams {
772            email: email.into(),
773            data,
774        };
775
776        let body = serde_json::to_string(&invite_payload)?;
777
778        let response = self
779            .client
780            .post(format!("{}{}/invite", self.project_url, AUTH_V1))
781            .headers(headers)
782            .body(body)
783            .send()
784            .await?;
785
786        let res_status = response.status();
787        let res_body = response.text().await?;
788
789        if let Ok(user) = from_str(&res_body) {
790            return Ok(user);
791        }
792
793        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
794            return Err(Error::AuthError {
795                status: res_status,
796                message: error.message,
797            });
798        }
799
800        // Fallback: return raw error
801        Err(Error::AuthError {
802            status: res_status,
803            message: res_body,
804        })
805    }
806
807    /// Verify the OTP sent to the user
808    /// # Example
809    /// ```
810    /// let params = VerifyEmailOtpParams {
811    ///     token: "abc123",
812    ///     otp_type: OtpType::EmailChange,
813    ///     options: None,
814    /// };
815    ///
816    /// let session = auth_client
817    ///     .verify_otp(params)
818    ///     .await
819    ///     .unwrap();
820    ///```
821    pub async fn verify_otp(&self, params: VerifyOtpParams) -> Result<Session, Error> {
822        let mut headers = HeaderMap::new();
823        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
824        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
825
826        let body = serde_json::to_string(&params)?;
827
828        let response = self
829            .client
830            .post(&format!("{}{}/verify", self.project_url, AUTH_V1))
831            .headers(headers)
832            .body(body)
833            .send()
834            .await?;
835
836        let res_status = response.status();
837        let res_body = response.text().await?;
838
839        if let Ok(session) = from_str(&res_body) {
840            return Ok(session);
841        }
842
843        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
844            return Err(Error::AuthError {
845                status: res_status,
846                message: error.message,
847            });
848        }
849
850        // Fallback: return raw error
851        Err(Error::AuthError {
852            status: res_status,
853            message: res_body,
854        })
855    }
856
857    /// Check the Health Status of the Auth Server
858    /// # Example
859    /// ```
860    /// let health = auth_client
861    ///     .get_health()
862    ///     .await
863    ///     .unwrap();
864    /// ```
865    pub async fn get_health(&self) -> Result<AuthServerHealth, Error> {
866        let mut headers = HeaderMap::new();
867        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
868
869        let response = self
870            .client
871            .get(&format!("{}{}/health", self.project_url, AUTH_V1))
872            .headers(headers)
873            .send()
874            .await?;
875
876        let res_status = response.status();
877        let res_body = response.text().await?;
878
879        if let Ok(health) = from_str::<AuthServerHealth>(&res_body) {
880            return Ok(health);
881        }
882
883        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
884            return Err(Error::AuthError {
885                status: res_status,
886                message: error.message,
887            });
888        }
889
890        // Fallback: return raw error
891        Err(Error::AuthError {
892            status: res_status,
893            message: res_body,
894        })
895    }
896
897    /// Retrieve the public settings of the server
898    /// # Example
899    /// ```
900    /// let settings = auth_client
901    ///     .get_settings()
902    ///     .await
903    ///     .unwrap();
904    /// ```
905    pub async fn get_settings(&self) -> Result<AuthServerSettings, Error> {
906        let mut headers = HeaderMap::new();
907        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
908
909        let response = self
910            .client
911            .get(&format!("{}{}/settings", self.project_url, AUTH_V1))
912            .headers(headers)
913            .send()
914            .await?;
915
916        let res_status = response.status();
917        let res_body = response.text().await?;
918
919        if let Ok(settings) = from_str(&res_body) {
920            return Ok(settings);
921        }
922
923        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
924            return Err(Error::AuthError {
925                status: res_status,
926                message: error.message,
927            });
928        }
929
930        // Fallback: return raw error
931        Err(Error::AuthError {
932            status: res_status,
933            message: res_body,
934        })
935    }
936
937    /// Exchange refresh token for a new session
938    /// # Example
939    /// ```
940    /// // When a user signs in they get a session
941    /// let original_session = auth_client
942    ///     .login_with_email_and_password(demo_email.as_ref(), demo_password)
943    ///     .await
944    ///     .unwrap();
945    ///
946    /// // Exchange the refresh token from the original session to create a new session
947    /// let new_session = auth_client
948    ///     .refresh_session(original_session.refresh_token)
949    ///     .await
950    ///     .unwrap();
951    /// ```
952    pub async fn exchange_token_for_session(&self, refresh_token: &str) -> Result<Session, Error> {
953        let mut headers = HeaderMap::new();
954        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
955        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
956
957        let body = serde_json::to_string(&RefreshSessionPayload { refresh_token })?;
958
959        let response = self
960            .client
961            .post(&format!(
962                "{}{}/token?grant_type=refresh_token",
963                self.project_url, AUTH_V1
964            ))
965            .headers(headers)
966            .body(body)
967            .send()
968            .await?;
969
970        let res_status = response.status();
971        let res_body = response.text().await?;
972
973        if let Ok(session) = from_str(&res_body) {
974            return Ok(session);
975        }
976
977        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
978            return Err(Error::AuthError {
979                status: res_status,
980                message: error.message,
981            });
982        }
983
984        // Fallback: return raw error
985        Err(Error::AuthError {
986            status: res_status,
987            message: res_body,
988        })
989    }
990
991    pub async fn refresh_session(&self, refresh_token: &str) -> Result<Session, Error> {
992        self.exchange_token_for_session(refresh_token).await
993    }
994
995    /// Exchange code for a new session
996    /// # Example
997    /// ```
998    /// // When a user signs in they get a session
999    /// let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
1000    ///
1001    /// let options = LoginWithOAuthOptions {
1002    ///     query_params: Some(HashMap::from([
1003    ///         (
1004    ///             "redirect_to".to_owned(),
1005    ///             "http://localhost:3000/auth/callback".to_owned(),
1006    ///         ),
1007    ///         ("response_type".to_owned(), "code".to_owned()),
1008    ///         ("skip_browser_redirect".to_owned(), "true".to_owned()),
1009    ///         (
1010    ///             "code_challenge".to_owned(),
1011    ///             pkce_challenge.as_str().to_owned(),
1012    ///         ),
1013    ///         ("code_challenge_method".to_owned(), "S256".to_owned()),
1014    ///     ])),
1015    ///     ..Default::default()
1016    /// };
1017    ///
1018    /// let oauth_res = auth_client
1019    ///     .login_with_oauth(Provider::Github, Some(options))?;
1020    ///
1021    /// // Exchange the code to create a new session
1022    /// let new_session = auth_client
1023    ///     .exchange_code_for_session(auth_code, pkce_verifier)
1024    ///     .await
1025    ///     .unwrap();
1026    /// ```
1027    pub async fn exchange_code_for_session(
1028        &self,
1029        auth_code: &str,
1030        code_verifier: &str,
1031    ) -> Result<Session, Error> {
1032        let mut headers = HeaderMap::new();
1033        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
1034        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
1035
1036        let body = serde_json::to_string(&ExchangeCodeForSessionPayload {
1037            auth_code,
1038            code_verifier,
1039        })?;
1040
1041        let response = self
1042            .client
1043            .post(&format!(
1044                "{}{}/token?grant_type=pkce",
1045                self.project_url, AUTH_V1
1046            ))
1047            .headers(headers)
1048            .body(body)
1049            .send()
1050            .await?;
1051
1052        let res_status = response.status();
1053        let res_body = response.text().await?;
1054
1055        if let Ok(session) = from_str(&res_body) {
1056            return Ok(session);
1057        }
1058
1059        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
1060            return Err(Error::AuthError {
1061                status: res_status,
1062                message: error.message,
1063            });
1064        }
1065
1066        // Fallback: return raw error
1067        Err(Error::AuthError {
1068            status: res_status,
1069            message: res_body,
1070        })
1071    }
1072
1073    /// Send a password recovery email. Invalid Email addresses will return Error Code 400.
1074    /// Valid email addresses that are not registered as users will not return an error.
1075    /// # Example
1076    /// ```
1077    /// let response = auth_client.reset_password_for_email(demo_email, None).await.unwrap();
1078    /// ```
1079    pub async fn reset_password_for_email(
1080        &self,
1081        email: &str,
1082        options: Option<ResetPasswordOptions>,
1083    ) -> Result<(), Error> {
1084        let redirect_to = options
1085            .as_ref()
1086            .and_then(|o| o.email_redirect_to.as_deref().map(str::to_owned));
1087
1088        let payload = ResetPasswordForEmailPayload {
1089            email: String::from(email),
1090            options,
1091        };
1092
1093        let mut headers = HeaderMap::new();
1094        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
1095        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
1096
1097        let body = serde_json::to_string(&payload)?;
1098
1099        let response = self
1100            .client
1101            .post(&format!("{}{}/recover", self.project_url, AUTH_V1))
1102            .query(&[("redirect_to", redirect_to.as_deref())])
1103            .headers(headers)
1104            .body(body)
1105            .send()
1106            .await?;
1107
1108        let res_status = response.status();
1109        let res_body = response.text().await?;
1110
1111        if res_status.is_success() {
1112            return Ok(());
1113        }
1114
1115        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
1116            return Err(Error::AuthError {
1117                status: res_status,
1118                message: error.message,
1119            });
1120        }
1121
1122        Err(Error::AuthError {
1123            status: res_status,
1124            message: res_body,
1125        })
1126    }
1127
1128    /// Resends emails for existing signup confirmation, email change, SMS OTP, or phone change OTP.
1129    /// # Example
1130    /// ```
1131    /// // Resend can also take MobileResendParams
1132    /// let credentials = DesktopResendParams {
1133    ///     otp_type: supabase_auth::models::EmailOtpType::Email,
1134    ///     email: demo_email.to_owned(),
1135    ///     options: None,
1136    /// };
1137    ///
1138    /// let resend = auth_client.resend(ResendParams::Desktop(credentials)).await;
1139    /// ```
1140    pub async fn resend(&self, credentials: ResendParams) -> Result<(), Error> {
1141        let mut headers = HeaderMap::new();
1142        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
1143        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
1144
1145        let body = serde_json::to_string(&credentials)?;
1146
1147        let response = self
1148            .client
1149            .post(&format!("{}{}/resend", self.project_url, AUTH_V1))
1150            .headers(headers)
1151            .body(body)
1152            .send()
1153            .await?;
1154
1155        let res_status = response.status();
1156        let res_body = response.text().await?;
1157
1158        if res_status.is_success() {
1159            return Ok(());
1160        }
1161
1162        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
1163            return Err(Error::AuthError {
1164                status: res_status,
1165                message: error.message,
1166            });
1167        }
1168
1169        Err(Error::AuthError {
1170            status: res_status,
1171            message: res_body,
1172        })
1173    }
1174
1175    /// Logs out a user with a given scope
1176    /// # Example
1177    /// ```
1178    /// auth_client.logout(Some(LogoutScope::Global), session.access_token).await.unwrap();
1179    /// ```
1180    pub async fn logout(
1181        &self,
1182        scope: Option<LogoutScope>,
1183        bearer_token: &str,
1184    ) -> Result<(), Error> {
1185        let mut headers = HeaderMap::new();
1186        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
1187        headers.insert(CONTENT_TYPE, HeaderValue::from_str("application/json")?);
1188        headers.insert(
1189            AUTHORIZATION,
1190            HeaderValue::from_str(&format!("Bearer {}", bearer_token))?,
1191        );
1192
1193        let body = serde_json::to_string(&scope)?;
1194
1195        let response = self
1196            .client
1197            .post(&format!("{}{}/logout", self.project_url, AUTH_V1))
1198            .headers(headers)
1199            .body(body)
1200            .send()
1201            .await?;
1202
1203        let res_status = response.status();
1204        let res_body = response.text().await?;
1205
1206        if res_status.is_success() {
1207            return Ok(());
1208        }
1209
1210        if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
1211            return Err(Error::AuthError {
1212                status: res_status,
1213                message: error.message,
1214            });
1215        }
1216
1217        Err(Error::AuthError {
1218            status: res_status,
1219            message: res_body,
1220        })
1221    }
1222
1223    /// Initiates an SSO Login Flow
1224    /// Returns the URL where the user must authenticate with the SSO Provider
1225    ///
1226    /// WARNING: Requires an SSO Provider and Supabase Pro plan
1227    ///
1228    /// # Example
1229    /// ```
1230    /// let url = auth_client.sso(params).await.unwrap();
1231    ///
1232    /// println!("{}", url.to_string());
1233    /// ```
1234    pub async fn sso(&self, params: LoginWithSSO) -> Result<Url, Error> {
1235        let mut headers = HeaderMap::new();
1236        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
1237        headers.insert("apikey", HeaderValue::from_str(&self.api_key)?);
1238
1239        let body = serde_json::to_string::<crate::models::LoginWithSSO>(&params)?;
1240
1241        let response = self
1242            .client
1243            .post(&format!("{}{}/sso", self.project_url, AUTH_V1))
1244            .headers(headers)
1245            .body(body)
1246            .send()
1247            .await?;
1248
1249        let res_status = response.status();
1250        let url = response.url().clone();
1251        let res_body = response.text().await?;
1252
1253        if res_status.is_server_error() || res_status.is_client_error() {
1254            if let Ok(error) = from_str::<SupabaseHTTPError>(&res_body) {
1255                return Err(AuthError {
1256                    status: res_status,
1257                    message: error.message,
1258                });
1259            }
1260
1261            // Fallback: return raw error
1262            return Err(AuthError {
1263                status: res_status,
1264                message: res_body,
1265            });
1266        }
1267
1268        Ok(url)
1269    }
1270
1271    /// Get the project URL from an AuthClient
1272    pub fn project_url(&self) -> &str {
1273        &self.project_url
1274    }
1275
1276    /// Get the API Key from an AuthClient
1277    pub fn api_key(&self) -> &str {
1278        &self.api_key
1279    }
1280
1281    /// Get the JWT Secret from an AuthClient
1282    pub fn jwt_secret(&self) -> &str {
1283        &self.jwt_secret
1284    }
1285}