browsing 0.1.7

Browser automation: navigate, click, extract, screenshot. Standalone browser control via CDP.
Documentation
//! HTTP authentication via CDP Network domain

use crate::browser::views::AuthCredentials;
use base64::{Engine as _, engine::general_purpose};
use crate::error::Result;
use std::sync::Arc;

/// Manages HTTP Basic and Proxy authentication
#[derive(Debug, Clone)]
pub struct AuthManager {
    client: Arc<crate::browser::cdp::CdpClient>,
}

impl AuthManager {
    /// Create a new auth manager
    pub fn new(client: Arc<crate::browser::cdp::CdpClient>) -> Self {
        Self { client }
    }

    /// Set Basic HTTP authentication header for all subsequent requests.
    /// Uses `Network.setExtraHTTPHeaders` to inject `Authorization: Basic ...`.
    pub async fn set_basic_auth(&self, creds: &AuthCredentials) -> Result<()> {
        let combo = format!("{}:{}", creds.username, creds.password);
        let encoded = general_purpose::STANDARD.encode(combo.as_bytes());
        let header_value = format!("Basic {encoded}");

        self.client
            .send_command(
                "Network.setExtraHTTPHeaders",
                serde_json::json!({
                    "headers": {
                        "Authorization": header_value,
                    }
                }),
            )
            .await?;
        Ok(())
    }

    /// Set Proxy-Authorization header for proxy authentication.
    pub async fn set_proxy_auth(&self, creds: &AuthCredentials) -> Result<()> {
        let combo = format!("{}:{}", creds.username, creds.password);
        let encoded = general_purpose::STANDARD.encode(combo.as_bytes());
        let header_value = format!("Basic {encoded}");

        self.client
            .send_command(
                "Network.setExtraHTTPHeaders",
                serde_json::json!({
                    "headers": {
                        "Proxy-Authorization": header_value,
                    }
                }),
            )
            .await?;
        Ok(())
    }

    /// Set arbitrary extra HTTP headers
    pub async fn set_extra_headers(&self, headers: std::collections::HashMap<String, String>) -> Result<()> {
        self.client
            .send_command(
                "Network.setExtraHTTPHeaders",
                serde_json::json!({ "headers": headers }),
            )
            .await?;
        Ok(())
    }

    /// Clear all extra HTTP headers (removes auth)
    pub async fn clear_auth(&self) -> Result<()> {
        self.client
            .send_command(
                "Network.setExtraHTTPHeaders",
                serde_json::json!({ "headers": {} }),
            )
            .await?;
        Ok(())
    }

    /// Convenience: set basic auth from username and password directly
    pub async fn authenticate(&self, username: &str, password: &str) -> Result<()> {
        let creds = AuthCredentials {
            username: username.to_string(),
            password: password.to_string(),
            realm: None,
        };
        self.set_basic_auth(&creds).await
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_auth_credentials_creation() {
        let creds = AuthCredentials {
            username: "user".to_string(),
            password: "pass".to_string(),
            realm: Some("test-realm".to_string()),
        };
        assert_eq!(creds.username, "user");
        assert_eq!(creds.password, "pass");
        assert_eq!(creds.realm, Some("test-realm".to_string()));
    }

    #[test]
    fn test_auth_credentials_serialization() {
        let creds = AuthCredentials {
            username: "admin".to_string(),
            password: "secret".to_string(),
            realm: None,
        };
        let json = serde_json::to_string(&creds).unwrap();
        assert!(json.contains("admin"));
        assert!(json.contains("secret"));
    }

    #[test]
    fn test_basic_auth_encoding() {
        let combo = "user:pass";
        let encoded = general_purpose::STANDARD.encode(combo.as_bytes());
        assert_eq!(encoded, "dXNlcjpwYXNz");
    }
}