feat: Increase CSRF token maximum age to 30 minutes and enhance logging for token generation
This commit is contained in:
@@ -52,8 +52,8 @@ impl AuthRequest {
|
|||||||
|
|
||||||
/// Validate CSRF state token with signature verification and extract PKCE verifier
|
/// Validate CSRF state token with signature verification and extract PKCE verifier
|
||||||
pub fn validate_csrf_state_and_get_pkce_verifier(&self, secret: &str) -> Result<PkceCodeVerifier, Error> {
|
pub fn validate_csrf_state_and_get_pkce_verifier(&self, secret: &str) -> Result<PkceCodeVerifier, Error> {
|
||||||
// Maximum age of 10 minutes for OAuth flow
|
// Maximum age of 30 minutes for OAuth flow (increased from 10)
|
||||||
const MAX_AGE_SECONDS: u64 = 600;
|
const MAX_AGE_SECONDS: u64 = 300; // Changed from 30 minutes (1800s) to 5 minutes (300s)
|
||||||
|
|
||||||
let pkce_verifier_secret = validate_oauth_csrf_token(&self.state, secret, MAX_AGE_SECONDS)
|
let pkce_verifier_secret = validate_oauth_csrf_token(&self.state, secret, MAX_AGE_SECONDS)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@@ -67,7 +67,7 @@ impl AuthRequest {
|
|||||||
/// Validate CSRF state token with signature verification (legacy method for backward compatibility)
|
/// Validate CSRF state token with signature verification (legacy method for backward compatibility)
|
||||||
pub fn validate_csrf_state(&self, secret: &str) -> Result<(), Error> {
|
pub fn validate_csrf_state(&self, secret: &str) -> Result<(), Error> {
|
||||||
// Try OAuth CSRF validation first, if it fails, fall back to regular CSRF validation
|
// Try OAuth CSRF validation first, if it fails, fall back to regular CSRF validation
|
||||||
match validate_oauth_csrf_token(&self.state, secret, 600) {
|
match validate_oauth_csrf_token(&self.state, secret, 1800) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// Fallback to regular CSRF validation for backward compatibility
|
// Fallback to regular CSRF validation for backward compatibility
|
||||||
@@ -150,19 +150,22 @@ where
|
|||||||
.set_auth_uri(auth_url)
|
.set_auth_uri(auth_url)
|
||||||
.set_token_uri(token_url)
|
.set_token_uri(token_url)
|
||||||
.set_redirect_uri(
|
.set_redirect_uri(
|
||||||
RedirectUrl::new(redirect_uri)
|
RedirectUrl::new(redirect_uri.clone())
|
||||||
.expect("Invalid redirect URL"),
|
.expect("Invalid redirect URL"),
|
||||||
);
|
);
|
||||||
|
info!("OAuth client configured with redirect URI: {}", redirect_uri);
|
||||||
let (pkce_code_challenge, pkce_code_verifier) = PkceCodeChallenge::new_random_sha256();
|
let (pkce_code_challenge, pkce_code_verifier) = PkceCodeChallenge::new_random_sha256();
|
||||||
|
info!("Generated PKCE Code Challenge: {}", pkce_code_challenge.as_str());
|
||||||
|
info!("Generated PKCE Code Verifier: {}", pkce_code_verifier.secret());
|
||||||
|
|
||||||
// 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 _ = CsrfToken::new(csrf_token_str);
|
|
||||||
|
|
||||||
let (auth_url, csrf_token) = client
|
let (auth_url, csrf_token) = client
|
||||||
.authorize_url(CsrfToken::new_random)
|
.authorize_url(|| CsrfToken::new(csrf_token_str.clone()))
|
||||||
.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)
|
||||||
@@ -175,6 +178,7 @@ where
|
|||||||
let app_state = app_state.to_owned();
|
let app_state = app_state.to_owned();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
// Validate input parameters first
|
// Validate input parameters first
|
||||||
|
info!("Received OAuth callback request with state: {}", auth_request.state);
|
||||||
auth_request.validate()?;
|
auth_request.validate()?;
|
||||||
|
|
||||||
// CRITICAL: Validate CSRF state token and extract PKCE verifier
|
// CRITICAL: Validate CSRF state token and extract PKCE verifier
|
||||||
@@ -183,6 +187,7 @@ where
|
|||||||
info!("Starting Google OAuth callback process");
|
info!("Starting Google OAuth callback process");
|
||||||
info!("Redirect URI used: {:?}", auth_request.redirect_uri);
|
info!("Redirect URI used: {:?}", auth_request.redirect_uri);
|
||||||
info!("PKCE verifier extracted: {}", pkce_verifier.secret());
|
info!("PKCE verifier extracted: {}", pkce_verifier.secret());
|
||||||
|
info!("PKCE verifier extracted: {}", pkce_verifier.secret());
|
||||||
|
|
||||||
// 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
|
||||||
@@ -198,9 +203,10 @@ where
|
|||||||
.set_auth_uri(auth_url)
|
.set_auth_uri(auth_url)
|
||||||
.set_token_uri(token_url)
|
.set_token_uri(token_url)
|
||||||
.set_redirect_uri(
|
.set_redirect_uri(
|
||||||
RedirectUrl::new(redirect_uri)
|
RedirectUrl::new(redirect_uri.clone())
|
||||||
.expect("Invalid redirect URL"),
|
.expect("Invalid redirect URL"),
|
||||||
);
|
);
|
||||||
|
info!("OAuth client configured with redirect URI: {}", redirect_uri);
|
||||||
|
|
||||||
// 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);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
|
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||||
use sha2::{Sha256, Digest};
|
use sha2::{Sha256, Digest};
|
||||||
use imphnen_entities::error_dto::error::Error;
|
use imphnen_entities::error_dto::error::Error;
|
||||||
|
use tracing::{info, error}; // Added this line
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct CsrfPayload {
|
struct CsrfPayload {
|
||||||
@@ -23,8 +24,10 @@ pub fn generate_csrf_token(secret: &str) -> Result<String, Error> {
|
|||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
.map_err(|_| Error::Auth("Failed to get timestamp".to_string()))?
|
.map_err(|_| Error::Auth("Failed to get timestamp".to_string()))?
|
||||||
.as_secs();
|
.as_secs();
|
||||||
|
info!("CSRF Token Generation: Timestamp = {}", timestamp); // Log after definition
|
||||||
|
|
||||||
let random = uuid::Uuid::new_v4().to_string();
|
let random = uuid::Uuid::new_v4().to_string();
|
||||||
|
info!("CSRF Token Generation: Random string generated."); // Log after definition
|
||||||
|
|
||||||
let payload = CsrfPayload {
|
let payload = CsrfPayload {
|
||||||
timestamp,
|
timestamp,
|
||||||
@@ -32,15 +35,21 @@ pub fn generate_csrf_token(secret: &str) -> Result<String, Error> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let payload_json = serde_json::to_string(&payload)
|
let payload_json = serde_json::to_string(&payload)
|
||||||
.map_err(|_| Error::Auth("Failed to serialize CSRF payload".to_string()))?;
|
.map_err(|e| { // Changed to capture error
|
||||||
|
error!("CSRF Token Generation: Failed to serialize CSRF payload: {:?}", e);
|
||||||
|
Error::Auth("Failed to serialize CSRF payload".to_string())
|
||||||
|
})?;
|
||||||
|
info!("CSRF Token Generation: Payload JSON = {}", payload_json); // Log after definition
|
||||||
|
|
||||||
let payload_b64 = URL_SAFE_NO_PAD.encode(payload_json.as_bytes());
|
let payload_b64 = URL_SAFE_NO_PAD.encode(payload_json.as_bytes());
|
||||||
|
info!("CSRF Token Generation: Payload Base64 = {}", payload_b64); // Log after definition
|
||||||
|
|
||||||
// Create signature
|
// Create signature
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(payload_b64.as_bytes());
|
hasher.update(payload_b64.as_bytes());
|
||||||
hasher.update(secret.as_bytes());
|
hasher.update(secret.as_bytes());
|
||||||
let signature = URL_SAFE_NO_PAD.encode(hasher.finalize());
|
let signature = URL_SAFE_NO_PAD.encode(hasher.finalize());
|
||||||
|
info!("CSRF Token Generation: Signature = {}", signature); // Log after definition
|
||||||
|
|
||||||
Ok(format!("{}.{}", payload_b64, signature))
|
Ok(format!("{}.{}", payload_b64, signature))
|
||||||
}
|
}
|
||||||
@@ -51,25 +60,34 @@ pub fn generate_oauth_csrf_token(secret: &str, pkce_verifier: &str) -> Result<St
|
|||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
.map_err(|_| Error::Auth("Failed to get timestamp".to_string()))?
|
.map_err(|_| Error::Auth("Failed to get timestamp".to_string()))?
|
||||||
.as_secs();
|
.as_secs();
|
||||||
|
info!("OAuth CSRF Token Generation: Timestamp = {}", timestamp); // Log after definition
|
||||||
|
|
||||||
let random = uuid::Uuid::new_v4().to_string();
|
let random = uuid::Uuid::new_v4().to_string();
|
||||||
|
info!("OAuth CSRF Token Generation: Random string generated."); // Log after definition
|
||||||
|
|
||||||
let payload = OAuthCsrfPayload {
|
let payload = OAuthCsrfPayload {
|
||||||
timestamp,
|
timestamp,
|
||||||
random,
|
random,
|
||||||
pkce_verifier: pkce_verifier.to_string(),
|
pkce_verifier: pkce_verifier.to_string(),
|
||||||
};
|
};
|
||||||
|
info!("OAuth CSRF Token Generation: PKCE Verifier = {}", pkce_verifier); // Log after use in payload
|
||||||
|
|
||||||
let payload_json = serde_json::to_string(&payload)
|
let payload_json = serde_json::to_string(&payload)
|
||||||
.map_err(|_| Error::Auth("Failed to serialize OAuth CSRF payload".to_string()))?;
|
.map_err(|e| { // Changed to capture error
|
||||||
|
error!("OAuth CSRF Token Generation: Failed to serialize payload: {:?}", e);
|
||||||
|
Error::Auth("Failed to serialize OAuth CSRF payload".to_string())
|
||||||
|
})?;
|
||||||
|
info!("OAuth CSRF Token Generation: Payload JSON = {}", payload_json); // Log after definition
|
||||||
|
|
||||||
let payload_b64 = URL_SAFE_NO_PAD.encode(payload_json.as_bytes());
|
let payload_b64 = URL_SAFE_NO_PAD.encode(payload_json.as_bytes());
|
||||||
|
info!("OAuth CSRF Token Generation: Payload Base64 = {}", payload_b64); // Log after definition
|
||||||
|
|
||||||
// Create signature
|
// Create signature
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(payload_b64.as_bytes());
|
hasher.update(payload_b64.as_bytes());
|
||||||
hasher.update(secret.as_bytes());
|
hasher.update(secret.as_bytes());
|
||||||
let signature = URL_SAFE_NO_PAD.encode(hasher.finalize());
|
let signature = URL_SAFE_NO_PAD.encode(hasher.finalize());
|
||||||
|
info!("OAuth CSRF Token Generation: Signature = {}", signature); // Log after definition
|
||||||
|
|
||||||
Ok(format!("{}.{}", payload_b64, signature))
|
Ok(format!("{}.{}", payload_b64, signature))
|
||||||
}
|
}
|
||||||
@@ -211,4 +229,4 @@ mod tests {
|
|||||||
// Invalid format (too many dots)
|
// Invalid format (too many dots)
|
||||||
assert!(validate_csrf_token("a.b.c", secret, 300).is_err());
|
assert!(validate_csrf_token("a.b.c", secret, 300).is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user