torrust_index_backend/services/
authentication.rs1use std::sync::Arc;
3
4use argon2::{Argon2, PasswordHash, PasswordVerifier};
5use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
6use pbkdf2::Pbkdf2;
7
8use super::user::{DbUserProfileRepository, DbUserRepository};
9use crate::config::Configuration;
10use crate::databases::database::{Database, Error};
11use crate::errors::ServiceError;
12use crate::models::user::{UserAuthentication, UserClaims, UserCompact, UserId};
13use crate::utils::clock;
14
15pub struct Service {
16 configuration: Arc<Configuration>,
17 json_web_token: Arc<JsonWebToken>,
18 user_repository: Arc<DbUserRepository>,
19 user_profile_repository: Arc<DbUserProfileRepository>,
20 user_authentication_repository: Arc<DbUserAuthenticationRepository>,
21}
22
23impl Service {
24 pub fn new(
25 configuration: Arc<Configuration>,
26 json_web_token: Arc<JsonWebToken>,
27 user_repository: Arc<DbUserRepository>,
28 user_profile_repository: Arc<DbUserProfileRepository>,
29 user_authentication_repository: Arc<DbUserAuthenticationRepository>,
30 ) -> Self {
31 Self {
32 configuration,
33 json_web_token,
34 user_repository,
35 user_profile_repository,
36 user_authentication_repository,
37 }
38 }
39
40 pub async fn login(&self, username: &str, password: &str) -> Result<(String, UserCompact), ServiceError> {
53 let user_profile = self
55 .user_profile_repository
56 .get_user_profile_from_username(username)
57 .await
58 .map_err(|_| ServiceError::WrongPasswordOrUsername)?;
59
60 let user_authentication = self
62 .user_authentication_repository
63 .get_user_authentication_from_id(&user_profile.user_id)
64 .await
65 .map_err(|_| ServiceError::InternalServerError)?;
66
67 verify_password(password.as_bytes(), &user_authentication)?;
68
69 let settings = self.configuration.settings.read().await;
70
71 if settings.mail.email_verification_enabled && !user_profile.email_verified {
73 return Err(ServiceError::EmailNotVerified);
74 }
75
76 drop(settings);
78
79 let user_compact = self.user_repository.get_compact(&user_profile.user_id).await?;
80
81 let token = self.json_web_token.sign(user_compact.clone()).await;
83
84 Ok((token, user_compact))
85 }
86
87 pub async fn renew_token(&self, token: &str) -> Result<(String, UserCompact), ServiceError> {
96 const ONE_WEEK_IN_SECONDS: u64 = 604_800;
97
98 let claims = self.json_web_token.verify(token).await?;
100
101 let user_compact = self.user_repository.get_compact(&claims.user.user_id).await?;
102
103 let token = match claims.exp - clock::now() {
105 x if x < ONE_WEEK_IN_SECONDS => self.json_web_token.sign(user_compact.clone()).await,
106 _ => token.to_string(),
107 };
108
109 Ok((token, user_compact))
110 }
111}
112
113pub struct JsonWebToken {
114 cfg: Arc<Configuration>,
115}
116
117impl JsonWebToken {
118 pub fn new(cfg: Arc<Configuration>) -> Self {
119 Self { cfg }
120 }
121
122 pub async fn sign(&self, user: UserCompact) -> String {
129 let settings = self.cfg.settings.read().await;
130
131 let key = settings.auth.secret_key.as_bytes();
133
134 let exp_date = clock::now() + 1_209_600; let claims = UserClaims { user, exp: exp_date };
138
139 encode(&Header::default(), &claims, &EncodingKey::from_secret(key)).expect("argument `Header` should match `EncodingKey`")
140 }
141
142 pub async fn verify(&self, token: &str) -> Result<UserClaims, ServiceError> {
148 let settings = self.cfg.settings.read().await;
149
150 match decode::<UserClaims>(
151 token,
152 &DecodingKey::from_secret(settings.auth.secret_key.as_bytes()),
153 &Validation::new(Algorithm::HS256),
154 ) {
155 Ok(token_data) => {
156 if token_data.claims.exp < clock::now() {
157 return Err(ServiceError::TokenExpired);
158 }
159 Ok(token_data.claims)
160 }
161 Err(_) => Err(ServiceError::TokenInvalid),
162 }
163 }
164}
165
166pub struct DbUserAuthenticationRepository {
167 database: Arc<Box<dyn Database>>,
168}
169
170impl DbUserAuthenticationRepository {
171 #[must_use]
172 pub fn new(database: Arc<Box<dyn Database>>) -> Self {
173 Self { database }
174 }
175
176 pub async fn get_user_authentication_from_id(&self, user_id: &UserId) -> Result<UserAuthentication, Error> {
183 self.database.get_user_authentication_from_id(*user_id).await
184 }
185}
186
187fn verify_password(password: &[u8], user_authentication: &UserAuthentication) -> Result<(), ServiceError> {
194 let parsed_hash = PasswordHash::new(&user_authentication.password_hash)?;
196
197 match parsed_hash.algorithm.as_str() {
198 "argon2id" => {
199 if Argon2::default().verify_password(password, &parsed_hash).is_err() {
200 return Err(ServiceError::WrongPasswordOrUsername);
201 }
202
203 Ok(())
204 }
205 "pbkdf2-sha256" => {
206 if Pbkdf2.verify_password(password, &parsed_hash).is_err() {
207 return Err(ServiceError::WrongPasswordOrUsername);
208 }
209
210 Ok(())
211 }
212 _ => Err(ServiceError::WrongPasswordOrUsername),
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::verify_password;
219 use crate::models::user::UserAuthentication;
220
221 #[test]
222 fn password_hashed_with_pbkdf2_sha256_should_be_verified() {
223 let password = "12345678".as_bytes();
224 let password_hash =
225 "$pbkdf2-sha256$i=10000,l=32$pZIh8nilm+cg6fk5Ubf2zQ$AngLuZ+sGUragqm4bIae/W+ior0TWxYFFaTx8CulqtY".to_string();
226 let user_authentication = UserAuthentication {
227 user_id: 1i64,
228 password_hash,
229 };
230
231 assert!(verify_password(password, &user_authentication).is_ok());
232 assert!(verify_password("incorrect password".as_bytes(), &user_authentication).is_err());
233 }
234
235 #[test]
236 fn password_hashed_with_argon2_should_be_verified() {
237 let password = "87654321".as_bytes();
238 let password_hash =
239 "$argon2id$v=19$m=4096,t=3,p=1$ycK5lJ4xmFBnaJ51M1j1eA$kU3UlNiSc3JDbl48TCj7JBDKmrT92DOUAgo4Yq0+nMw".to_string();
240 let user_authentication = UserAuthentication {
241 user_id: 1i64,
242 password_hash,
243 };
244
245 assert!(verify_password(password, &user_authentication).is_ok());
246 assert!(verify_password("incorrect password".as_bytes(), &user_authentication).is_err());
247 }
248}