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 get_or_create_user(
43 &self,
44 name: &str,
45 email: &str,
46 role: &Role,
47 ) -> Result<Auth0User, AppError> {
48 let existing = self.get_user_by_email(email).await?;
49
50 let user = if let Some(existing_user) = existing.into_iter().next() {
51 let mut req = UpdateUserRequest::new();
53 req.name = Some(name.to_string());
54 self.update_user(&existing_user.user_id, req).await?
55 } else {
56 let password = generate_password();
58 let body = CreateUserRequest::new("Username-Password-Authentication", email, name, password);
59 let res = self
60 .http
61 .post_authorized("/api/v2/users", &body, &self.token)
62 .await?;
63
64 if !res.status().is_success() {
65 let text = res.text().await.unwrap_or_default();
66 return Err(AppError::Auth0(format!("Failed to create user: {text}")));
67 }
68
69 res.json::<Auth0User>().await?
70 };
71
72 self.assign_role(&user.user_id, role).await?;
73
74 Ok(user)
75 }
76
77 pub async fn update_user(&self, user_id: &str, req: UpdateUserRequest) -> Result<Auth0User, AppError> {
78 let path = format!("/api/v2/users/{}", user_id);
79 let res = self
80 .http
81 .patch_authorized(&path, &req, &self.token)
82 .await?;
83
84 if !res.status().is_success() {
85 let text = res.text().await.unwrap_or_default();
86 return Err(AppError::Auth0(text));
87 }
88
89 Ok(res.json::<Auth0User>().await?)
90 }
91
92 async fn assign_role(&self, user_id: &str, role: &Role) -> Result<(), AppError> {
94 let role_id = self.get_role_id(role).await?;
95 let path = format!("/api/v2/users/{}/roles", user_id);
96
97 #[derive(Serialize)]
98 struct Body {
99 roles: Vec<String>,
100 }
101
102 let res = self
103 .http
104 .post_authorized(&path, &Body { roles: vec![role_id] }, &self.token)
105 .await?;
106
107 if !res.status().is_success() {
108 let text = res.text().await.unwrap_or_default();
109 return Err(AppError::Auth0(format!("Failed to assign role: {text}")));
110 }
111
112 Ok(())
113 }
114
115 async fn get_role_id(&self, role: &Role) -> Result<String, AppError> {
117 let auth0_name = role.as_auth0_name();
118 let path = format!("/api/v2/roles?name_filter={}", auth0_name);
119 let res = self.http.get_authorized(&path, &self.token).await?;
120
121 if !res.status().is_success() {
122 let text = res.text().await.unwrap_or_default();
123 return Err(AppError::Auth0(format!("Failed to fetch roles: {text}")));
124 }
125
126 let roles = res.json::<Vec<Auth0Role>>().await?;
127 roles
128 .into_iter()
129 .find(|r| r.name == auth0_name)
130 .map(|r| r.id)
131 .ok_or_else(|| AppError::Auth0(format!("Role '{auth0_name}' not found in Auth0")))
132 }
133}
134
135fn generate_password() -> String {
138 let mut rng = rand::thread_rng();
139
140 let uppercase: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZ";
141 let lowercase: &[u8] = b"abcdefghjkmnpqrstuvwxyz";
142 let digits: &[u8] = b"23456789";
143 let special: &[u8] = b"!@#$%^&*";
144 let all: Vec<u8> = [uppercase, lowercase, digits, special].concat();
145
146 let mut chars: Vec<u8> = vec![
148 uppercase[rng.gen_range(0..uppercase.len())],
149 lowercase[rng.gen_range(0..lowercase.len())],
150 digits[rng.gen_range(0..digits.len())],
151 special[rng.gen_range(0..special.len())],
152 ];
153
154 for _ in 0..12 {
155 chars.push(all[rng.gen_range(0..all.len())]);
156 }
157
158 chars.shuffle(&mut rng);
159 String::from_utf8(chars).expect("password chars are all ASCII")
160}