Skip to main content

mtjp9_rs_auth0_client/users/
create_user.rs

1//! Create User API helper
2//!
3//! This module wraps the **Auth0 Management API v2 – Create User** endpoint
4//! (<https://auth0.com/docs/api/management/v2/users/post-users>).
5//!
6//! # Example
7//! ```ignore
8//! use your_crate::users::{CreateUserRequest, create_user};
9//! use your_crate::token::BearerToken;
10//! use your_crate::domain::Domain;
11//! use std::env;
12//!
13// ! #[tokio::main]
14// ! async fn main() -> anyhow::Result<()> {
15// !     let token = BearerToken::new(env::var("MGMT_API_TOKEN")?)?;
16// !     let domain = Domain::new(env::var("AUTH0_DOMAIN")?)?;
17// !
18// !     let req = CreateUserRequest::builder()
19// !         .email("user@example.com")
20// !         .connection("Username-Password-Authentication")
21// !         .password("SecureP@ssword123!")
22// !         .given_name("John")
23// !         .family_name("Doe")
24// !         .verify_email(true)
25// !         .build()?;
26// !
27// !     let user = create_user(&domain, &token, req).await?;
28// !     println!("Created user: {} (id = {})", user.email, user.user_id);
29// !     Ok(())
30// ! }
31//! ```
32use crate::{
33    domain::Domain,
34    error::{Auth0Error, Result},
35    token::BearerToken,
36};
37use reqwest::Client;
38use serde::{Deserialize, Serialize};
39use serde_json::Value;
40
41#[derive(Debug, Default, Serialize, Deserialize)]
42pub struct CreateUserRequest {
43    /// The user's email address.
44    pub email: String,
45
46    /// The connection to create the user in (e.g. "Username-Password-Authentication").
47    pub connection: String,
48
49    /// The user's password, if applicable.
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub password: Option<String>,
52
53    /// The user's given name.
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub given_name: Option<String>,
56
57    /// The user's family name.
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub family_name: Option<String>,
60
61    /// The user's full name.
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub name: Option<String>,
64
65    /// The user's nickname.
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub nickname: Option<String>,
68
69    /// URL pointing to the user's picture.
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub picture: Option<String>,
72
73    /// Whether the user's id is verified.
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub user_id: Option<String>,
76
77    /// Whether the user's email is verified.
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub email_verified: Option<bool>,
80
81    /// The user's phone number (E.164 format).
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub phone_number: Option<String>,
84
85    /// Whether the user's phone number is verified.
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub phone_verified: Option<bool>,
88
89    /// Additional metadata for the user.
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub user_metadata: Option<Value>,
92
93    /// App-specific metadata.
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub app_metadata: Option<Value>,
96
97    /// Whether the user is blocked.
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub blocked: Option<bool>,
100
101    /// Whether to send a verification email to the user.
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub verify_email: Option<bool>,
104}
105
106impl CreateUserRequest {
107    pub fn builder() -> CreateUserRequestBuilder {
108        CreateUserRequestBuilder::default()
109    }
110}
111
112#[derive(Default)]
113pub struct CreateUserRequestBuilder {
114    email: Option<String>,
115    connection: Option<String>,
116    password: Option<String>,
117    given_name: Option<String>,
118    family_name: Option<String>,
119    name: Option<String>,
120    nickname: Option<String>,
121    picture: Option<String>,
122    user_id: Option<String>,
123    email_verified: Option<bool>,
124    phone_number: Option<String>,
125    phone_verified: Option<bool>,
126    user_metadata: Option<Value>,
127    app_metadata: Option<Value>,
128    blocked: Option<bool>,
129    verify_email: Option<bool>,
130}
131
132impl CreateUserRequestBuilder {
133    pub fn email(mut self, email: impl Into<String>) -> Self {
134        self.email = Some(email.into());
135        self
136    }
137
138    pub fn connection(mut self, connection: impl Into<String>) -> Self {
139        self.connection = Some(connection.into());
140        self
141    }
142
143    pub fn password(mut self, password: impl Into<String>) -> Self {
144        self.password = Some(password.into());
145        self
146    }
147
148    pub fn given_name(mut self, given_name: impl Into<String>) -> Self {
149        self.given_name = Some(given_name.into());
150        self
151    }
152
153    pub fn family_name(mut self, family_name: impl Into<String>) -> Self {
154        self.family_name = Some(family_name.into());
155        self
156    }
157
158    pub fn name(mut self, name: impl Into<String>) -> Self {
159        self.name = Some(name.into());
160        self
161    }
162
163    pub fn nickname(mut self, nickname: impl Into<String>) -> Self {
164        self.nickname = Some(nickname.into());
165        self
166    }
167
168    pub fn picture(mut self, picture: impl Into<String>) -> Self {
169        self.picture = Some(picture.into());
170        self
171    }
172
173    pub fn user_id(mut self, user_id: impl Into<String>) -> Self {
174        self.user_id = Some(user_id.into());
175        self
176    }
177
178    pub fn email_verified(mut self, verified: bool) -> Self {
179        self.email_verified = Some(verified);
180        self
181    }
182
183    pub fn phone_number(mut self, phone_number: impl Into<String>) -> Self {
184        self.phone_number = Some(phone_number.into());
185        self
186    }
187
188    pub fn phone_verified(mut self, verified: bool) -> Self {
189        self.phone_verified = Some(verified);
190        self
191    }
192
193    pub fn user_metadata(mut self, metadata: Value) -> Self {
194        self.user_metadata = Some(metadata);
195        self
196    }
197
198    pub fn app_metadata(mut self, metadata: Value) -> Self {
199        self.app_metadata = Some(metadata);
200        self
201    }
202
203    pub fn blocked(mut self, blocked: bool) -> Self {
204        self.blocked = Some(blocked);
205        self
206    }
207
208    pub fn verify_email(mut self, verify_email: bool) -> Self {
209        self.verify_email = Some(verify_email);
210        self
211    }
212
213    pub fn build(self) -> Result<CreateUserRequest> {
214        let email = self
215            .email
216            .ok_or_else(|| Auth0Error::InvalidRequest("Email is required".to_string()))?;
217
218        let connection = self
219            .connection
220            .ok_or_else(|| Auth0Error::InvalidRequest("Connection is required".to_string()))?;
221
222        if !email.contains('@') {
223            return Err(Auth0Error::InvalidRequest(
224                "Invalid email format".to_string(),
225            ));
226        }
227
228        Ok(CreateUserRequest {
229            email,
230            connection,
231            password: self.password,
232            given_name: self.given_name,
233            family_name: self.family_name,
234            name: self.name,
235            nickname: self.nickname,
236            picture: self.picture,
237            user_id: self.user_id,
238            email_verified: self.email_verified,
239            phone_number: self.phone_number,
240            phone_verified: self.phone_verified,
241            user_metadata: self.user_metadata,
242            app_metadata: self.app_metadata,
243            blocked: self.blocked,
244            verify_email: self.verify_email,
245        })
246    }
247}
248
249#[derive(Debug, Deserialize)]
250pub struct CreateUserResponse {
251    /// The user's unique identifier.
252    pub user_id: String,
253
254    /// The user's email address.
255    pub email: String,
256
257    /// Whether the user's email is verified.
258    pub email_verified: bool,
259
260    /// The user's given name.
261    pub given_name: Option<String>,
262
263    /// The user's family name.
264    pub family_name: Option<String>,
265
266    /// The user's full name.
267    pub name: Option<String>,
268
269    /// The user's nickname.
270    pub nickname: Option<String>,
271
272    /// URL pointing to the user's picture.
273    pub picture: Option<String>,
274
275    /// The user's phone number.
276    pub phone_number: Option<String>,
277
278    /// Whether the user's phone number is verified.
279    pub phone_verified: Option<bool>,
280
281    /// Additional metadata for the user.
282    pub user_metadata: Option<Value>,
283
284    /// App-specific metadata.
285    pub app_metadata: Option<Value>,
286
287    /// Whether the user is blocked.
288    pub blocked: Option<bool>,
289
290    /// When the user was created.
291    pub created_at: String,
292
293    /// When the user was last updated.
294    pub updated_at: String,
295
296    /// List of identity providers.
297    pub identities: Vec<Identity>,
298}
299
300#[derive(Debug, Deserialize)]
301pub struct Identity {
302    /// The connection name.
303    pub connection: String,
304
305    /// The user ID for this identity.
306    pub user_id: String,
307
308    /// The identity provider.
309    pub provider: String,
310
311    /// Whether this is a social identity.
312    pub is_social: Option<bool>,
313}
314
315/// Call the Auth0 Management API to create a new user.
316///
317/// * `domain` – The Auth0 domain (e.g. `my-tenant.eu.auth0.com`).
318/// * `token` – Bearer token with `create:users` scope.
319/// * `request` – Body describing the user.
320pub async fn create_user(
321    domain: &Domain,
322    token: &BearerToken,
323    request: CreateUserRequest,
324) -> Result<CreateUserResponse> {
325    let url = domain.to_url("/api/v2/users");
326
327    let resp = Client::new()
328        .post(url)
329        .bearer_auth(token.as_str())
330        .header("Content-Type", "application/json")
331        .json(&request)
332        .send()
333        .await?;
334
335    if resp.status().is_success() {
336        let user = resp.json::<CreateUserResponse>().await?;
337        Ok(user)
338    } else {
339        Err(Auth0Error::from_response(resp).await)
340    }
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346    // use crate::random_password;
347    // use crate::domain::Domain;
348    // use crate::token::BearerToken;
349    // use mockito::{Server, ServerGuard};
350    use serde_json::json;
351
352    // async fn setup_mock_server() -> ServerGuard {
353    //     Server::new_async().await
354    // }
355
356    // fn setup_domain(server: &ServerGuard) -> Domain {
357    //     let url = server.url();
358    //     let domain = url.replace("http://", "").replace("https://", "");
359    //     Domain::new(domain).unwrap()
360    // }
361
362    // fn setup_token() -> BearerToken {
363    //     BearerToken::new("test_token_123").unwrap()
364    // }
365
366    #[test]
367    fn test_create_user_request_builder_valid() {
368        let req = CreateUserRequest::builder()
369            .email("test@example.com")
370            .connection("Username-Password-Authentication")
371            .password("SecurePassword123!")
372            .given_name("John")
373            .family_name("Doe")
374            .name("John Doe")
375            .nickname("johndoe")
376            .picture("https://example.com/avatar.jpg")
377            .email_verified(true)
378            .phone_number("+1234567890")
379            .phone_verified(false)
380            .user_metadata(json!({"favorite_color": "blue"}))
381            .app_metadata(json!({"roles": ["user"]}))
382            .blocked(false)
383            .verify_email(true)
384            .build();
385
386        assert!(req.is_ok());
387        let req = req.unwrap();
388        assert_eq!(req.email, "test@example.com");
389        assert_eq!(req.connection, "Username-Password-Authentication");
390        assert_eq!(req.password, Some("SecurePassword123!".to_string()));
391        assert_eq!(req.given_name, Some("John".to_string()));
392        assert_eq!(req.family_name, Some("Doe".to_string()));
393        assert_eq!(req.name, Some("John Doe".to_string()));
394        assert_eq!(req.nickname, Some("johndoe".to_string()));
395        assert_eq!(
396            req.picture,
397            Some("https://example.com/avatar.jpg".to_string())
398        );
399        assert_eq!(req.email_verified, Some(true));
400        assert_eq!(req.phone_number, Some("+1234567890".to_string()));
401        assert_eq!(req.phone_verified, Some(false));
402        assert_eq!(req.user_metadata, Some(json!({"favorite_color": "blue"})));
403        assert_eq!(req.app_metadata, Some(json!({"roles": ["user"]})));
404        assert_eq!(req.blocked, Some(false));
405        assert_eq!(req.verify_email, Some(true));
406    }
407
408    #[test]
409    fn test_create_user_request_builder_minimal() {
410        let req = CreateUserRequest::builder()
411            .email("test@example.com")
412            .connection("Username-Password-Authentication")
413            .build();
414
415        assert!(req.is_ok());
416        let req = req.unwrap();
417        assert_eq!(req.email, "test@example.com");
418        assert_eq!(req.connection, "Username-Password-Authentication");
419        assert_eq!(req.password, None);
420        assert_eq!(req.given_name, None);
421        assert_eq!(req.family_name, None);
422    }
423
424    #[test]
425    fn test_create_user_request_builder_missing_email() {
426        let req = CreateUserRequest::builder()
427            .connection("Username-Password-Authentication")
428            .build();
429
430        assert!(req.is_err());
431        match req {
432            Err(Auth0Error::InvalidRequest(msg)) => {
433                assert_eq!(msg, "Email is required");
434            }
435            _ => panic!("Expected InvalidRequest error"),
436        }
437    }
438
439    #[test]
440    fn test_create_user_request_builder_missing_connection() {
441        let req = CreateUserRequest::builder()
442            .email("test@example.com")
443            .build();
444
445        assert!(req.is_err());
446        match req {
447            Err(Auth0Error::InvalidRequest(msg)) => {
448                assert_eq!(msg, "Connection is required");
449            }
450            _ => panic!("Expected InvalidRequest error"),
451        }
452    }
453
454    #[test]
455    fn test_create_user_request_builder_invalid_email() {
456        let req = CreateUserRequest::builder()
457            .email("invalid_email")
458            .connection("Username-Password-Authentication")
459            .build();
460
461        assert!(req.is_err());
462        match req {
463            Err(Auth0Error::InvalidRequest(msg)) => {
464                assert_eq!(msg, "Invalid email format");
465            }
466            _ => panic!("Expected InvalidRequest error"),
467        }
468    }
469}