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