use crate::data::account::AccountRepository; use crate::dto::account::{AccessTokenClaims, CreateAccount, Login, RefreshTokenClaims, Tokens}; use crate::error::AppError; use crate::service::account::entity::Account; use crate::utils::uuid::sqlx_uuid_to_uuid; use crate::JwtSecretKey; use chrono::{Duration, Utc}; use derive_more::Constructor; use jsonwebtoken::errors::ErrorKind; use jsonwebtoken::{decode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; use rand::Rng; use sha2::{Digest, Sha512}; use std::ops::Add; use uuid::Uuid; #[derive(Constructor, Clone)] pub struct AccountService { account_repository: AccountRepository, secret: JwtSecretKey, } impl AccountService { pub async fn create_account(&self, create_account_dto: CreateAccount) -> Tokens { let salt: String = rand::thread_rng() .sample_iter::(rand::distributions::Standard) .take(32) .collect(); let mut hasher = Sha512::new(); hasher.update(create_account_dto.password.add(salt.as_str()).as_bytes()); let hash = hasher.finalize().to_vec(); let account = self .account_repository .create_account(Account::new( Uuid::new_v4(), create_account_dto.name, hash, salt, create_account_dto.email, )) .await; self.create_tokens(account.id).await } pub async fn create_tokens(&self, for_account: Uuid) -> Tokens { let header = Header::new(Algorithm::HS512); let iat = Utc::now(); let access_token_claims = AccessTokenClaims::new( iat.add(Duration::hours(1)).timestamp(), for_account.to_string(), iat.timestamp(), ); let encoding_key = EncodingKey::from_secret(self.secret.0.as_slice()); let access_token = jsonwebtoken::encode(&header, &access_token_claims, &encoding_key).unwrap(); let refresh_token_data = self .account_repository .create_refresh_token(for_account) .await; let refresh_token_claims = RefreshTokenClaims::new( iat.add(Duration::days(30)).timestamp(), refresh_token_data.user_id.to_string(), iat.timestamp(), sqlx_uuid_to_uuid(refresh_token_data.id), ); let refresh_token = jsonwebtoken::encode(&header, &refresh_token_claims, &encoding_key).unwrap(); Tokens::new(access_token, refresh_token) } pub async fn login(&self, login_dto: Login) -> Result { let account = if let Some(account) = self .account_repository .get_account_by_name(login_dto.name) .await? { account } else { return Err(AppError::from_code("USER_NOT_FOUND".to_string())); }; let mut hasher = Sha512::new(); hasher.update( login_dto .password .add(account.password_salt.as_str()) .as_bytes(), ); let hash = hasher.finalize().to_vec(); if hash == account.password_hash { Ok(self.create_tokens(account.id).await) } else { Err(AppError::from_code("INCORRECT_PASSWORD".to_string())) } } pub async fn refresh_token(&self, token: String) -> Result { let token = match decode::( token.as_str(), &DecodingKey::from_secret(self.secret.0.as_slice()), &Validation::new(Algorithm::HS512), ) { Err(err) => { return Err(match *err.kind() { ErrorKind::InvalidToken => { AppError::from_code_and_status("NOT_TOKEN".to_string(), 400) } ErrorKind::ExpiredSignature => { AppError::from_code_and_status("EXPIRED".to_string(), 403) } _ => AppError::from_code_and_status("INVALID_TOKEN".to_string(), 401), }); } Ok(claims) => claims, }; let user_uuid = Uuid::parse_str(&token.claims.sub).unwrap(); let token_uuid = token.claims.refresh_token_id; let old_token = if let Some(token) = self .account_repository .get_refresh_token(token_uuid, user_uuid) .await? { token } else { return Err(AppError::from_code_and_status( "INVALID_TOKEN".to_string(), 401, )); }; if old_token.time_created.add(Duration::days(30)) > Utc::now().naive_utc() { return Err(AppError::from_code_and_status( "TOKEN_EXPIRED".to_string(), 400, )); }; self.account_repository .delete_refresh_token(token_uuid, user_uuid) .await?; Ok(self.create_tokens(user_uuid).await) } }