User lib 1.0

This commit is contained in:
Franklin 2023-09-30 10:13:43 -04:00
parent ed455426dd
commit c784889623
7 changed files with 65 additions and 89 deletions

19
Cargo.lock generated
View File

@ -277,6 +277,17 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "err"
version = "0.1.1"
source = "git+https://git.franklinblanco.dev/franklinblanco/err.git#18cc77b6266d0fc90237a7ccb297d3eeb574f78a"
dependencies = [
"serde",
"sqlx",
"stdext",
"thiserror",
]
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.1" version = "0.3.1"
@ -1374,6 +1385,12 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "stdext"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f3b6b32ae82412fb897ef134867d53a294f57ba5b758f06d71e865352c3e207"
[[package]] [[package]]
name = "stringprep" name = "stringprep"
version = "0.1.3" version = "0.1.3"
@ -1608,12 +1625,12 @@ version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"data-encoding", "data-encoding",
"err",
"futures-util", "futures-util",
"log", "log",
"ring", "ring",
"serde", "serde",
"sqlx", "sqlx",
"thiserror",
"tokio", "tokio",
] ]

View File

@ -18,4 +18,5 @@ ring = "0.16.20"
data-encoding = "2.3.2" data-encoding = "2.3.2"
futures-util = "0.3" futures-util = "0.3"
log = "0.4.19" log = "0.4.19"
thiserror = "1.0.48"
err = { git = "https://git.franklinblanco.dev/franklinblanco/err.git" }

View File

@ -1,39 +0,0 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
/// Used to return a simple error from FromStr implementations.
#[error("Error parsing string into value")]
FromStrError,
/// Every error that is returned from a DAO operation.
#[error("Error from the Database: {0}")]
DatabaseError(#[from] sqlx::Error),
/// A vec of ValidationErrors
#[error("Validation Errors: {0:?}")]
ValidationErrors(Vec<ValidationError>),
/// Something already exists. That something should be {0}
/// Example: "User" "Credential"
#[error("Error {0} Already exists.")]
AlreadyExistsError(String),
/// Example: "User with id X"
#[error("{0} Not found.")]
NotFoundError(String),
/// Used to specify authentication error.
/// Example: Password incorrect for user
#[error("Credential supplied is incorrect. {0}")]
IncorrectCredentialError(String),
#[error("Too many credentials supplied, maximum is 3.")]
TooManyCredentialsError,
/// Used for anything else.
#[error("Unexpected Error: {0}")]
UnexpectedError(String),
}
/// Any string validation error such as Phone number validation or email, etc...
/// Reason should be a Key for internationalization
#[derive(Error, Debug)]
#[error("Error validating `{what}`. Reason: {reason}")]
pub struct ValidationError {
pub what: String,
pub reason: String,
}

View File

@ -1,5 +1,6 @@
use std::{fmt::Display, str::FromStr}; use std::{fmt::Display, str::FromStr};
use err::{Error, trace};
use sqlx::{ use sqlx::{
encode::IsNull, encode::IsNull,
error::BoxDynError, error::BoxDynError,
@ -8,8 +9,6 @@ use sqlx::{
}; };
use crate::domain::credential::CredentialType; use crate::domain::credential::CredentialType;
use crate::domain::error::Error;
use crate::domain::error::Error::FromStrError;
impl FromStr for CredentialType { impl FromStr for CredentialType {
type Err = Error; type Err = Error;
@ -19,7 +18,7 @@ impl FromStr for CredentialType {
"PhoneNumber" => Ok(Self::PhoneNumber), "PhoneNumber" => Ok(Self::PhoneNumber),
"Email" => Ok(Self::Email), "Email" => Ok(Self::Email),
"Username" => Ok(Self::Username), "Username" => Ok(Self::Username),
_ => Err(FromStrError), _ => Err(Error::new(trace!()).error_type(err::ErrorType::Parser).message(s)),
} }
} }
} }

View File

@ -1,5 +1,4 @@
pub mod credential; pub mod credential;
pub mod error;
pub mod impls; pub mod impls;
pub mod token; pub mod token;
pub mod user; pub mod user;

View File

