torrust_index_backend/services/
user.rs1use std::sync::Arc;
3
4use argon2::password_hash::SaltString;
5use argon2::{Argon2, PasswordHasher};
6use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
7use log::{debug, info};
8use pbkdf2::password_hash::rand_core::OsRng;
9
10use crate::config::{Configuration, EmailOnSignup};
11use crate::databases::database::{Database, Error};
12use crate::errors::ServiceError;
13use crate::mailer;
14use crate::mailer::VerifyClaims;
15use crate::models::user::{UserCompact, UserId, UserProfile};
16use crate::utils::validation::validate_email_address;
17use crate::web::api::v1::contexts::user::forms::RegistrationForm;
18
19fn no_email() -> String {
23 String::new()
24}
25
26pub struct RegistrationService {
27 configuration: Arc<Configuration>,
28 mailer: Arc<mailer::Service>,
29 user_repository: Arc<DbUserRepository>,
30 user_profile_repository: Arc<DbUserProfileRepository>,
31}
32
33impl RegistrationService {
34 #[must_use]
35 pub fn new(
36 configuration: Arc<Configuration>,
37 mailer: Arc<mailer::Service>,
38 user_repository: Arc<DbUserRepository>,
39 user_profile_repository: Arc<DbUserProfileRepository>,
40 ) -> Self {
41 Self {
42 configuration,
43 mailer,
44 user_repository,
45 user_profile_repository,
46 }
47 }
48
49 pub async fn register_user(&self, registration_form: &RegistrationForm, api_base_url: &str) -> Result<UserId, ServiceError> {
69 info!("registering user: {}", registration_form.username);
70
71 let settings = self.configuration.settings.read().await;
72
73 let opt_email = match settings.auth.email_on_signup {
74 EmailOnSignup::Required => {
75 if registration_form.email.is_none() {
76 return Err(ServiceError::EmailMissing);
77 }
78 registration_form.email.clone()
79 }
80 EmailOnSignup::None => None,
81 EmailOnSignup::Optional => registration_form.email.clone(),
82 };
83
84 if let Some(email) = ®istration_form.email {
85 if !validate_email_address(email) {
86 return Err(ServiceError::EmailInvalid);
87 }
88 }
89
90 if registration_form.password != registration_form.confirm_password {
91 return Err(ServiceError::PasswordsDontMatch);
92 }
93
94 let password_length = registration_form.password.len();
95
96 if password_length <= settings.auth.min_password_length {
97 return Err(ServiceError::PasswordTooShort);
98 }
99
100 if password_length >= settings.auth.max_password_length {
101 return Err(ServiceError::PasswordTooLong);
102 }
103
104 let salt = SaltString::generate(&mut OsRng);
105
106 let argon2 = Argon2::default();
108
109 let password_hash = argon2
111 .hash_password(registration_form.password.as_bytes(), &salt)?
112 .to_string();
113
114 if registration_form.username.contains('@') {
115 return Err(ServiceError::UsernameInvalid);
116 }
117
118 let user_id = self
119 .user_repository
120 .add(
121 ®istration_form.username,
122 &opt_email.clone().unwrap_or(no_email()),
123 &password_hash,
124 )
125 .await?;
126
127 if user_id == 1 {
129 let _ = self.user_repository.grant_admin_role(&user_id).await;
130 }
131
132 if settings.mail.email_verification_enabled && opt_email.is_some() {
133 let mail_res = self
134 .mailer
135 .send_verification_mail(
136 &opt_email.expect("variable `email` is checked above"),
137 ®istration_form.username,
138 user_id,
139 api_base_url,
140 )
141 .await;
142
143 if mail_res.is_err() {
144 let _ = self.user_repository.delete(&user_id).await;
145 return Err(ServiceError::FailedToSendVerificationEmail);
146 }
147 }
148
149 Ok(user_id)
150 }
151
152 pub async fn verify_email(&self, token: &str) -> Result<bool, ServiceError> {
160 let settings = self.configuration.settings.read().await;
161
162 let token_data = match decode::<VerifyClaims>(
163 token,
164 &DecodingKey::from_secret(settings.auth.secret_key.as_bytes()),
165 &Validation::new(Algorithm::HS256),
166 ) {
167 Ok(token_data) => {
168 if !token_data.claims.iss.eq("email-verification") {
169 return Ok(false);
170 }
171
172 token_data.claims
173 }
174 Err(_) => return Ok(false),
175 };
176
177 drop(settings);
178
179 let user_id = token_data.sub;
180
181 if self.user_profile_repository.verify_email(&user_id).await.is_err() {
182 return Err(ServiceError::DatabaseError);
183 };
184
185 Ok(true)
186 }
187}
188
189pub struct BanService {
190 user_repository: Arc<DbUserRepository>,
191 user_profile_repository: Arc<DbUserProfileRepository>,
192 banned_user_list: Arc<DbBannedUserList>,
193}
194
195impl BanService {
196 #[must_use]
197 pub fn new(
198 user_repository: Arc<DbUserRepository>,
199 user_profile_repository: Arc<DbUserProfileRepository>,
200 banned_user_list: Arc<DbBannedUserList>,
201 ) -> Self {
202 Self {
203 user_repository,
204 user_profile_repository,
205 banned_user_list,
206 }
207 }
208
209 pub async fn ban_user(&self, username_to_be_banned: &str, user_id: &UserId) -> Result<(), ServiceError> {
219 debug!("user with ID {user_id} banning username: {username_to_be_banned}");
220
221 let user = self.user_repository.get_compact(user_id).await?;
222
223 if !user.administrator {
225 return Err(ServiceError::Unauthorized);
226 }
227
228 let user_profile = self
229 .user_profile_repository
230 .get_user_profile_from_username(username_to_be_banned)
231 .await?;
232
233 self.banned_user_list.add(&user_profile.user_id).await?;
234
235 Ok(())
236 }
237}
238
239pub struct DbUserRepository {
240 database: Arc<Box<dyn Database>>,
241}
242
243impl DbUserRepository {
244 #[must_use]
245 pub fn new(database: Arc<Box<dyn Database>>) -> Self {
246 Self { database }
247 }
248
249 pub async fn get_compact(&self, user_id: &UserId) -> Result<UserCompact, ServiceError> {
255 self.database
258 .get_user_compact_from_id(*user_id)
259 .await
260 .map_err(|_| ServiceError::UserNotFound)
261 }
262
263 pub async fn grant_admin_role(&self, user_id: &UserId) -> Result<(), Error> {
269 self.database.grant_admin_role(*user_id).await
270 }
271
272 pub async fn delete(&self, user_id: &UserId) -> Result<(), Error> {
278 self.database.delete_user(*user_id).await
279 }
280
281 pub async fn add(&self, username: &str, email: &str, password_hash: &str) -> Result<UserId, Error> {
287 self.database.insert_user_and_get_id(username, email, password_hash).await
288 }
289}
290
291pub struct DbUserProfileRepository {
292 database: Arc<Box<dyn Database>>,
293}
294
295impl DbUserProfileRepository {
296 #[must_use]
297 pub fn new(database: Arc<Box<dyn Database>>) -> Self {
298 Self { database }
299 }
300
301 pub async fn verify_email(&self, user_id: &UserId) -> Result<(), Error> {
307 self.database.verify_email(*user_id).await
308 }
309
310 pub async fn get_user_profile_from_username(&self, username: &str) -> Result<UserProfile, Error> {
316 self.database.get_user_profile_from_username(username).await
317 }
318}
319
320pub struct DbBannedUserList {
321 database: Arc<Box<dyn Database>>,
322}
323
324impl DbBannedUserList {
325 #[must_use]
326 pub fn new(database: Arc<Box<dyn Database>>) -> Self {
327 Self { database }
328 }
329
330 pub async fn add(&self, user_id: &UserId) -> Result<(), Error> {
341 let reason = "no reason".to_string();
347
348 let date_expiry = chrono::NaiveDateTime::parse_from_str("9999-01-01 00:00:00", "%Y-%m-%d %H:%M:%S")
350 .expect("Could not parse date from 9999-01-01 00:00:00.");
351
352 self.database.ban_user(*user_id, &reason, date_expiry).await
353 }
354}