feat(auth): Enhance Google OAuth flow with async email extraction and caching

This commit is contained in:
MythEclipse
2025-08-12 19:25:10 +07:00
parent 44b1e09551
commit b40a430c49
11 changed files with 204 additions and 34 deletions
@@ -2,7 +2,7 @@ use axum::{
extract::{Query, State},
response::Redirect,
routing::get,
Json, Router,
Json, Router, Extension,
};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
@@ -12,6 +12,7 @@ use imphnen_libs::enviroment::ENV; // Import ENV
use crate::v1::auth::google::google_oauth_service::{AuthRequest, GoogleOauthService, GoogleOauthServiceImpl};
use imphnen_entities::error_dto::error::Error;
use crate::v1::auth::AuthLoginResponsetDto;
use crate::AppState;
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct GoogleAuthUrlResponse {
@@ -56,9 +57,9 @@ where
.route(
"/callback",
get(
move |State(controller): State<Arc<Self>>, Query(auth_request): Query<AuthRequest>| async move {
move |State(controller): State<Arc<Self>>, Extension(app_state): Extension<AppState>, Query(auth_request): Query<AuthRequest>| async move {
let controller = Arc::clone(&controller);
controller.google_oauth_callback(auth_request).await
controller.google_oauth_callback(auth_request, &app_state).await
},
),
)
@@ -70,8 +71,8 @@ where
Ok(Redirect::to(authorize_url.as_str()))
}
pub async fn google_oauth_callback(&self, auth_request: AuthRequest) -> Result<Json<AuthLoginResponsetDto>, Error> {
let (user, token) = self.google_oauth_service.google_oauth_callback(auth_request).await?;
pub async fn google_oauth_callback(&self, auth_request: AuthRequest, app_state: &AppState) -> Result<Json<AuthLoginResponsetDto>, Error> {
let (user, token) = self.google_oauth_service.google_oauth_callback(auth_request, app_state).await?;
let auth_response = AuthLoginResponsetDto {
user,
token,
@@ -9,7 +9,7 @@ use oauth2::url::Url;
use tracing::{info, error};
use imphnen_entities::error_dto::error::Error;
use imphnen_libs::{jsonwebtoken::{encode_access_token, encode_refresh_token}, enviroment::Env};
use imphnen_libs::{jsonwebtoken::{encode_access_token, encode_refresh_token}, enviroment::Env, AppState};
use imphnen_utils::{generate_oauth_csrf_token, validate_oauth_csrf_token, validate_csrf_token};
use crate::v1::auth::TokenDto;
use crate::v1::auth::auth_service::AuthServiceTrait;
@@ -91,7 +91,7 @@ pub trait GoogleOauthService<A: AuthServiceTrait + Send + Sync + 'static, U: Use
fn with_services(auth_service: A, users_service: U, env: &'static Env) -> Self;
fn google_oauth_client(&self) -> BasicClient;
fn generate_auth_url(&self) -> (Url, CsrfToken);
async fn google_oauth_callback(&self, auth_request: AuthRequest) -> Result<(UsersDetailItemDto, TokenDto), Error>; // Changed return type
async fn google_oauth_callback(&self, auth_request: AuthRequest, app_state: &AppState) -> Result<(UsersDetailItemDto, TokenDto), Error>; // Changed return type
}
#[derive(Clone)]
@@ -158,7 +158,7 @@ where
.url()
}
async fn google_oauth_callback(&self, auth_request: AuthRequest) -> Result<(UsersDetailItemDto, TokenDto), Error> {
async fn google_oauth_callback(&self, auth_request: AuthRequest, app_state: &AppState) -> Result<(UsersDetailItemDto, TokenDto), Error> {
// Validate input parameters first
auth_request.validate()?;
@@ -280,6 +280,20 @@ where
refresh_token,
};
// Cache the user in auth repository for subsequent requests
let auth_repo = crate::v1::auth::AuthRepository::new(app_state);
let user_query_dto: crate::v1::users::users_dto::UsersDetailQueryDto = (&user).into();
if let Err(err_store) = auth_repo.query_store_user(user_query_dto).await {
error!(
"Failed to store user cache for {}: {}",
user.email, err_store
);
// Don't fail the login, just log the error
error!("Google OAuth login succeeded but caching failed for user: {}", user.email);
} else {
info!("Successfully cached user {} after Google OAuth login", user.email);
}
info!("Successfully completed Google OAuth for user: {}", user.email);
Ok((user, token_dto))
}
@@ -1,5 +1,5 @@
use super::PermissionsEnum;
use crate::{AppState, AuthRepository, common_response, extract_email};
use crate::{AppState, AuthRepository, common_response, extract_email, extract_email_async};
use axum::{
http::{HeaderMap, StatusCode},
response::Response,
@@ -11,12 +11,24 @@ pub async fn permissions_guard(
required_permissions: Vec<PermissionsEnum>,
) -> Result<(), Response> {
let auth_repo = AuthRepository::new(&state);
let email = extract_email(headers).ok_or_else(|| {
common_response(
StatusCode::UNAUTHORIZED,
"Invalid or missing authorization token",
)
})?;
// Try synchronous email extraction first (for internal JWT tokens)
let email = match extract_email(headers) {
Some(email) => email,
None => {
// If sync extraction fails, try async (for Google tokens)
match extract_email_async(headers).await {
Some(email) => email,
None => {
return Err(common_response(
StatusCode::UNAUTHORIZED,
"Invalid or missing authorization token",
));
}
}
}
};
let raw_user = auth_repo
.query_get_stored_user(email.clone())
.await
+21 -3
View File
@@ -6,7 +6,7 @@ use crate::{
AppState, MetaRequestDto, ResponseListSuccessDto, UsersRepository, UsersSchema,
};
use crate::{
ResponseSuccessDto, common_response, extract_email, success_list_response,
ResponseSuccessDto, common_response, extract_email, extract_email_async, success_list_response,
success_response, validate_request,
};
use axum::http::HeaderMap;
@@ -73,10 +73,19 @@ impl UsersServiceTrait for UsersService {
async fn get_user_me(headers: HeaderMap, state: &AppState) -> Response {
let repo = UsersRepository::new(state);
// Try synchronous email extraction first (for internal JWT tokens)
let email = match extract_email(&headers) {
Some(email) => email,
None => return common_response(StatusCode::UNAUTHORIZED, "Invalid token"),
None => {
// If sync extraction fails, try async (for Google tokens)
match extract_email_async(&headers).await {
Some(email) => email,
None => return common_response(StatusCode::UNAUTHORIZED, "Invalid token"),
}
}
};
match repo.query_user_by_email(email).await {
Ok(user) if !user.is_deleted => success_response(ResponseSuccessDto {
data: UserDto::from(&user), // Corrected to use UserDto::from by reference
@@ -134,10 +143,19 @@ impl UsersServiceTrait for UsersService {
user: UsersUpdateRequestDto,
) -> Response {
let repo = UsersRepository::new(state);
// Try synchronous email extraction first (for internal JWT tokens)
let email = match extract_email(&headers) {
Some(email) => email,
None => return common_response(StatusCode::UNAUTHORIZED, "Unauthorized"),
None => {
// If sync extraction fails, try async (for Google tokens)
match extract_email_async(&headers).await {
Some(email) => email,
None => return common_response(StatusCode::UNAUTHORIZED, "Unauthorized"),
}
}
};
let user_data = match repo.query_user_by_email(email.clone()).await {
Ok(user) => user,
Err(_) => return common_response(StatusCode::NOT_FOUND, "User not found"),