init project, impl authorization

This commit is contained in:
2022-06-06 14:17:58 +05:00
commit ec73d6584f
26 changed files with 2680 additions and 0 deletions

View File

@ -0,0 +1,139 @@
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)
}
}