auth0_integration/services/
auth0_client.rs1use rand::Rng;
2use rand::seq::SliceRandom;
3use serde::Serialize;
4
5use crate::config::Auth0Config;
6use crate::error::AppError;
7use crate::models::{Auth0Role, Auth0User, CreateUserRequest, Role, UpdateUserRequest};
8use crate::services::HttpClient;
9
10pub struct Auth0Client {
11 http: HttpClient,
12 token: String,
13}
14
15impl Auth0Client {
16 pub fn new(config: &Auth0Config, token: String) -> Self {
17 Self {
18 http: HttpClient::new(config),
19 token,
20 }
21 }
22
23 pub async fn get_user_by_email(&self, email: &str) -> Result<Vec<Auth0User>, AppError> {
24 let path = format!("/api/v2/users-by-email?email={}", email);
25 let res = self.http.get_authorized(&path, &self.token).await?;
26
27 if !res.status().is_success() {
28 let text = res.text().await.unwrap_or_default();
29 return Err(AppError::Auth0(text));
30 }
31
32 Ok(res.json::<Vec<Auth0User>>().await?)
33 }
34
35 pub async fn create_user(
42 &self,
43 name: &str,
44 email: &str,
45 role: &Role,
46 ) -> Result<Auth0User, AppError> {
47 let password = generate_password();
48 let body = CreateUserRequest::new("Username-Password-Authentication", email, name, password);
49 let res = self
50 .http
51 .post_authorized("/api/v2/users", &body, &self.token)
52 .await?;
53
54 if !res.status().is_success() {
55 let text = res.text().await.unwrap_or_default();
56 return Err(AppError::Auth0(format!("Failed to create user: {text}")));
57 }
58
59 let user = res.json::<Auth0User>().await?;
60 self.assign_role(&user.user_id, role).await?;
61
62 Ok(user)
63 }
64
65 pub async fn update_user(
66 &self,
67 user_id: &str,
68 req: UpdateUserRequest,
69 role: Option<&Role>,
70 ) -> Result<Auth0User, AppError> {
71 let path = format!("/api/v2/users/{}", user_id);
72 let res = self
73 .http
74 .patch_authorized(&path, &req, &self.token)
75 .await?;
76
77 if !res.status().is_success() {
78 let text = res.text().await.unwrap_or_default();
79 return Err(AppError::Auth0(text));
80 }
81
82 let user = res.json::<Auth0User>().await?;
83
84 if let Some(role) = role {
85 let current_roles = self.get_user_roles(user_id).await?;
86 let already_has_role = current_roles
87 .iter()
88 .any(|r| r.name == role.as_auth0_name());
89 if !already_has_role {
90 self.assign_role(user_id, role).await?;
91 }
92 }
93
94 Ok(user)
95 }
96
97 pub async fn delete_user(&self, user_id: &str) -> Result<(), AppError> {
98 let path = format!("/api/v2/users/{}", user_id);
99 let res = self
100 .http
101 .delete_authorized(&path, &self.token)
102 .await?;
103
104 if !res.status().is_success() {
105 let text = res.text().await.unwrap_or_default();
106 return Err(AppError::Auth0(format!("Failed to delete user: {text}")));
107 }
108
109 Ok(())
110 }
111
112 async fn assign_role(&self, user_id: &str, role: &Role) -> Result<(), AppError> {
115 let role_id = self.get_role_id(role).await?;
116 let path = format!("/api/v2/users/{}/roles", user_id);
117
118 #[derive(Serialize)]
119 struct Body {
120 roles: Vec<String>,
121 }
122
123 let existing = self.get_user_roles(user_id).await?;
125 if !existing.is_empty() {
126 let existing_ids: Vec<String> = existing.into_iter().map(|r| r.id).collect();
127 let res = self
128 .http
129 .delete_authorized_with_body(&path, &Body { roles: existing_ids }, &self.token)
130 .await?;
131 if !res.status().is_success() {
132 let text = res.text().await.unwrap_or_default();
133 return Err(AppError::Auth0(format!("Failed to remove existing roles: {text}")));
134 }
135 }
136
137 let res = self
138 .http
139 .post_authorized(&path, &Body { roles: vec![role_id] }, &self.token)
140 .await?;
141
142 if !res.status().is_success() {
143 let text = res.text().await.unwrap_or_default();
144 return Err(AppError::Auth0(format!("Failed to assign role: {text}")));
145 }
146
147 Ok(())
148 }
149
150 async fn get_user_roles(&self, user_id: &str) -> Result<Vec<Auth0Role>, AppError> {
151 let path = format!("/api/v2/users/{}/roles", user_id);
152 let res = self.http.get_authorized(&path, &self.token).await?;
153
154 if !res.status().is_success() {
155 let text = res.text().await.unwrap_or_default();
156 return Err(AppError::Auth0(format!("Failed to fetch user roles: {text}")));
157 }
158
159 Ok(res.json::<Vec<Auth0Role>>().await?)
160 }
161
162 async fn get_role_id(&self, role: &Role) -> Result<String, AppError> {
164 let auth0_name = role.as_auth0_name();
165 let path = format!("/api/v2/roles?name_filter={}", auth0_name);
166 let res = self.http.get_authorized(&path, &self.token).await?;
167
168 if !res.status().is_success() {
169 let text = res.text().await.unwrap_or_default();
170 return Err(AppError::Auth0(format!("Failed to fetch roles: {text}")));
171 }
172
173 let roles = res.json::<Vec<Auth0Role>>().await?;
174 roles
175 .into_iter()
176 .find(|r| r.name == auth0_name)
177 .map(|r| r.id)
178 .ok_or_else(|| AppError::Auth0(format!("Role '{auth0_name}' not found in Auth0")))
179 }
180}
181
182fn generate_password() -> String {
185 let mut rng = rand::thread_rng();
186
187 let uppercase: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZ";
188 let lowercase: &[u8] = b"abcdefghjkmnpqrstuvwxyz";
189 let digits: &[u8] = b"23456789";
190 let special: &[u8] = b"!@#$%^&*";
191 let all: Vec<u8> = [uppercase, lowercase, digits, special].concat();
192
193 let mut chars: Vec<u8> = vec![
195 uppercase[rng.gen_range(0..uppercase.len())],
196 lowercase[rng.gen_range(0..lowercase.len())],
197 digits[rng.gen_range(0..digits.len())],
198 special[rng.gen_range(0..special.len())],
199 ];
200
201 for _ in 0..12 {
202 chars.push(all[rng.gen_range(0..all.len())]);
203 }
204
205 chars.shuffle(&mut rng);
206 String::from_utf8(chars).expect("password chars are all ASCII")
207}