140 lines
5.0 KiB
Rust
140 lines
5.0 KiB
Rust
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::<char, _>(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<Tokens, AppError> {
|
|
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<Tokens, AppError> {
|
|
let token = match decode::<RefreshTokenClaims>(
|
|
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)
|
|
}
|
|
}
|