Update dependencies and refactor OAuth service
- Updated various dependencies in Cargo.toml to their latest versions for improved performance and security. - Refactored the Google OAuth service to streamline the creation of the OAuth client and improve code readability. - Changed the way WebSocket messages are sent in clear_db_test.rs to use `into()` for better type handling. - Enhanced error handling and logging in the Google OAuth callback method.
This commit is contained in:
Generated
+327
-550
File diff suppressed because it is too large
Load Diff
+30
-30
@@ -16,58 +16,58 @@ members = [
|
|||||||
|
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
oauth2 = "4.4.2"
|
oauth2 = "5.0.0"
|
||||||
reqwest = { version = "0.11.24", features = ["json"] }
|
reqwest = { version = "0.12.23", features = ["json"] }
|
||||||
serde_json = "1.0.111"
|
serde_json = "1.0.142"
|
||||||
axum = { version = "0.8.4", features = ["multipart"] }
|
axum = { version = "0.8.4", features = ["multipart"] }
|
||||||
log = "0.4.25"
|
log = "0.4.27"
|
||||||
serde = { version = "1.0.217", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
tokio = { version = "1.45.0", features = ["full"] }
|
tokio = { version = "1.47.1", features = ["full"] }
|
||||||
argon2 = { version = "0.5.3", features = ["password-hash"] }
|
argon2 = { version = "0.5.3", features = ["password-hash"] }
|
||||||
jsonwebtoken = "9.3.1"
|
jsonwebtoken = "9.3.1"
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.41"
|
||||||
utoipa = { version = "5.3.1", features = ["axum_extras"] }
|
utoipa = { version = "5.4.0", features = ["axum_extras"] }
|
||||||
utoipa-swagger-ui = { version = "9.0.0", features = ["axum"] }
|
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
|
||||||
lettre = { version = "0.11.16", features = ["tokio1-native-tls"] }
|
lettre = { version = "0.11.18", features = ["tokio1-native-tls"] }
|
||||||
surrealdb = { version = "2.3.7", features = ["kv-mem", "kv-fdb"] }
|
surrealdb = { version = "2.3.7", features = ["kv-mem", "kv-fdb"] }
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.14"
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.99"
|
||||||
rand = { version = "0.9.1", features = ["std", "alloc"] }
|
rand = { version = "0.9.2", features = ["std", "alloc"] }
|
||||||
rand_distr = "0.5.1"
|
rand_distr = "0.5.1"
|
||||||
tower-http = { version = "0.6.4", features = ["cors", "trace"] }
|
tower-http = { version = "0.6.6", features = ["cors", "trace"] }
|
||||||
http-body-util = "0.1.1"
|
http-body-util = "0.1.3"
|
||||||
validator = { version = "0.20.0", features = ["derive"] }
|
validator = { version = "0.20.0", features = ["derive"] }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.5.0"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
axum-test = "17.2.0"
|
axum-test = "17.3.0"
|
||||||
axum-extra = { version = "0.10.1", features = ["typed-header"] }
|
axum-extra = { version = "0.10.1", features = ["typed-header"] }
|
||||||
fancy-regex = "0.15.0"
|
fancy-regex = "0.16.1"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
tower = "0.5.2"
|
tower = "0.5.2"
|
||||||
env_logger = "0.11.8"
|
env_logger = "0.11.8"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.41"
|
||||||
uuid = { version = "1.8.0", features = ["v4", "fast-rng", "serde"] }
|
uuid = { version = "1.18.0", features = ["v4", "fast-rng", "serde"] }
|
||||||
strum = { version = "0.27.1", features = ["derive"] }
|
strum = { version = "0.27.2", features = ["derive"] }
|
||||||
strum_macros = "0.27.1"
|
strum_macros = "0.27.2"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.9"
|
||||||
hmac = "0.12"
|
hmac = "0.12"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
urlencoding = "2.1"
|
urlencoding = "2.1"
|
||||||
hyper = "1.6.0"
|
hyper = "1.6.0"
|
||||||
hyper-util = "0.1.0"
|
hyper-util = "0.1.16"
|
||||||
minio = "0.1.0"
|
minio = "0.3.0"
|
||||||
|
|
||||||
|
|
||||||
tokio-test = "0.4.3"
|
tokio-test = "0.4.4"
|
||||||
mockall = "0.12.1"
|
mockall = "0.13.1"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.21.3"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
tokio-tungstenite = "0.23"
|
tokio-tungstenite = "0.27"
|
||||||
url = "2.5"
|
url = "2.5"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
http = "0.2"
|
http = "1.3"
|
||||||
tests = { path = "./tests" }
|
tests = { path = "./tests" }
|
||||||
imphnen-iam = { path = "./imphnen-iam" }
|
imphnen-iam = { path = "./imphnen-iam" }
|
||||||
imphnen-cms = { path = "./imphnen-cms" }
|
imphnen-cms = { path = "./imphnen-cms" }
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
let url = Url::parse(SURREALDB_URL_WS)?; // Menggunakan SURREALDB_URL_WS statis
|
let url = Url::parse(SURREALDB_URL_WS)?; // Menggunakan SURREALDB_URL_WS statis
|
||||||
|
|
||||||
let (ws_stream, _) = connect_async(url).await?;
|
let (ws_stream, _) = connect_async(url.as_str()).await?;
|
||||||
let (mut write, mut read) = ws_stream.split();
|
let (mut write, mut read) = ws_stream.split();
|
||||||
|
|
||||||
// Authenticate (signin)
|
// Authenticate (signin)
|
||||||
@@ -43,7 +43,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
"id": 1
|
"id": 1
|
||||||
}).to_string();
|
}).to_string();
|
||||||
println!("DEBUG: Sending signin query: {}", signin_query);
|
println!("DEBUG: Sending signin query: {}", signin_query);
|
||||||
write.send(Message::Text(signin_query)).await?;
|
write.send(Message::Text(signin_query.into())).await?;
|
||||||
|
|
||||||
let signin_response = read.next().await.ok_or("Failed to read signin response")?;
|
let signin_response = read.next().await.ok_or("Failed to read signin response")?;
|
||||||
let signin_response_msg = signin_response?;
|
let signin_response_msg = signin_response?;
|
||||||
@@ -60,7 +60,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
"id": 2
|
"id": 2
|
||||||
}).to_string();
|
}).to_string();
|
||||||
println!("DEBUG: Sending use query: {}", use_query);
|
println!("DEBUG: Sending use query: {}", use_query);
|
||||||
write.send(Message::Text(use_query)).await?;
|
write.send(Message::Text(use_query.into())).await?;
|
||||||
|
|
||||||
let use_response = read.next().await.ok_or("Failed to read use response")?;
|
let use_response = read.next().await.ok_or("Failed to read use response")?;
|
||||||
let use_response_msg = use_response?;
|
let use_response_msg = use_response?;
|
||||||
@@ -82,7 +82,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}).to_string();
|
}).to_string();
|
||||||
|
|
||||||
println!("DEBUG: Attempting REMOVE TABLE {}: {}", table, query_json);
|
println!("DEBUG: Attempting REMOVE TABLE {}: {}", table, query_json);
|
||||||
write.send(Message::Text(query_json)).await?;
|
write.send(Message::Text(query_json.into())).await?;
|
||||||
let response_result = read.next().await.ok_or("Stream ended unexpectedly")?;
|
let response_result = read.next().await.ok_or("Stream ended unexpectedly")?;
|
||||||
|
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}).to_string();
|
}).to_string();
|
||||||
|
|
||||||
println!("DEBUG: Attempting DELETE {}: {}", table, delete_all_json);
|
println!("DEBUG: Attempting DELETE {}: {}", table, delete_all_json);
|
||||||
write.send(Message::Text(delete_all_json)).await?;
|
write.send(Message::Text(delete_all_json.into())).await?;
|
||||||
let delete_response_result = read.next().await.ok_or("Stream ended unexpectedly during DELETE type::")?;
|
let delete_response_result = read.next().await.ok_or("Stream ended unexpectedly during DELETE type::")?;
|
||||||
|
|
||||||
match delete_response_result {
|
match delete_response_result {
|
||||||
@@ -137,7 +137,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
"params": [select_query],
|
"params": [select_query],
|
||||||
"id": i + 1000
|
"id": i + 1000
|
||||||
}).to_string();
|
}).to_string();
|
||||||
write.send(Message::Text(select_json)).await?;
|
write.send(Message::Text(select_json.into())).await?;
|
||||||
let select_response_result = read.next().await.ok_or("Stream ended unexpectedly during SELECT check")?;
|
let select_response_result = read.next().await.ok_or("Stream ended unexpectedly during SELECT check")?;
|
||||||
match select_response_result {
|
match select_response_result {
|
||||||
Ok(select_msg) => {
|
Ok(select_msg) => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::future::Future;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use oauth2::{
|
use oauth2::{
|
||||||
basic::BasicClient, AuthUrl, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, PkceCodeVerifier,
|
AuthUrl, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, PkceCodeVerifier,
|
||||||
RedirectUrl, Scope, TokenUrl,
|
RedirectUrl, Scope, TokenUrl,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -102,7 +102,6 @@ async fn get_default_role_id(app_state: &AppState) -> Result<String, Error> {
|
|||||||
pub trait GoogleOauthService<A: AuthServiceTrait + Send + Sync + 'static, U: UsersServiceTrait + Send + Sync + 'static>: Send + Sync + 'static {
|
pub trait GoogleOauthService<A: AuthServiceTrait + Send + Sync + 'static, U: UsersServiceTrait + Send + Sync + 'static>: Send + Sync + 'static {
|
||||||
// Removed new() from trait
|
// Removed new() from trait
|
||||||
fn with_services(auth_service: A, users_service: U, env: &'static Env) -> Self;
|
fn with_services(auth_service: A, users_service: U, env: &'static Env) -> Self;
|
||||||
fn google_oauth_client(&self, custom_redirect_uri: Option<String>) -> BasicClient;
|
|
||||||
fn generate_auth_url(&self, custom_redirect_uri: Option<String>) -> (Url, CsrfToken);
|
fn generate_auth_url(&self, custom_redirect_uri: Option<String>) -> (Url, CsrfToken);
|
||||||
fn google_oauth_callback(&self, auth_request: AuthRequest, app_state: &AppState) -> Pin<Box<dyn Future<Output = Result<(UsersDetailItemDto, TokenDto), Error>> + Send + '_>>; // Changed return type
|
fn google_oauth_callback(&self, auth_request: AuthRequest, app_state: &AppState) -> Pin<Box<dyn Future<Output = Result<(UsersDetailItemDto, TokenDto), Error>> + Send + '_>>; // Changed return type
|
||||||
}
|
}
|
||||||
@@ -137,44 +136,38 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn google_oauth_client(&self, custom_redirect_uri: Option<String>) -> BasicClient {
|
|
||||||
|
fn generate_auth_url(&self, custom_redirect_uri: Option<String>) -> (Url, CsrfToken) {
|
||||||
let google_client_id = ClientId::new(self.env.google_client_id.clone());
|
let google_client_id = ClientId::new(self.env.google_client_id.clone());
|
||||||
let google_client_secret = ClientSecret::new(self.env.google_client_secret.clone());
|
let google_client_secret = ClientSecret::new(self.env.google_client_secret.clone());
|
||||||
let auth_url = AuthUrl::new("https://accounts.google.com/o/oauth2/v2/auth".to_string())
|
let auth_url = AuthUrl::new("https://accounts.google.com/o/oauth2/v2/auth".to_string())
|
||||||
.expect("Invalid authorization endpoint URL");
|
.expect("Invalid authorization endpoint URL");
|
||||||
let token_url = TokenUrl::new("https://oauth2.googleapis.com/token".to_string())
|
let token_url = TokenUrl::new("https://oauth2.googleapis.com/token".to_string())
|
||||||
.expect("Invalid token endpoint URL");
|
.expect("Invalid token endpoint URL");
|
||||||
|
|
||||||
let redirect_uri = custom_redirect_uri.unwrap_or_else(|| self.env.google_redirect_url.clone());
|
let redirect_uri = custom_redirect_uri.unwrap_or_else(|| self.env.google_redirect_url.clone());
|
||||||
|
let client = oauth2::basic::BasicClient::new(google_client_id)
|
||||||
BasicClient::new(
|
.set_client_secret(google_client_secret)
|
||||||
google_client_id,
|
.set_auth_uri(auth_url)
|
||||||
Some(google_client_secret),
|
.set_token_uri(token_url)
|
||||||
auth_url,
|
.set_redirect_uri(
|
||||||
Some(token_url),
|
RedirectUrl::new(redirect_uri)
|
||||||
)
|
.expect("Invalid redirect URL"),
|
||||||
.set_redirect_uri(
|
);
|
||||||
RedirectUrl::new(redirect_uri)
|
|
||||||
.expect("Invalid redirect URL"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_auth_url(&self, custom_redirect_uri: Option<String>) -> (Url, CsrfToken) {
|
|
||||||
let client = self.google_oauth_client(custom_redirect_uri);
|
|
||||||
let (pkce_code_challenge, pkce_code_verifier) = PkceCodeChallenge::new_random_sha256();
|
let (pkce_code_challenge, pkce_code_verifier) = PkceCodeChallenge::new_random_sha256();
|
||||||
|
|
||||||
// Generate a signed CSRF token with PKCE verifier for stateless validation
|
// Generate a signed CSRF token with PKCE verifier for stateless validation
|
||||||
let csrf_token_str = generate_oauth_csrf_token(&self.env.access_token_secret, pkce_code_verifier.secret())
|
let csrf_token_str = generate_oauth_csrf_token(&self.env.access_token_secret, pkce_code_verifier.secret())
|
||||||
.unwrap_or_else(|_| uuid::Uuid::new_v4().to_string()); // Fallback to UUID if signing fails
|
.unwrap_or_else(|_| uuid::Uuid::new_v4().to_string()); // Fallback to UUID if signing fails
|
||||||
|
|
||||||
let csrf_token = CsrfToken::new(csrf_token_str);
|
let _ = CsrfToken::new(csrf_token_str);
|
||||||
|
|
||||||
client
|
let (auth_url, csrf_token) = client
|
||||||
.authorize_url(|| csrf_token.clone())
|
.authorize_url(CsrfToken::new_random)
|
||||||
.add_scope(Scope::new("https://www.googleapis.com/auth/userinfo.email".to_string()))
|
.add_scope(Scope::new("https://www.googleapis.com/auth/userinfo.email".to_string()))
|
||||||
.add_scope(Scope::new("https://www.googleapis.com/auth/userinfo.profile".to_string()))
|
.add_scope(Scope::new("https://www.googleapis.com/auth/userinfo.profile".to_string()))
|
||||||
.set_pkce_challenge(pkce_code_challenge)
|
.set_pkce_challenge(pkce_code_challenge)
|
||||||
.url()
|
.url();
|
||||||
|
(auth_url, csrf_token)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn google_oauth_callback(&self, auth_request: AuthRequest, app_state: &AppState) -> Pin<Box<dyn Future<Output = Result<(UsersDetailItemDto, TokenDto), Error>> + Send + '_>> {
|
fn google_oauth_callback(&self, auth_request: AuthRequest, app_state: &AppState) -> Pin<Box<dyn Future<Output = Result<(UsersDetailItemDto, TokenDto), Error>> + Send + '_>> {
|
||||||
@@ -193,7 +186,21 @@ where
|
|||||||
|
|
||||||
// Use the SAME redirect URI that was used for auth URL generation
|
// Use the SAME redirect URI that was used for auth URL generation
|
||||||
// This is crucial for OAuth security and consistency
|
// This is crucial for OAuth security and consistency
|
||||||
let client = self_clone.google_oauth_client(auth_request.redirect_uri.clone());
|
let google_client_id = ClientId::new(self_clone.env.google_client_id.clone());
|
||||||
|
let google_client_secret = ClientSecret::new(self_clone.env.google_client_secret.clone());
|
||||||
|
let auth_url = AuthUrl::new("https://accounts.google.com/o/oauth2/v2/auth".to_string())
|
||||||
|
.expect("Invalid authorization endpoint URL");
|
||||||
|
let token_url = TokenUrl::new("https://oauth2.googleapis.com/token".to_string())
|
||||||
|
.expect("Invalid token endpoint URL");
|
||||||
|
let redirect_uri = auth_request.redirect_uri.clone().unwrap_or_else(|| self_clone.env.google_redirect_url.clone());
|
||||||
|
let client = oauth2::basic::BasicClient::new(google_client_id)
|
||||||
|
.set_client_secret(google_client_secret)
|
||||||
|
.set_auth_uri(auth_url)
|
||||||
|
.set_token_uri(token_url)
|
||||||
|
.set_redirect_uri(
|
||||||
|
RedirectUrl::new(redirect_uri)
|
||||||
|
.expect("Invalid redirect URL"),
|
||||||
|
);
|
||||||
|
|
||||||
// Debug the OAuth client configuration
|
// Debug the OAuth client configuration
|
||||||
let effective_redirect_uri = auth_request.redirect_uri.as_ref().unwrap_or(&self_clone.env.google_redirect_url);
|
let effective_redirect_uri = auth_request.redirect_uri.as_ref().unwrap_or(&self_clone.env.google_redirect_url);
|
||||||
@@ -207,18 +214,18 @@ where
|
|||||||
let token_response = client
|
let token_response = client
|
||||||
.exchange_code(oauth2::AuthorizationCode::new(auth_request.code.clone()))
|
.exchange_code(oauth2::AuthorizationCode::new(auth_request.code.clone()))
|
||||||
.set_pkce_verifier(pkce_verifier)
|
.set_pkce_verifier(pkce_verifier)
|
||||||
.request_async(oauth2::reqwest::async_http_client)
|
.request_async(&reqwest::Client::new())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!("Failed to exchange OAuth code with Google: {}", e);
|
error!("Failed to exchange OAuth code with Google: {}", e);
|
||||||
error!("OAuth code was: {}", auth_request.code);
|
error!("OAuth code was: {}", auth_request.code);
|
||||||
error!("Redirect URI was: {:?}", auth_request.redirect_uri);
|
error!("Redirect URI was: {:?}", auth_request.redirect_uri);
|
||||||
|
|
||||||
// Debug OAuth client configuration
|
// Debug OAuth client configuration
|
||||||
error!("Google Client ID: {}", self_clone.env.google_client_id);
|
error!("Google Client ID: {}", self_clone.env.google_client_id);
|
||||||
error!("OAuth client redirect URI configured: {}",
|
error!("OAuth client redirect URI configured: {}",
|
||||||
auth_request.redirect_uri.as_ref().unwrap_or(&self_clone.env.google_redirect_url));
|
auth_request.redirect_uri.as_ref().unwrap_or(&self_clone.env.google_redirect_url));
|
||||||
|
|
||||||
// Try to extract more details from the error
|
// Try to extract more details from the error
|
||||||
match &e {
|
match &e {
|
||||||
oauth2::RequestTokenError::ServerResponse(response) => {
|
oauth2::RequestTokenError::ServerResponse(response) => {
|
||||||
@@ -235,7 +242,7 @@ where
|
|||||||
error!("Google OAuth Other Error: {:?}", other);
|
error!("Google OAuth Other Error: {:?}", other);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Error::Auth("Authentication error: Failed to exchange authorization code".to_string())
|
Error::Auth("Authentication error: Failed to exchange authorization code".to_string())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user