1use std::sync::Arc;
3
4use argon2::password_hash::SaltString;
5use argon2::{Argon2, PasswordHasher};
6use async_trait::async_trait;
7use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
8#[cfg(test)]
9use mockall::automock;
10use pbkdf2::password_hash::rand_core::OsRng;
11use tracing::{debug, info};
12
13use super::authentication::DbUserAuthenticationRepository;
14use super::authorization::{self, ACTION};
15use crate::config::{Configuration, PasswordConstraints};
16use crate::databases::database::{Database, Error};
17use crate::errors::ServiceError;
18use crate::mailer;
19use crate::mailer::VerifyClaims;
20use crate::models::user::{UserCompact, UserId, UserProfile, Username};
21use crate::services::authentication::verify_password;
22use crate::utils::validation::validate_email_address;
23use crate::web::api::server::v1::contexts::user::forms::{ChangePasswordForm, RegistrationForm};
24
25fn no_email() -> String {
29 String::new()
30}
31
32pub struct RegistrationService {
33 configuration: Arc<Configuration>,
34 mailer: Arc<mailer::Service>,
35 user_repository: Arc<Box<dyn Repository>>,
36 user_profile_repository: Arc<DbUserProfileRepository>,
37}
38
39impl RegistrationService {
40 #[must_use]
41 pub fn new(
42 configuration: Arc<Configuration>,
43 mailer: Arc<mailer::Service>,
44 user_repository: Arc<Box<dyn Repository>>,
45 user_profile_repository: Arc<DbUserProfileRepository>,
46 ) -> Self {
47 Self {
48 configuration,
49 mailer,
50 user_repository,
51 user_profile_repository,
52 }
53 }
54
55 pub async fn register_user(&self, registration_form: &RegistrationForm, api_base_url: &str) -> Result<UserId, ServiceError> {
75 info!("registering user: {}", registration_form.username);
76
77 let settings = self.configuration.settings.read().await;
78
79 match &settings.registration {
80 Some(registration) => {
81 let Ok(username) = registration_form.username.parse::<Username>() else {
82 return Err(ServiceError::UsernameInvalid);
83 };
84
85 let opt_email = match ®istration.email {
86 Some(email) => {
87 if email.required && registration_form.email.is_none() {
88 return Err(ServiceError::EmailMissing);
89 }
90 match ®istration_form.email {
91 Some(email) => {
92 if email.trim() == String::new() {
93 None
94 } else {
95 Some(email.clone())
96 }
97 }
98 None => None,
99 }
100 }
101 None => None,
102 };
103
104 if let Some(email) = &opt_email {
105 if !validate_email_address(email) {
106 return Err(ServiceError::EmailInvalid);
107 }
108 }
109
110 let password_constraints = PasswordConstraints {
111 min_password_length: settings.auth.password_constraints.min_password_length,
112 max_password_length: settings.auth.password_constraints.max_password_length,
113 };
114
115 validate_password_constraints(
116 ®istration_form.password,
117 ®istration_form.confirm_password,
118 &password_constraints,
119 )?;
120
121 let password_hash = hash_password(®istration_form.password)?;
122
123 let user_id = self
124 .user_repository
125 .add(
126 &username.to_string(),
127 &opt_email.clone().unwrap_or(no_email()),
128 &password_hash,
129 )
130 .await?;
131
132 if user_id == 1 {
134 drop(self.user_repository.grant_admin_role(&user_id).await);
135 }
136
137 if let Some(email) = ®istration.email {
138 if email.verification_required {
139 if let Some(email) = opt_email {
141 let mail_res = self
142 .mailer
143 .send_verification_mail(&email, ®istration_form.username, user_id, api_base_url)
144 .await;
145
146 if mail_res.is_err() {
147 drop(self.user_repository.delete(&user_id).await);
148 return Err(ServiceError::FailedToSendVerificationEmail);
149 }
150 }
151 }
152 }
153
154 Ok(user_id)
155 }
156 None => Err(ServiceError::ClosedForRegistration),
157 }
158 }
159
160 pub async fn verify_email(&self, token: &str) -> Result<bool, ServiceError> {
168 let settings = self.configuration.settings.read().await;
169
170 let token_data = match decode::<VerifyClaims>(
171 token,
172 &DecodingKey::from_secret(settings.auth.user_claim_token_pepper.as_bytes()),
173 &Validation::new(Algorithm::HS256),
174 ) {
175 Ok(token_data) => {
176 if !token_data.claims.iss.eq("email-verification") {
177 return Ok(false);
178 }
179
180 token_data.claims
181 }
182 Err(_) => return Ok(false),
183 };
184
185 drop(settings);
186
187 let user_id = token_data.sub;
188
189 if self.user_profile_repository.verify_email(&user_id).await.is_err() {
190 return Err(ServiceError::DatabaseError);
191 };
192
193 Ok(true)
194 }
195}
196
197pub struct ProfileService {
198 configuration: Arc<Configuration>,
199 user_authentication_repository: Arc<DbUserAuthenticationRepository>,
200 authorization_service: Arc<authorization::Service>,
201}
202
203impl ProfileService {
204 #[must_use]
205 pub fn new(
206 configuration: Arc<Configuration>,
207 user_repository: Arc<DbUserAuthenticationRepository>,
208 authorization_service: Arc<authorization::Service>,
209 ) -> Self {
210 Self {
211 configuration,
212 user_authentication_repository: user_repository,
213 authorization_service,
214 }
215 }
216
217 pub async fn change_password(
231 &self,
232 maybe_user_id: Option<UserId>,
233 change_password_form: &ChangePasswordForm,
234 ) -> Result<(), ServiceError> {
235 let Some(user_id) = maybe_user_id else {
236 return Err(ServiceError::UnauthorizedActionForGuests);
237 };
238
239 self.authorization_service
240 .authorize(ACTION::ChangePassword, maybe_user_id)
241 .await?;
242
243 info!("changing user password for user ID: {}", user_id);
244
245 let settings = self.configuration.settings.read().await;
246
247 let user_authentication = self
248 .user_authentication_repository
249 .get_user_authentication_from_id(&user_id)
250 .await?;
251
252 verify_password(change_password_form.current_password.as_bytes(), &user_authentication)?;
253
254 let password_constraints = PasswordConstraints {
255 min_password_length: settings.auth.password_constraints.min_password_length,
256 max_password_length: settings.auth.password_constraints.max_password_length,
257 };
258
259 validate_password_constraints(
260 &change_password_form.password,
261 &change_password_form.confirm_password,
262 &password_constraints,
263 )?;
264
265 let password_hash = hash_password(&change_password_form.password)?;
266
267 self.user_authentication_repository
268 .change_password(user_id, &password_hash)
269 .await?;
270
271 Ok(())
272 }
273}
274
275pub struct BanService {
276 user_profile_repository: Arc<DbUserProfileRepository>,
277 banned_user_list: Arc<DbBannedUserList>,
278 authorization_service: Arc<authorization::Service>,
279}
280
281impl BanService {
282 #[must_use]
283 pub fn new(
284 user_profile_repository: Arc<DbUserProfileRepository>,
285 banned_user_list: Arc<DbBannedUserList>,
286 authorization_service: Arc<authorization::Service>,
287 ) -> Self {
288 Self {
289 user_profile_repository,
290 banned_user_list,
291 authorization_service,
292 }
293 }
294
295 pub async fn ban_user(&self, username_to_be_banned: &str, maybe_user_id: Option<UserId>) -> Result<(), ServiceError> {
305 let Some(user_id) = maybe_user_id else {
306 return Err(ServiceError::UnauthorizedActionForGuests);
307 };
308
309 self.authorization_service.authorize(ACTION::BanUser, maybe_user_id).await?;
310
311 debug!("user with ID {} banning username: {username_to_be_banned}", user_id);
312
313 let user_profile = self
314 .user_profile_repository
315 .get_user_profile_from_username(username_to_be_banned)
316 .await?;
317
318 self.banned_user_list.add(&user_profile.user_id).await?;
319
320 Ok(())
321 }
322}
323
324#[cfg_attr(test, automock)]
325#[async_trait]
326pub trait Repository: Sync + Send {
327 async fn get_compact(&self, user_id: &UserId) -> Result<UserCompact, ServiceError>;
328 async fn grant_admin_role(&self, user_id: &UserId) -> Result<(), Error>;
329 async fn delete(&self, user_id: &UserId) -> Result<(), Error>;
330 async fn add(&self, username: &str, email: &str, password_hash: &str) -> Result<UserId, Error>;
331}
332
333pub struct DbUserRepository {
334 database: Arc<Box<dyn Database>>,
335}
336
337impl DbUserRepository {
338 #[must_use]
339 pub fn new(database: Arc<Box<dyn Database>>) -> Self {
340 Self { database }
341 }
342}
343
344#[async_trait]
345impl Repository for DbUserRepository {
346 async fn get_compact(&self, user_id: &UserId) -> Result<UserCompact, ServiceError> {
352 self.database
355 .get_user_compact_from_id(*user_id)
356 .await
357 .map_err(|_| ServiceError::UserNotFound)
358 }
359
360 async fn grant_admin_role(&self, user_id: &UserId) -> Result<(), Error> {
366 self.database.grant_admin_role(*user_id).await
367 }
368
369 async fn delete(&self, user_id: &UserId) -> Result<(), Error> {
375 self.database.delete_user(*user_id).await
376 }
377
378 async fn add(&self, username: &str, email: &str, password_hash: &str) -> Result<UserId, Error> {
384 self.database.insert_user_and_get_id(username, email, password_hash).await
385 }
386}
387
388pub struct DbUserProfileRepository {
389 database: Arc<Box<dyn Database>>,
390}
391
392impl DbUserProfileRepository {
393 #[must_use]
394 pub fn new(database: Arc<Box<dyn Database>>) -> Self {
395 Self { database }
396 }
397
398 pub async fn verify_email(&self, user_id: &UserId) -> Result<(), Error> {
404 self.database.verify_email(*user_id).await
405 }
406
407 pub async fn get_user_profile_from_username(&self, username: &str) -> Result<UserProfile, Error> {
413 self.database.get_user_profile_from_username(username).await
414 }
415}
416
417pub struct DbBannedUserList {
418 database: Arc<Box<dyn Database>>,
419}
420
421impl DbBannedUserList {
422 #[must_use]
423 pub fn new(database: Arc<Box<dyn Database>>) -> Self {
424 Self { database }
425 }
426
427 pub async fn add(&self, user_id: &UserId) -> Result<(), Error> {
438 let reason = "no reason".to_string();
444
445 let date_expiry = chrono::NaiveDateTime::parse_from_str("9999-01-01 00:00:00", "%Y-%m-%d %H:%M:%S")
447 .expect("Could not parse date from 9999-01-01 00:00:00.");
448
449 self.database.ban_user(*user_id, &reason, date_expiry).await
450 }
451}
452
453fn validate_password_constraints(
454 password: &str,
455 confirm_password: &str,
456 password_rules: &PasswordConstraints,
457) -> Result<(), ServiceError> {
458 if password != confirm_password {
459 return Err(ServiceError::PasswordsDontMatch);
460 }
461
462 let password_length = password.len();
463
464 if password_length < password_rules.min_password_length {
465 return Err(ServiceError::PasswordTooShort);
466 }
467
468 if password_length > password_rules.max_password_length {
469 return Err(ServiceError::PasswordTooLong);
470 }
471
472 Ok(())
473}
474
475fn hash_password(password: &str) -> Result<String, ServiceError> {
476 let salt = SaltString::generate(&mut OsRng);
477
478 let argon2 = Argon2::default();
480
481 let password_hash = argon2.hash_password(password.as_bytes(), &salt)?.to_string();
483
484 Ok(password_hash)
485}