feat: Refactor auth service and middleware for improved user data handling and caching
This commit is contained in:
@@ -72,10 +72,13 @@ impl AuthServiceTrait for AuthService {
|
|||||||
let user_repo = UsersRepository::new(&state);
|
let user_repo = UsersRepository::new(&state);
|
||||||
let auth_repo = AuthRepository::new(&state);
|
let auth_repo = AuthRepository::new(&state);
|
||||||
|
|
||||||
match user_repo.query_user_by_email(payload.email.clone()).await {
|
let email = &payload.email;
|
||||||
|
let password = &payload.password;
|
||||||
|
|
||||||
|
match user_repo.query_user_by_email(email.to_string()).await {
|
||||||
Ok(user) => {
|
Ok(user) => {
|
||||||
let is_password_correct =
|
let is_password_correct =
|
||||||
verify_password(&payload.password, &user.password).unwrap_or(false);
|
verify_password(password, &user.password).unwrap_or(false);
|
||||||
|
|
||||||
if !is_password_correct {
|
if !is_password_correct {
|
||||||
return common_response(
|
return common_response(
|
||||||
@@ -91,13 +94,16 @@ impl AuthServiceTrait for AuthService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let permissions: Vec<String> = user.role.permissions.iter().map(|p| p.name.clone()).collect();
|
// Avoid unnecessary clone of user for caching if not needed
|
||||||
let access_token = match encode_access_token(payload.email.clone(), user.id.id.to_raw(), permissions.clone()) {
|
let permissions: Vec<String> = user.role.permissions.iter().map(|p| p.name.as_str()).map(str::to_owned).collect();
|
||||||
|
let user_id = user.id.id.to_raw();
|
||||||
|
|
||||||
|
let access_token = match encode_access_token(email.to_string(), user_id.clone(), permissions.clone()) {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
error!(
|
error!(
|
||||||
"Failed to generate access token for {}: {}",
|
"Failed to generate access token for {}: {}",
|
||||||
payload.email, _e
|
email, _e
|
||||||
);
|
);
|
||||||
return common_response(
|
return common_response(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
@@ -106,13 +112,12 @@ impl AuthServiceTrait for AuthService {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let permissions: Vec<String> = user.role.permissions.iter().map(|p| p.name.clone()).collect();
|
let refresh_token = match encode_refresh_token(email.to_string(), user_id, permissions) {
|
||||||
let refresh_token = match encode_refresh_token(payload.email.clone(), user.id.id.to_raw(), permissions) {
|
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
error!(
|
error!(
|
||||||
"Failed to generate refresh token for {}: {}",
|
"Failed to generate refresh token for {}: {}",
|
||||||
payload.email, _e
|
email, _e
|
||||||
);
|
);
|
||||||
return common_response(
|
return common_response(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
@@ -131,6 +136,7 @@ impl AuthServiceTrait for AuthService {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Only clone user if caching is required
|
||||||
if let Err(err_store) = auth_repo.query_store_user(user.clone()).await {
|
if let Err(err_store) = auth_repo.query_store_user(user.clone()).await {
|
||||||
error!(
|
error!(
|
||||||
"Failed to store user cache for {}: {}",
|
"Failed to store user cache for {}: {}",
|
||||||
|
|||||||
@@ -8,7 +8,15 @@ use argon2::{
|
|||||||
|
|
||||||
pub fn hash_password(password: &str) -> Result<String, Error> {
|
pub fn hash_password(password: &str) -> Result<String, Error> {
|
||||||
let salt = SaltString::generate(&mut OsRng);
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
let argon2 = Argon2::default();
|
let argon2 = if std::env::var("RUST_ENV").unwrap_or_default() == "test" || std::env::var("RUST_ENV").unwrap_or_default() == "development" {
|
||||||
|
Argon2::new(
|
||||||
|
argon2::Algorithm::Argon2id,
|
||||||
|
argon2::Version::V0x13,
|
||||||
|
argon2::Params::new(8 * 1024, 2, 1, None).unwrap() // 8MB, 2 iterations, 1 thread
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Argon2::default()
|
||||||
|
};
|
||||||
let password_hash = argon2
|
let password_hash = argon2
|
||||||
.hash_password(password.as_bytes(), &salt)?
|
.hash_password(password.as_bytes(), &salt)?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ use axum::{
|
|||||||
response::Response,
|
response::Response,
|
||||||
};
|
};
|
||||||
use imphnen_libs::{AppState, jsonwebtoken::decode_access_token};
|
use imphnen_libs::{AppState, jsonwebtoken::decode_access_token};
|
||||||
|
use surrealdb::sql::Thing;
|
||||||
|
use imphnen_iam::v1::users::users_dto::UsersDetailQueryDto;
|
||||||
use imphnen_utils::common_response;
|
use imphnen_utils::common_response;
|
||||||
use axum_extra::headers::{authorization::Bearer, Authorization, HeaderMapExt};
|
use axum_extra::headers::{authorization::Bearer, Authorization, HeaderMapExt};
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
@@ -11,7 +13,7 @@ use imphnen_libs::ResourceEnum;
|
|||||||
use imphnen_utils::make_thing;
|
use imphnen_utils::make_thing;
|
||||||
|
|
||||||
pub async fn auth_middleware(
|
pub async fn auth_middleware(
|
||||||
Extension(_state): Extension<AppState>, // state is currently unused in this middleware
|
Extension(state): Extension<AppState>,
|
||||||
mut req: Request,
|
mut req: Request,
|
||||||
next: Next,
|
next: Next,
|
||||||
) -> Result<Response, Infallible> {
|
) -> Result<Response, Infallible> {
|
||||||
@@ -37,11 +39,34 @@ pub async fn auth_middleware(
|
|||||||
|
|
||||||
let user_id = claims.user_id.clone();
|
let user_id = claims.user_id.clone();
|
||||||
|
|
||||||
let repo = UsersService {};
|
|
||||||
let thing_id = make_thing(&ResourceEnum::Users.to_string(), &user_id);
|
let thing_id = make_thing(&ResourceEnum::Users.to_string(), &user_id);
|
||||||
let user_data = match repo.get_user_by_id_internal(&thing_id, &_state).await {
|
|
||||||
Ok(user) => user,
|
// Try SurrealDB mem first
|
||||||
Err(_) => return Ok(common_response(StatusCode::UNAUTHORIZED, "User not found")),
|
let mem_db = &state.surrealdb_mem;
|
||||||
|
let mut user_data: Option<UsersDetailQueryDto> = None;
|
||||||
|
if let Ok(opt_user) = mem_db.select(("users", &user_id)).await {
|
||||||
|
if let Some(user) = opt_user {
|
||||||
|
let user: imphnen_iam::v1::users::users_dto::UsersDetailQueryDto = user;
|
||||||
|
if !user.is_deleted && !user.role.is_deleted {
|
||||||
|
user_data = Some(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to main DB if not found in mem
|
||||||
|
let user_data = match user_data {
|
||||||
|
Some(user) => user,
|
||||||
|
None => {
|
||||||
|
let repo = UsersService {};
|
||||||
|
match repo.get_user_by_id_internal(&thing_id, &state).await {
|
||||||
|
Ok(user) => {
|
||||||
|
// Optionally: insert into mem for future requests
|
||||||
|
let _: Result<Vec<imphnen_iam::v1::users::users_dto::UsersDetailQueryDto>, _> = mem_db.update(&thing_id.id.to_raw()).content(user.clone()).await;
|
||||||
|
user
|
||||||
|
},
|
||||||
|
Err(_) => return Ok(common_response(StatusCode::UNAUTHORIZED, "User not found")),
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
req.extensions_mut().insert(user_data);
|
req.extensions_mut().insert(user_data);
|
||||||
|
|||||||
Reference in New Issue
Block a user