feat: Enhance permissions and roles management with improved indexing and state handling

This commit is contained in:
MythEclipse
2025-08-14 23:11:44 +07:00
parent 199d0c885e
commit 10dc869eaf
10 changed files with 134 additions and 171 deletions
+2 -1
View File
@@ -139,7 +139,8 @@ pub async fn post_new_password(
tag = "Authentication"
)]
pub async fn post_refresh_token(
Extension(state): Extension<AppState>,
Json(payload): Json<AuthRefreshTokenRequestDto>,
) -> impl IntoResponse {
AuthService::mutation_refresh_token(payload).await
AuthService::mutation_refresh_token(payload, &state).await
}
+34 -40
View File
@@ -9,7 +9,7 @@ use crate::{
decode_refresh_token, encode_access_token, encode_refresh_token,
encode_reset_password_token, extract_email_token, generate_otp, get_iso_date,
hash_password, make_thing, send_email, success_response, validate_request,
verify_password, surrealdb_init_ws, surrealdb_init_mem,
verify_password,
};
use axum::{http::StatusCode, response::Response};
use surrealdb::Uuid;
@@ -37,6 +37,7 @@ pub trait AuthServiceTrait: Send + Sync + 'static {
) -> Response;
async fn mutation_refresh_token(
payload: AuthRefreshTokenRequestDto,
state: &AppState,
) -> Response;
async fn mutation_forgot_password(
payload: AuthResendOtpRequestDto,
@@ -88,7 +89,7 @@ impl AuthServiceTrait for AuthService {
}
let permissions: Vec<String> = user.role.permissions.iter().map(|p| p.name.clone()).collect();
let access_token = match encode_access_token(payload.email.clone(), user.id.id.to_raw(), permissions) {
let access_token = match encode_access_token(payload.email.clone(), user.id.id.to_raw(), permissions.clone()) {
Ok(token) => token,
Err(_e) => {
error!(
@@ -185,7 +186,7 @@ let refresh_token = match encode_refresh_token(payload.email.clone(), user.id.id
}
let permissions: Vec<String> = user.role.permissions.iter().map(|p| p.name.clone()).collect();
let access_token = match encode_access_token(payload.email.clone(), user.id.id.to_raw(), permissions) {
let access_token = match encode_access_token(payload.email.clone(), user.id.id.to_raw(), permissions.clone()) {
Ok(token) => token,
Err(_e) => {
error!(
@@ -328,7 +329,7 @@ let refresh_token = match encode_refresh_token(payload.email.clone(), user.id.id
created_at: get_iso_date(),
updated_at: get_iso_date(),
role: role_thing,
is_active: true,
is_active: false,
..Default::default()
})
.await
@@ -378,27 +379,15 @@ let refresh_token = match encode_refresh_token(payload.email.clone(), user.id.id
}
}
async fn mutation_refresh_token(payload: AuthRefreshTokenRequestDto) -> Response {
async fn mutation_refresh_token(
payload: AuthRefreshTokenRequestDto,
state: &AppState,
) -> Response {
if let Err((status, message)) = validate_request(&payload) {
return common_response(status, &message);
}
let surrealdb_ws = match surrealdb_init_ws().await {
Ok(db) => db,
Err(e) => {
error!("Failed to initialize websocket database: {}", e);
return common_response(StatusCode::INTERNAL_SERVER_ERROR, "Database initialization error");
}
};
let surrealdb_mem = match surrealdb_init_mem().await {
Ok(db) => db,
Err(e) => {
error!("Failed to initialize memory database: {}", e);
return common_response(StatusCode::INTERNAL_SERVER_ERROR, "Database initialization error");
}
};
let state = AppState { surrealdb_ws, surrealdb_mem };
let user_repo = UsersRepository::new(&state);
let user_repo = UsersRepository::new(state);
let user = match decode_refresh_token(&payload.refresh_token) {
Ok(token_data) => {
match user_repo.query_user_by_email(token_data.claims.sub.clone()).await {
@@ -508,37 +497,42 @@ let token = match encode_reset_password_token(user.email.clone(), user.id.id.to_
let auth_repo = AuthRepository::new(state);
let email = payload.email.clone();
let user = match user_repo.query_user_by_email(email.clone()).await {
Ok(user) if !user.is_deleted => user,
Ok(user) => user,
_ => {
return common_response(StatusCode::NOT_FOUND, "User not found");
}
};
if user.is_active {
return common_response(StatusCode::BAD_REQUEST, "User already active");
}
let patch = UsersSchema {
id: user.id.clone(),
is_active: true,
..UsersSchema::from(user.clone())
};
match auth_repo.query_get_stored_otp(email.clone()).await {
Ok(stored_otp) => match stored_otp == payload.otp {
true => match user_repo.query_update_user(patch).await {
Ok(_) => match auth_repo.query_delete_stored_otp(email.clone()).await {
Ok(_) => common_response(StatusCode::OK, "Email verified successfully"),
Err(e_del) => {
error!("Failed to delete OTP for {}: {}", email, e_del);
common_response(StatusCode::INTERNAL_SERVER_ERROR, &e_del.to_string())
Ok(stored_otp) => {
if stored_otp != payload.otp {
// Delete OTP even if it doesn't match
let _ = auth_repo.query_delete_stored_otp(email.clone()).await;
return common_response(StatusCode::BAD_REQUEST, "Failed to verify OTP");
}
match user_repo.query_update_user(patch).await {
Ok(_) => {
match auth_repo.query_delete_stored_otp(email.clone()).await {
Ok(_) => common_response(StatusCode::OK, "Email verified successfully"),
Err(e_del) => {
error!("Failed to delete OTP for {}: {}", email, e_del);
common_response(StatusCode::INTERNAL_SERVER_ERROR, &e_del.to_string())
}
}
},
Err(err_update) => {
common_response(StatusCode::BAD_REQUEST, &err_update.to_string())
}
},
false => match auth_repo.query_delete_stored_otp(email.clone()).await {
Ok(_) => common_response(StatusCode::BAD_REQUEST, "Failed to verify OTP"),
Err(e_del_mismatch) => common_response(
StatusCode::INTERNAL_SERVER_ERROR,
&format!("Failed to delete OTP: {e_del_mismatch}"),
),
},
Err(err_update) => common_response(StatusCode::BAD_REQUEST, &err_update.to_string()),
}
},
Err(err_get) => common_response(StatusCode::BAD_REQUEST, &err_get.to_string()),
}
@@ -78,12 +78,21 @@ impl AuthRequest {
}
}
use crate::{RolesRepository, RolesEnum};
/// Helper function to get default role ID for new OAuth users
async fn get_default_role_id(_env: &Env) -> Result<String, Error> {
// Use the User role ID from the seed data directly
let default_role_id = "5713cb37-dc02-4e87-8048-d7a41d352059".to_string();
info!("Using default User role ID from seed: {}", default_role_id);
Ok(default_role_id)
async fn get_default_role_id(app_state: &AppState) -> Result<String, Error> {
let role_repo = RolesRepository::new(app_state);
match role_repo.query_role_by_name(RolesEnum::User.to_string()).await {
Ok(role) => {
info!("Using default User role ID: {}", role.id);
Ok(role.id)
},
Err(e) => {
error!("Failed to retrieve User role: {:?}", e);
Err(Error::Anyhow(anyhow::Error::msg("Failed to get default role ID".to_string())))
}
}
}
#[async_trait]
@@ -253,7 +262,7 @@ where
info!("Google user data: name={:?}, given_name={:?}, family_name={:?}, picture={:?}",
google_user.name, google_user.given_name, google_user.family_name, google_user.picture);
let user = self.users_service.get_user_by_email(&google_user.email).await?;
let user = self.users_service.get_user_by_email(&google_user.email, app_state).await?;
let user = match user {
Some(mut user) => {
@@ -262,7 +271,7 @@ where
// Update avatar if user doesn't have one and Google provides one
if user.avatar.is_none() && google_user.picture.is_some() {
info!("Updating avatar for existing user: {}", google_user.email);
match self.users_service.update_user_avatar(&google_user.email, google_user.picture.clone()).await {
match self.users_service.update_user_avatar(&google_user.email, google_user.picture.clone(), app_state).await {
Ok(_) => {
info!("Successfully updated avatar for user: {}", google_user.email);
user.avatar = google_user.picture.clone();
@@ -279,11 +288,11 @@ where
info!("Creating new user for email: {}", google_user.email);
// Get default role ID using robust lookup
let default_role_id = get_default_role_id(self.env).await
.unwrap_or_else(|e| {
error!("Failed to get default role ID, using fallback: {:?}", e);
"5713cb37-dc02-4e87-8048-d7a41d352059".to_string() // Hardcoded User role ID as final fallback
});
let default_role_id = get_default_role_id(app_state).await
.map_err(|e| {
error!("Failed to get default role ID: {:?}", e);
Error::Anyhow(anyhow::Error::msg("Failed to get default role ID for new user".to_string()))
})?;
let new_user = UsersCreateRequestDto {
email: google_user.email.clone(),
@@ -306,12 +315,12 @@ where
avatar: google_user.picture.clone(), // Set avatar from Google user picture
};
self.users_service.create_user_by_dto(new_user).await?
self.users_service.create_user_by_dto(new_user, app_state).await?
}
};
let permissions: Vec<String> = user.role.permissions.iter().map(|p| p.name.clone()).collect();
let access_token = encode_access_token(user.email.clone(), user.id.clone(), permissions)
let access_token = encode_access_token(user.email.clone(), user.id.clone(), permissions.clone())
.map_err(|e| {
error!("Failed to generate access token for {}: {:?}", user.email, e);
Error::Auth("Failed to generate access token".to_string())