supabase_auth_redux/
signup.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use tracing::{debug, info, trace_span, Instrument};
5
6use crate::error::AuthError;
7use crate::models::user::UserSchema;
8use crate::util::handle_response_code;
9use crate::{AuthClient, IdType};
10
11#[derive(Debug, Serialize, Deserialize)]
12struct SignupRequest {
13    pub email: Option<String>,
14    pub phone_number: Option<String>,
15    pub password: String,
16    pub data: Option<HashMap<String, String>>,
17}
18
19#[derive(Debug, Serialize, Deserialize)]
20struct SignupResponse {
21    pub access_token: String,
22    pub token_type: String,
23    pub expires_in: i64,
24    pub expires_at: i64,
25    pub refresh_token: String,
26    pub user: UserSchema,
27}
28
29impl AuthClient {
30    /// Creates a new user account
31    ///
32    /// This method registers a new user with the provided credentials and optional metadata.
33    /// Upon successful registration, the user is automatically signed in and authentication
34    /// tokens are returned.
35    ///
36    /// # Arguments
37    ///
38    /// * `signup_id_type` - The user's identifier (email or phone number)
39    /// * `password` - The desired password for the account
40    /// * `metadata` - Optional user metadata to store with the account
41    ///
42    /// # Returns
43    ///
44    /// Returns a tuple containing:
45    /// - The newly created `UserSchema` with user information
46    /// - An access token string for immediate authentication
47    ///
48    /// # Errors
49    ///
50    /// Returns `AuthError::InvalidParameters` if required fields are missing.
51    /// Returns `AuthError::Http` if the API request fails or user already exists.
52    ///
53    /// # Example
54    ///
55    /// ```rust,no_run
56    /// # use supabase_auth_redux::{AuthClient, IdType};
57    /// # use std::collections::HashMap;
58    /// # async fn example() -> Result<(), supabase_auth_redux::AuthError> {
59    /// let client = AuthClient::new("https://your-project.supabase.co", "your-anon-key")?;
60    ///
61    /// let mut metadata = HashMap::new();
62    /// metadata.insert("first_name".to_string(), "John".to_string());
63    /// metadata.insert("last_name".to_string(), "Doe".to_string());
64    ///
65    /// let (user, access_token) = client
66    ///     .signup(
67    ///         IdType::Email("newuser@example.com".to_string()),
68    ///         "secure_password".to_string(),
69    ///         Some(metadata),
70    ///     )
71    ///     .await?;
72    ///
73    /// println!("User created with ID: {}", user.id);
74    /// # Ok(())
75    /// # }
76    /// ```
77    pub async fn signup(
78        &self,
79        signup_id_type: IdType,
80        password: String,
81        _metadata: Option<HashMap<String, String>>,
82    ) -> Result<(UserSchema, String), AuthError> {
83        let body = match signup_id_type {
84            IdType::Email(email) => SignupRequest {
85                email: Some(email),
86                phone_number: None,
87                password,
88                data: _metadata,
89            },
90            IdType::PhoneNumber(phone_number) => SignupRequest {
91                email: None,
92                phone_number: Some(phone_number),
93                password,
94                data: _metadata,
95            },
96        };
97
98        let resp = match self
99            .http_client
100            .post(format!("{}/auth/v1/{}", self.supabase_api_url, "signup"))
101            .header("apiKey", &self.supabase_anon_key)
102            .bearer_auth(&self.supabase_anon_key)
103            .json(&body)
104            .send()
105            .instrument(trace_span!("gotrue create user"))
106            .await
107        {
108            Ok(resp) => resp,
109            Err(e) => {
110                debug!("{}", e);
111                return Err(AuthError::Http);
112            }
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                debug!("{}", e);
120                return Err(AuthError::Http);
121            }
122        };
123        debug!("resp_text: {}", resp_text);
124        resp_code_result?;
125
126        let created_user_resp = match serde_json::from_str::<SignupResponse>(&resp_text) {
127            Ok(token_response) => token_response,
128            Err(e) => {
129                debug!("{}", e);
130                return Err(AuthError::Internal);
131            }
132        };
133
134        let created_user = created_user_resp.user;
135        info!(user_id = created_user.id.to_string(), "created user");
136
137        Ok((created_user, created_user_resp.access_token))
138    }
139}