@ -7,17 +7,13 @@ use crate::domain::user::User;
use crate::dto::token::{AuthenticateUserDto, RefreshAuthTokenForUserDto}; use crate::dto::token::{AuthenticateUserDto, RefreshAuthTokenForUserDto};
use crate::dto::users::{UserLoginPayload, UserRegisterPayload, UserResetPasswordPayload}; use crate::dto::users::{UserLoginPayload, UserRegisterPayload, UserResetPasswordPayload};
use crate::domain::error::Error::{
AlreadyExistsError, IncorrectCredentialError, NotFoundError, TooManyCredentialsError,
UnexpectedError,
};
use crate::domain::error::{Error, ValidationError};
use crate::resources::expirations::AUTH_TOKEN_EXPIRATION_TIME_MILLIS; use crate::resources::expirations::AUTH_TOKEN_EXPIRATION_TIME_MILLIS;
use crate::utils::hasher::{ use crate::utils::hasher::{
generate_multiple_random_token_with_rng, hash_password, hash_password_with_existing_salt, generate_multiple_random_token_with_rng, hash_password, hash_password_with_existing_salt,
}; };
use crate::validation::user_validator::validate_user_for_creation; use crate::validation::user_validator::validate_user_for_creation;
use chrono::Utc; use chrono::Utc;
use err::{trace, Error, ErrorType, ServiceError, ValidationError, x_u_res_db_or_res, u_res_or_res};
use log::error; use log::error;
use sqlx::{PgConnection, Postgres, Transaction}; use sqlx::{PgConnection, Postgres, Transaction};
@ -30,13 +26,15 @@ pub async fn register_user<'a>(
validate_user_for_creation(&user, &mut validation_errors); validate_user_for_creation(&user, &mut validation_errors);
// Find if user exists // Find if user exists
if user.credentials.len() > 3 { if user.credentials.len() > 3 {
return Err(TooManyCredentialsError); return Err(Error::new(trace!()));
} }
for credential_dto in user.credentials.iter() { for credential_dto in user.credentials.iter() {
match get_credential(transaction, credential_dto.credential.clone()).await? { match x_u_res_db_or_res!(get_credential(transaction, credential_dto.credential.clone()).await) {
None => {} None => {}
Some(_) => { Some(_) => {
return Err(AlreadyExistsError(String::from("Credential"))); return Err(Error::new(trace!()).error_type(ErrorType::Service(
ServiceError::AlreadyExistsError(String::from("Credential")),
)));
} }
}; };
} }
@ -52,11 +50,11 @@ pub async fn register_user<'a>(
last_updated: now, last_updated: now,
}; };
let persisted_user = insert_user(transaction, user_to_insert).await?; let persisted_user = x_u_res_db_or_res!(insert_user(transaction, user_to_insert).await);
// Insert Credentials // Insert Credentials
for credential in user.credentials { for credential in user.credentials {
insert_credential(transaction, credential, &persisted_user.id).await?; x_u_res_db_or_res!(insert_credential(transaction, credential, &persisted_user.id).await);
} }
create_token_for_user(transaction, persisted_user.id).await create_token_for_user(transaction, persisted_user.id).await
@ -65,23 +63,23 @@ pub async fn authenticate_user<'a>(
conn: &mut PgConnection, conn: &mut PgConnection,
user: AuthenticateUserDto, user: AuthenticateUserDto,
) -> Result<User, Error> { ) -> Result<User, Error> {
let persisted_user = match get_user_with_id(conn, &user.id).await? { let persisted_user = match x_u_res_db_or_res!(get_user_with_id(conn, &user.id).await) {
None => { None => {
return Err(NotFoundError(format!("User with id: {}", user.id))); return Err(Error::new(trace!()).error_type(ErrorType::Service(ServiceError::NotFoundError(format!("User with id: {}", user.id)))));
} }
Some(persisted_user) => persisted_user, Some(persisted_user) => persisted_user,
}; };
match validate_user_token(conn, &user.id, user.auth_token.clone()).await? { match x_u_res_db_or_res!(validate_user_token(conn, &user.id, user.auth_token.clone()).await) {
None => Err(NotFoundError(format!("Auth Token {}", user.auth_token))), None => return Err(Error::new(trace!()).error_type(ErrorType::Service(ServiceError::NotFoundError(format!("Auth Token {}", user.auth_token))))),
Some(persisted_token) => { Some(persisted_token) => {
// Check if persisted_token expired // Check if persisted_token expired
if Utc::now().timestamp_millis() - persisted_token.last_updated.timestamp_millis() if Utc::now().timestamp_millis() - persisted_token.last_updated.timestamp_millis()
> AUTH_TOKEN_EXPIRATION_TIME_MILLIS > AUTH_TOKEN_EXPIRATION_TIME_MILLIS
{ {
Err(IncorrectCredentialError(String::from( Err(Error::new(trace!()).error_type(ErrorType::Service(ServiceError::IncorrectCredentialError(String::from(
"Auth Token Expired. Use refresh token to get a new one.", "Auth Token Expired. Use refresh token to get a new one.",
))) )))))
} else { } else {
// Not expired // Not expired
Ok(persisted_user) Ok(persisted_user)
@ -95,9 +93,9 @@ pub async fn refresh_auth_token<'a>(
conn: &mut PgConnection, conn: &mut PgConnection,
user: RefreshAuthTokenForUserDto, user: RefreshAuthTokenForUserDto,
) -> Result<Token, Error> { ) -> Result<Token, Error> {
let _persisted_user = match get_user_with_id(conn, &user.id).await? { let _persisted_user = match x_u_res_db_or_res!(get_user_with_id(conn, &user.id).await) {
None => { None => {
return Err(NotFoundError(format!("User with id: {}", user.id))); return Err(Error::new(trace!()).error_type(ErrorType::Service(ServiceError::NotFoundError(format!("User with id: {}", user.id)))));
} }
Some(persisted_user) => persisted_user, Some(persisted_user) => persisted_user,
}; };
@ -106,9 +104,9 @@ pub async fn refresh_auth_token<'a>(
if tokens.len() > 0 { if tokens.len() > 0 {
let new_auth_token = tokens.remove(0); let new_auth_token = tokens.remove(0);
Ok(update_token(conn, user.refresh_token, new_auth_token).await?) Ok(x_u_res_db_or_res!(update_token(conn, user.refresh_token, new_auth_token).await))
} else { } else {
Err(UnexpectedError(String::from("No tokens were created."))) Err(Error::new(trace!()).error_type(ErrorType::Service(ServiceError::UnexpectedError(String::from("No tokens were created.")))))
} }
} }
@ -117,15 +115,15 @@ pub async fn reset_password(
conn: &mut PgConnection, conn: &mut PgConnection,
user: UserResetPasswordPayload, user: UserResetPasswordPayload,
) -> Result<User, Error> { ) -> Result<User, Error> {
let password_matches = validate_user_password(conn, &user.id, user.password).await?; let password_matches = u_res_or_res!(validate_user_password(conn, &user.id, user.password).await);
if let Some(persisted_user) = password_matches { if let Some(persisted_user) = password_matches {
// Change pass // Change pass
Ok(change_password(conn, persisted_user, &user.new_password).await?) Ok(u_res_or_res!(change_password(conn, persisted_user, &user.new_password).await))
} else { } else {
Err(IncorrectCredentialError(String::from( Err(Error::new(trace!()).error_type(ErrorType::Service(ServiceError::IncorrectCredentialError(String::from(
"Password incorrect.", "Password incorrect.",
))) )))))
} }
} }
@ -136,14 +134,14 @@ pub async fn force_reset_password<'a>(
user_id: &i32, user_id: &i32,
new_password: String, new_password: String,
) -> Result<User, Error> { ) -> Result<User, Error> {
let persisted_user = match get_user_with_id(conn, user_id).await? { let persisted_user = match x_u_res_db_or_res!(get_user_with_id(conn, user_id).await) {
None => { None => {
error!("Serious error. User doesn't exist but credentials pointing to the user do."); error!("Serious error. User doesn't exist but credentials pointing to the user do.");
return Err(Error::NotFoundError(format!("User with id: {user_id}"))); return Err(Error::new(trace!()).error_type(ErrorType::Service(ServiceError::NotFoundError(format!("User with id: {user_id}")))));
} }
Some(persisted_user) => persisted_user, Some(persisted_user) => persisted_user,
}; };
Ok(change_password(conn, persisted_user, &new_password).await?) Ok(u_res_or_res!(change_password(conn, persisted_user, &new_password).await))
} }
/// ///
@ -151,19 +149,19 @@ pub async fn password_login<'a>(
conn: &mut Transaction<'a, Postgres>, conn: &mut Transaction<'a, Postgres>,
user: UserLoginPayload, user: UserLoginPayload,
) -> Result<Token, Error> { ) -> Result<Token, Error> {
let persisted_user_credential = match get_credential(conn, user.credential.clone()).await? { let persisted_user_credential = match x_u_res_db_or_res!(get_credential(conn, user.credential.clone()).await) {
None => return Err(NotFoundError(format!("Credential {}", user.credential))), None => return Err(Error::new(trace!()).error_type(ErrorType::Service(ServiceError::NotFoundError(format!("Credential {}", user.credential))))),
Some(persisted_credential) => persisted_credential, Some(persisted_credential) => persisted_credential,
}; };
let persisted_user_opt = let persisted_user_opt =
validate_user_password(conn, &persisted_user_credential.user_id, user.password).await?; u_res_or_res!(validate_user_password(conn, &persisted_user_credential.user_id, user.password).await);
if let Some(_) = persisted_user_opt { if let Some(_) = persisted_user_opt {
Ok(create_token_for_user(conn, persisted_user_credential.user_id).await?) Ok(u_res_or_res!(create_token_for_user(conn, persisted_user_credential.user_id).await))
} else { } else {
Err(NotFoundError(format!( Err(Error::new(trace!()).error_type(ErrorType::Service(ServiceError::NotFoundError(format!(
"User with id: {}", "User with id: {}",
persisted_user_credential.user_id persisted_user_credential.user_id
))) )))))
} }
} }
@ -172,8 +170,8 @@ pub async fn get_user_credentials<'a>(
transaction: &mut Transaction<'a, Postgres>, transaction: &mut Transaction<'a, Postgres>,
user: AuthenticateUserDto, user: AuthenticateUserDto,
) -> Result<Vec<Credential>, Error> { ) -> Result<Vec<Credential>, Error> {
let persisted_user = authenticate_user(transaction, user).await?; let persisted_user = u_res_or_res!(authenticate_user(transaction, user).await);
Ok(fetch_user_credentials(transaction, &persisted_user.id).await?) Ok(x_u_res_db_or_res!(fetch_user_credentials(transaction, &persisted_user.id).await))
} }
async fn create_token_for_user<'a>( async fn create_token_for_user<'a>(
@ -188,18 +186,18 @@ async fn create_token_for_user<'a>(
auth_token: match tokens.get(0) { auth_token: match tokens.get(0) {
None => { None => {
error!("Tokens were not created."); error!("Tokens were not created.");
return Err(Error::UnexpectedError(String::from( return Err(Error::new(trace!()).error_type(ErrorType::Service(ServiceError::UnexpectedError(String::from(
"Tokens were not created.", "Tokens were not created.",
))); )))));
} }
Some(token) => token.clone(), Some(token) => token.clone(),
}, },
refresh_token: match tokens.get(1) { refresh_token: match tokens.get(1) {
None => { None => {
error!("Tokens were not created."); error!("Tokens were not created.");
return Err(Error::UnexpectedError(String::from( return Err(Error::new(trace!()).error_type(ErrorType::Service(ServiceError::UnexpectedError(String::from(
"Tokens were not created.", "Tokens were not created.",
))); )))));
} }
Some(token) => token.clone(), Some(token) => token.clone(),
}, },
@ -208,7 +206,7 @@ async fn create_token_for_user<'a>(
}; };
// Insert token in DB // Insert token in DB
Ok(insert_token(transaction, token_to_insert).await?) Ok(x_u_res_db_or_res!(insert_token(transaction, token_to_insert).await))
} }
async fn validate_user_password<'a>( async fn validate_user_password<'a>(
@ -216,10 +214,10 @@ async fn validate_user_password<'a>(
user_id: &i32, user_id: &i32,
password: String, password: String,
) -> Result<Option<User>, Error> { ) -> Result<Option<User>, Error> {
let persisted_user = match get_user_with_id(conn, user_id).await? { let persisted_user = match x_u_res_db_or_res!(get_user_with_id(conn, user_id).await) {
None => { None => {
error!("Serious error. User doesn't exist but credentials pointing to the user do."); error!("Serious error. User doesn't exist but credentials pointing to the user do.");
return Err(Error::NotFoundError(format!("User with id: {user_id}"))); return Err(Error::new(trace!()).error_type(ErrorType::Service(ServiceError::NotFoundError(format!("User with id: {user_id}")))));
} }
Some(persisted_user) => persisted_user, Some(persisted_user) => persisted_user,
}; };
@ -240,5 +238,5 @@ async fn change_password<'a>(
let hash_result = hash_password(&new_password); let hash_result = hash_password(&new_password);
persisted_user.password = hash_result.hash; persisted_user.password = hash_result.hash;
persisted_user.salt = hash_result.salt; persisted_user.salt = hash_result.salt;
Ok(update_user(conn, persisted_user).await?) Ok(x_u_res_db_or_res!(update_user(conn, persisted_user).await))
} }

View File

@ -1,5 +1,6 @@
use err::ValidationError;
use crate::domain::credential::CredentialType; use crate::domain::credential::CredentialType;
use crate::domain::error::ValidationError;
use crate::dto::users::{UserLoginPayload, UserRegisterPayload}; use crate::dto::users::{UserLoginPayload, UserRegisterPayload};
use crate::resources::error_messages::ERROR_INVALID_USERNAME; use crate::resources::error_messages::ERROR_INVALID_USERNAME;
use crate::resources::{ use crate::resources::{