supabase_auth_redux/
signin_with_password.rs

1use serde::{Deserialize, Serialize};
2use tracing::{debug, error, info, instrument, trace_span, Instrument};
3
4use crate::error::AuthError;
5use crate::models::token::TokenResponse;
6use crate::util::handle_response_code;
7use crate::AuthClient;
8use crate::IdType;
9
10#[derive(Debug, Deserialize, Serialize)]
11struct TokenPasswordGrant {
12    email: Option<String>,
13    phone: Option<String>,
14    password: String,
15}
16
17impl AuthClient {
18    /// Signs in a user with their email/phone and password
19    ///
20    /// This method authenticates a user using their credentials and returns authentication tokens
21    /// that can be used for subsequent API requests.
22    ///
23    /// # Arguments
24    ///
25    /// * `id` - The user's identifier (email or phone number)
26    /// * `password` - The user's password
27    ///
28    /// # Returns
29    ///
30    /// Returns a `TokenResponse` containing access and refresh tokens on successful authentication.
31    ///
32    /// # Errors
33    ///
34    /// Returns `AuthError::InvalidParameters` if email/phone or password is empty.
35    /// Returns `AuthError::NotAuthorized` if credentials are invalid.
36    /// Returns `AuthError::Http` if the API request fails.
37    ///
38    /// # Example
39    ///
40    /// ```rust,no_run
41    /// # use supabase_auth_redux::{AuthClient, IdType};
42    /// # async fn example() -> Result<(), supabase_auth_redux::AuthError> {
43    /// let client = AuthClient::new("https://your-project.supabase.co", "your-anon-key")?;
44    ///
45    /// let tokens = client
46    ///     .signin_with_password(
47    ///         IdType::Email("user@example.com".to_string()),
48    ///         "secure_password".to_string(),
49    ///     )
50    ///     .await?;
51    ///
52    /// println!("Access token: {}", tokens.access_token);
53    /// # Ok(())
54    /// # }
55    /// ```
56    #[instrument(skip_all)]
57    pub async fn signin_with_password(
58        &self,
59        id: IdType,
60        password: String,
61    ) -> Result<TokenResponse, AuthError> {
62        if password.is_empty() {
63            error!("empty password");
64            return Err(AuthError::InvalidParameters);
65        }
66
67        let token_password_grant = match id {
68            IdType::Email(email) => {
69                if email.is_empty() {
70                    error!("empty email");
71                    return Err(AuthError::InvalidParameters);
72                }
73
74                info!(email = email);
75                TokenPasswordGrant {
76                    email: Some(email),
77                    phone: None,
78                    password,
79                }
80            }
81            IdType::PhoneNumber(phone_number) => {
82                if phone_number.is_empty() {
83                    error!("empty phone_number");
84                    return Err(AuthError::InvalidParameters);
85                }
86
87                info!(phone_number = phone_number);
88                TokenPasswordGrant {
89                    email: None,
90                    phone: Some(phone_number),
91                    password,
92                }
93            }
94        };
95
96        let resp = match self
97            .http_client
98            .post(format!(
99                "{}/auth/v1/{}",
100                self.supabase_api_url, "token?grant_type=password"
101            ))
102            .bearer_auth(&self.supabase_anon_key)
103            .header("apiKey", &self.supabase_anon_key)
104            .json(&token_password_grant)
105            .send()
106            .instrument(trace_span!("gotrue token password"))
107            .await
108        {
109            Ok(resp) => resp,
110            Err(e) => {
111                error!("{}", e);
112                return Err(AuthError::Http);
113            }
114        };
115        let resp_code_result = handle_response_code(resp.status()).await;
116        let resp_text = match resp.text().await {
117            Ok(resp_text) => resp_text,
118            Err(e) => {
119                log::error!("{}", e);
120                return Err(AuthError::Http);
121            }
122        };
123        debug!("resp_text: {}", resp_text);
124        resp_code_result?;
125
126        let token_response = match serde_json::from_str::<TokenResponse>(&resp_text) {
127            Ok(token_response) => token_response,
128            Err(e) => {
129                error!("{}", e);
130                return Err(AuthError::Internal);
131            }
132        };
133        info!(
134            tokens_are_nonempty =
135                !token_response.access_token.is_empty() && !token_response.refresh_token.is_empty()
136        );
137        debug!(
138            token = token_response.access_token,
139            refresh_token = token_response.refresh_token
140        );
141
142        Ok(token_response)
143    }
144}