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}