feat(auth): Enhance Google OAuth flow with async email extraction and caching
This commit is contained in:
@@ -21,5 +21,6 @@ uuid.workspace = true
|
||||
tracing.workspace = true
|
||||
base64.workspace = true
|
||||
sha2.workspace = true
|
||||
reqwest.workspace = true
|
||||
dotenvy = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::decode_access_token;
|
||||
use axum::http::{HeaderMap, header::AUTHORIZATION};
|
||||
|
||||
/// Extracts the email from the Authorization header, if present and valid.
|
||||
/// Supports both our internal JWT tokens and Google access tokens.
|
||||
pub fn extract_email(headers: &HeaderMap) -> Option<String> {
|
||||
info!(?headers, "extract_email called with headers");
|
||||
let auth_header = match headers.get(AUTHORIZATION) {
|
||||
@@ -27,19 +28,103 @@ pub fn extract_email(headers: &HeaderMap) -> Option<String> {
|
||||
}
|
||||
};
|
||||
info!(token, "Extracted bearer token in extract_email");
|
||||
|
||||
// First try to decode as our internal JWT token
|
||||
match decode_access_token(token) {
|
||||
Ok(data) => {
|
||||
info!(email = %data.claims.sub, "Successfully decoded access token in extract_email");
|
||||
info!(email = %data.claims.sub, "Successfully decoded internal access token in extract_email");
|
||||
Some(data.claims.sub)
|
||||
}
|
||||
Err(_) => {
|
||||
info!("Failed to decode as internal JWT, checking if it's a Google token");
|
||||
// If it fails, it might be a Google access token
|
||||
// For Google tokens, we need async validation, so we'll return None here
|
||||
// and handle Google tokens separately in the calling code
|
||||
error!("Token is not a valid internal JWT. If this is a Google token, please use extract_email_async or handle Google OAuth flow properly.");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Async version that can handle Google access tokens
|
||||
pub async fn extract_email_async(headers: &HeaderMap) -> Option<String> {
|
||||
info!(?headers, "extract_email_async called with headers");
|
||||
let auth_header = match headers.get(AUTHORIZATION) {
|
||||
Some(h) => h,
|
||||
None => {
|
||||
error!("Authorization header missing in extract_email_async");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let auth_str = match auth_header.to_str() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
error!(error = ?e, "Failed to decode access token in extract_email");
|
||||
error!(error = ?e, "Failed to convert Authorization header to str in extract_email_async");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let token = match auth_str.strip_prefix("Bearer ") {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
error!(auth_str, "Authorization header does not start with 'Bearer ' in extract_email_async");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
info!(token, "Extracted bearer token in extract_email_async");
|
||||
|
||||
// First try to decode as our internal JWT token
|
||||
match decode_access_token(token) {
|
||||
Ok(data) => {
|
||||
info!(email = %data.claims.sub, "Successfully decoded internal access token in extract_email_async");
|
||||
Some(data.claims.sub)
|
||||
}
|
||||
Err(_) => {
|
||||
info!("Failed to decode as internal JWT, trying Google token validation");
|
||||
// If it fails, try to validate as Google access token
|
||||
extract_email_from_google_token(token).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts email from Google access token by calling Google's tokeninfo endpoint
|
||||
async fn extract_email_from_google_token(token: &str) -> Option<String> {
|
||||
use serde_json::Value;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let tokeninfo_url = format!("https://oauth2.googleapis.com/tokeninfo?access_token={}", token);
|
||||
|
||||
match client.get(&tokeninfo_url).send().await {
|
||||
Ok(response) => {
|
||||
if response.status().is_success() {
|
||||
match response.json::<Value>().await {
|
||||
Ok(token_info) => {
|
||||
if let Some(email) = token_info.get("email").and_then(|e| e.as_str()) {
|
||||
info!(email = %email, "Successfully extracted email from Google token");
|
||||
Some(email.to_string())
|
||||
} else {
|
||||
error!("Email not found in Google token info response");
|
||||
None
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!(error = ?e, "Failed to parse Google token info response");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!(status = %response.status(), "Google token validation failed");
|
||||
None
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!(error = ?e, "Failed to validate Google token");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts the email from a JWT token string.
|
||||
/// Supports both our internal JWT tokens and Google access tokens.
|
||||
pub fn extract_email_token(token: String) -> Option<String> {
|
||||
info!(token = %token, "extract_email_token called with token");
|
||||
match decode_access_token(&token) {
|
||||
@@ -47,9 +132,29 @@ pub fn extract_email_token(token: String) -> Option<String> {
|
||||
info!(email = %data.claims.sub, "Successfully decoded token in extract_email_token");
|
||||
Some(data.claims.sub)
|
||||
}
|
||||
Err(e) => {
|
||||
error!(error = ?e, "Failed to decode token in extract_email_token");
|
||||
Err(_) => {
|
||||
info!("Failed to decode as internal JWT in extract_email_token, checking if it's a Google token");
|
||||
// If it fails, it might be a Google access token
|
||||
// For Google tokens, we need async validation, so we'll return None here
|
||||
// and handle Google tokens separately in the calling code
|
||||
error!("Token is not a valid internal JWT. If this is a Google token, please use extract_email_token_async or handle Google OAuth flow properly.");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Async version of extract_email_token that can handle Google access tokens
|
||||
pub async fn extract_email_token_async(token: String) -> Option<String> {
|
||||
info!(token = %token, "extract_email_token_async called with token");
|
||||
match decode_access_token(&token) {
|
||||
Ok(data) => {
|
||||
info!(email = %data.claims.sub, "Successfully decoded internal token in extract_email_token_async");
|
||||
Some(data.claims.sub)
|
||||
}
|
||||
Err(_) => {
|
||||
info!("Failed to decode as internal JWT in extract_email_token_async, trying Google token validation");
|
||||
// If it fails, try to validate as Google access token
|
||||
extract_email_from_google_token(&token).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ pub mod csrf_token;
|
||||
|
||||
pub use logger::init_logger;
|
||||
pub use bind_filter::*;
|
||||
pub use extract_email::*;
|
||||
pub use extract_email::{extract_email, extract_email_async, extract_email_token, extract_email_token_async};
|
||||
pub use generate_date::*;
|
||||
pub use generate_otp::*;
|
||||
pub use get_id::*;
|
||||
|
||||
Reference in New Issue
Block a user