diff --git a/backend/src/auth.rs b/backend/src/auth.rs index 1c2417b..0cf8841 100644 --- a/backend/src/auth.rs +++ b/backend/src/auth.rs @@ -31,13 +31,11 @@ impl AuthService { println!("✅ Input validation passed"); // Create CalDAV config with provided credentials - let caldav_config = CalDAVConfig { - server_url: request.server_url.clone(), - username: request.username.clone(), - password: request.password.clone(), - calendar_path: None, - tasks_path: None, - }; + let caldav_config = CalDAVConfig::new( + request.server_url.clone(), + request.username.clone(), + request.password.clone() + ); println!("📝 Created CalDAV config"); // Test authentication against CalDAV server @@ -74,13 +72,11 @@ impl AuthService { pub fn caldav_config_from_token(&self, token: &str, password: &str) -> Result { let claims = self.verify_token(token)?; - Ok(CalDAVConfig { - server_url: claims.server_url, - username: claims.username, - password: password.to_string(), - calendar_path: None, - tasks_path: None, - }) + Ok(CalDAVConfig::new( + claims.server_url, + claims.username, + password.to_string() + )) } fn validate_login(&self, request: &CalDAVLoginRequest) -> Result<(), ApiError> { diff --git a/backend/src/config.rs b/backend/src/config.rs index 7392bed..2154c15 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -17,14 +17,16 @@ use base64::prelude::*; /// /// ```rust /// # use calendar_backend::config::CalDAVConfig; -/// # fn example() -> Result<(), Box> { -/// // Load configuration from environment variables -/// let config = CalDAVConfig::from_env()?; +/// let config = CalDAVConfig { +/// server_url: "https://caldav.example.com".to_string(), +/// username: "user@example.com".to_string(), +/// password: "password".to_string(), +/// calendar_path: None, +/// tasks_path: None, +/// }; /// /// // Use the configuration for HTTP requests /// let auth_header = format!("Basic {}", config.get_basic_auth()); -/// # Ok(()) -/// # } /// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CalDAVConfig { @@ -41,74 +43,37 @@ pub struct CalDAVConfig { /// Optional path to the calendar collection on the server /// - /// If not provided, the client will need to discover available calendars + /// If not provided, the client will discover available calendars /// through CalDAV PROPFIND requests pub calendar_path: Option, - - /// Optional path to the tasks/todo collection on the server - /// - /// Some CalDAV servers store tasks separately from calendar events - pub tasks_path: Option, } impl CalDAVConfig { - /// Creates a new CalDAVConfig by loading values from environment variables. + /// Creates a new CalDAVConfig with the given credentials. /// - /// This method will attempt to load a `.env` file from the current directory - /// and then read the following required environment variables: + /// # Arguments /// - /// - `CALDAV_SERVER_URL`: The CalDAV server base URL - /// - `CALDAV_USERNAME`: Username for authentication - /// - `CALDAV_PASSWORD`: Password for authentication - /// - /// Optional environment variables: - /// - /// - `CALDAV_CALENDAR_PATH`: Path to calendar collection - /// - `CALDAV_TASKS_PATH`: Path to tasks collection - /// - /// # Errors - /// - /// Returns `ConfigError::MissingVar` if any required environment variable - /// is not set or cannot be read. + /// * `server_url` - The base URL of the CalDAV server + /// * `username` - Username for authentication + /// * `password` - Password for authentication /// /// # Example /// /// ```rust /// # use calendar_backend::config::CalDAVConfig; - /// - /// match CalDAVConfig::from_env() { - /// Ok(config) => { - /// println!("Loaded config for server: {}", config.server_url); - /// } - /// Err(e) => { - /// eprintln!("Failed to load config: {}", e); - /// } - /// } + /// let config = CalDAVConfig::new( + /// "https://caldav.example.com".to_string(), + /// "user@example.com".to_string(), + /// "password".to_string() + /// ); /// ``` - pub fn from_env() -> Result { - // Attempt to load .env file, but don't fail if it doesn't exist - dotenvy::dotenv().ok(); - - let server_url = env::var("CALDAV_SERVER_URL") - .map_err(|_| ConfigError::MissingVar("CALDAV_SERVER_URL".to_string()))?; - - let username = env::var("CALDAV_USERNAME") - .map_err(|_| ConfigError::MissingVar("CALDAV_USERNAME".to_string()))?; - - let password = env::var("CALDAV_PASSWORD") - .map_err(|_| ConfigError::MissingVar("CALDAV_PASSWORD".to_string()))?; - - // Optional paths - it's fine if these are not set - let calendar_path = env::var("CALDAV_CALENDAR_PATH").ok(); - let tasks_path = env::var("CALDAV_TASKS_PATH").ok(); - - Ok(CalDAVConfig { + pub fn new(server_url: String, username: String, password: String) -> Self { + Self { server_url, username, password, - calendar_path, - tasks_path, - }) + calendar_path: env::var("CALDAV_CALENDAR_PATH").ok(), // Optional override from env + } } /// Generates a Base64-encoded string for HTTP Basic Authentication. @@ -192,9 +157,12 @@ mod tests { /// Run with: `cargo test test_baikal_auth` #[tokio::test] async fn test_baikal_auth() { - // Load config from .env - let config = CalDAVConfig::from_env() - .expect("Failed to load CalDAV config from environment"); + // Use test config - update these values to test with real server + let config = CalDAVConfig::new( + "https://example.com".to_string(), + "test_user".to_string(), + "test_password".to_string() + ); println!("Testing authentication to: {}", config.server_url); @@ -238,8 +206,12 @@ mod tests { /// Run with: `cargo test test_propfind_calendars` #[tokio::test] async fn test_propfind_calendars() { - let config = CalDAVConfig::from_env() - .expect("Failed to load CalDAV config from environment"); + // Use test config - update these values to test with real server + let config = CalDAVConfig::new( + "https://example.com".to_string(), + "test_user".to_string(), + "test_password".to_string() + ); let client = reqwest::Client::new(); diff --git a/backend/src/debug_caldav.rs b/backend/src/debug_caldav.rs index 4e929c7..cfb27fd 100644 --- a/backend/src/debug_caldav.rs +++ b/backend/src/debug_caldav.rs @@ -2,7 +2,12 @@ use crate::calendar::CalDAVClient; use crate::config::CalDAVConfig; pub async fn debug_caldav_fetch() -> Result<(), Box> { - let config = CalDAVConfig::from_env()?; + // Use debug/test configuration + let config = CalDAVConfig::new( + "https://example.com".to_string(), + "debug_user".to_string(), + "debug_password".to_string() + ); let client = CalDAVClient::new(config); println!("=== DEBUG: CalDAV Fetch ==="); diff --git a/backend/src/handlers/auth.rs b/backend/src/handlers/auth.rs index 01a42cb..425ba30 100644 --- a/backend/src/handlers/auth.rs +++ b/backend/src/handlers/auth.rs @@ -52,13 +52,11 @@ pub async fn login( println!("📝 Created CalDAV config"); // First verify the credentials are valid by attempting to discover calendars - let config = CalDAVConfig { - server_url: request.server_url.clone(), - username: request.username.clone(), - password: request.password.clone(), - calendar_path: None, - tasks_path: None, - }; + let config = CalDAVConfig::new( + request.server_url.clone(), + request.username.clone(), + request.password.clone() + ); let client = CalDAVClient::new(config); client.discover_calendars() .await diff --git a/backend/tests/integration_tests.rs b/backend/tests/integration_tests.rs index 7edffeb..44ad75f 100644 --- a/backend/tests/integration_tests.rs +++ b/backend/tests/integration_tests.rs @@ -72,9 +72,9 @@ mod test_utils { pub async fn login(&self) -> String { let login_payload = json!({ - "username": std::env::var("CALDAV_USERNAME").unwrap_or("test".to_string()), - "password": std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string()), - "server_url": std::env::var("CALDAV_SERVER_URL").unwrap_or("https://example.com".to_string()) + "username": "test".to_string(), + "password": "test".to_string(), + "server_url": "https://example.com".to_string() }); let response = self.client @@ -134,12 +134,10 @@ mod tests { async fn test_auth_login() { let server = TestServer::start().await; - // Load credentials from .env - dotenvy::dotenv().ok(); - let username = std::env::var("CALDAV_USERNAME").unwrap_or("test".to_string()); - let password = std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string()); - - let server_url = std::env::var("CALDAV_SERVER_URL").unwrap_or("https://example.com".to_string()); + // Use test credentials + let username = "test".to_string(); + let password = "test".to_string(); + let server_url = "https://example.com".to_string(); let login_payload = json!({ "username": username, @@ -196,7 +194,7 @@ mod tests { // Load password from env for CalDAV requests dotenvy::dotenv().ok(); - let password = std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string()); + let password = "test".to_string(); let response = server.client .get(&format!("{}/api/user/info", server.base_url)) @@ -226,7 +224,7 @@ mod tests { // Load password from env for CalDAV requests dotenvy::dotenv().ok(); - let password = std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string()); + let password = "test".to_string(); let response = server.client .get(&format!("{}/api/calendar/events?year=2024&month=12", server.base_url)) @@ -254,7 +252,7 @@ mod tests { // Load password from env for CalDAV requests dotenvy::dotenv().ok(); - let password = std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string()); + let password = "test".to_string(); let create_payload = json!({ "title": "Integration Test Event", @@ -308,7 +306,7 @@ mod tests { // Load password from env for CalDAV requests dotenvy::dotenv().ok(); - let password = std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string()); + let password = "test".to_string(); // Use a dummy UID for testing - this will likely return 404 but we're testing the endpoint structure let test_uid = "test-event-uid"; @@ -373,7 +371,7 @@ mod tests { // Load password from env for CalDAV requests dotenvy::dotenv().ok(); - let password = std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string()); + let password = "test".to_string(); let create_payload = json!({ "title": "Integration Test Series", @@ -431,7 +429,7 @@ mod tests { // Load password from env for CalDAV requests dotenvy::dotenv().ok(); - let password = std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string()); + let password = "test".to_string(); let update_payload = json!({ "series_uid": "test-series-uid", @@ -493,7 +491,7 @@ mod tests { // Load password from env for CalDAV requests dotenvy::dotenv().ok(); - let password = std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string()); + let password = "test".to_string(); let delete_payload = json!({ "series_uid": "test-series-to-delete", diff --git a/frontend/src/config.rs b/frontend/src/config.rs deleted file mode 100644 index 2827bd8..0000000 --- a/frontend/src/config.rs +++ /dev/null @@ -1,284 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::env; -use base64::prelude::*; - -/// Configuration for CalDAV server connection and authentication. -/// -/// This struct holds all the necessary information to connect to a CalDAV server, -/// including server URL, credentials, and optional collection paths. -/// -/// # Security Note -/// -/// The password field contains sensitive information and should be handled carefully. -/// This struct implements `Debug` but in production, consider implementing a custom -/// `Debug` that masks the password field. -/// -/// # Example -/// -/// ```rust -/// use crate::config::CalDAVConfig; -/// -/// // Load configuration from environment variables -/// let config = CalDAVConfig::from_env()?; -/// -/// // Use the configuration for HTTP requests -/// let auth_header = format!("Basic {}", config.get_basic_auth()); -/// ``` -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CalDAVConfig { - /// The base URL of the CalDAV server (e.g., "https://caldav.example.com/dav/") - pub server_url: String, - - /// Username for authentication with the CalDAV server - pub username: String, - - /// Password for authentication with the CalDAV server - /// - /// **Security Note**: This contains sensitive information - pub password: String, - - /// Optional path to the calendar collection on the server - /// - /// If not provided, the client will need to discover available calendars - /// through CalDAV PROPFIND requests - pub calendar_path: Option, - - /// Optional path to the tasks/todo collection on the server - /// - /// Some CalDAV servers store tasks separately from calendar events - pub tasks_path: Option, -} - -impl CalDAVConfig { - /// Creates a new CalDAVConfig by loading values from environment variables. - /// - /// This method will attempt to load a `.env` file from the current directory - /// and then read the following required environment variables: - /// - /// - `CALDAV_SERVER_URL`: The CalDAV server base URL - /// - `CALDAV_USERNAME`: Username for authentication - /// - `CALDAV_PASSWORD`: Password for authentication - /// - /// Optional environment variables: - /// - /// - `CALDAV_CALENDAR_PATH`: Path to calendar collection - /// - `CALDAV_TASKS_PATH`: Path to tasks collection - /// - /// # Errors - /// - /// Returns `ConfigError::MissingVar` if any required environment variable - /// is not set or cannot be read. - /// - /// # Example - /// - /// ```rust - /// use crate::config::CalDAVConfig; - /// - /// match CalDAVConfig::from_env() { - /// Ok(config) => { - /// println!("Loaded config for server: {}", config.server_url); - /// } - /// Err(e) => { - /// eprintln!("Failed to load config: {}", e); - /// } - /// } - /// ``` - pub fn from_env() -> Result { - // Attempt to load .env file, but don't fail if it doesn't exist - dotenvy::dotenv().ok(); - - let server_url = env::var("CALDAV_SERVER_URL") - .map_err(|_| ConfigError::MissingVar("CALDAV_SERVER_URL".to_string()))?; - - let username = env::var("CALDAV_USERNAME") - .map_err(|_| ConfigError::MissingVar("CALDAV_USERNAME".to_string()))?; - - let password = env::var("CALDAV_PASSWORD") - .map_err(|_| ConfigError::MissingVar("CALDAV_PASSWORD".to_string()))?; - - // Optional paths - it's fine if these are not set - let calendar_path = env::var("CALDAV_CALENDAR_PATH").ok(); - let tasks_path = env::var("CALDAV_TASKS_PATH").ok(); - - Ok(CalDAVConfig { - server_url, - username, - password, - calendar_path, - tasks_path, - }) - } - - /// Generates a Base64-encoded string for HTTP Basic Authentication. - /// - /// This method combines the username and password in the format - /// `username:password` and encodes it using Base64, which is the - /// standard format for the `Authorization: Basic` HTTP header. - /// - /// # Returns - /// - /// A Base64-encoded string that can be used directly in the - /// `Authorization` header: `Authorization: Basic ` - /// - /// # Example - /// - /// ```rust - /// use crate::config::CalDAVConfig; - /// - /// let config = CalDAVConfig { - /// server_url: "https://example.com".to_string(), - /// username: "user".to_string(), - /// password: "pass".to_string(), - /// calendar_path: None, - /// tasks_path: None, - /// }; - /// - /// let auth_value = config.get_basic_auth(); - /// let auth_header = format!("Basic {}", auth_value); - /// ``` - pub fn get_basic_auth(&self) -> String { - let credentials = format!("{}:{}", self.username, self.password); - BASE64_STANDARD.encode(&credentials) - } -} - -/// Errors that can occur when loading or using CalDAV configuration. -#[derive(Debug, thiserror::Error)] -pub enum ConfigError { - /// A required environment variable is missing or cannot be read. - /// - /// This error occurs when calling `CalDAVConfig::from_env()` and one of the - /// required environment variables (`CALDAV_SERVER_URL`, `CALDAV_USERNAME`, - /// or `CALDAV_PASSWORD`) is not set. - #[error("Missing environment variable: {0}")] - MissingVar(String), - - /// The configuration contains invalid or malformed values. - /// - /// This could include malformed URLs, invalid authentication credentials, - /// or other configuration issues that prevent proper CalDAV operation. - #[error("Invalid configuration: {0}")] - Invalid(String), -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_basic_auth_encoding() { - let config = CalDAVConfig { - server_url: "https://example.com".to_string(), - username: "testuser".to_string(), - password: "testpass".to_string(), - calendar_path: None, - tasks_path: None, - }; - - let auth = config.get_basic_auth(); - let expected = BASE64_STANDARD.encode("testuser:testpass"); - assert_eq!(auth, expected); - } - - /// Integration test that authenticates with the actual Baikal CalDAV server - /// - /// This test requires a valid .env file with: - /// - CALDAV_SERVER_URL - /// - CALDAV_USERNAME - /// - CALDAV_PASSWORD - /// - /// Run with: `cargo test test_baikal_auth` - #[tokio::test] - async fn test_baikal_auth() { - // Load config from .env - let config = CalDAVConfig::from_env() - .expect("Failed to load CalDAV config from environment"); - - println!("Testing authentication to: {}", config.server_url); - - // Create HTTP client - let client = reqwest::Client::new(); - - // Make a simple OPTIONS request to test authentication - let response = client - .request(reqwest::Method::OPTIONS, &config.server_url) - .header("Authorization", format!("Basic {}", config.get_basic_auth())) - .header("User-Agent", "calendar-app/0.1.0") - .send() - .await - .expect("Failed to send request to CalDAV server"); - - println!("Response status: {}", response.status()); - println!("Response headers: {:#?}", response.headers()); - - // Check if we got a successful response or at least not a 401 Unauthorized - assert!( - response.status().is_success() || response.status() != 401, - "Authentication failed with status: {}. Check your credentials in .env", - response.status() - ); - - // For Baikal/CalDAV servers, we should see DAV headers - assert!( - response.headers().contains_key("dav") || - response.headers().contains_key("DAV") || - response.status().is_success(), - "Server doesn't appear to be a CalDAV server - missing DAV headers" - ); - - println!("✓ Authentication test passed!"); - } - - /// Test making a PROPFIND request to discover calendars - /// - /// This test requires a valid .env file and makes an actual CalDAV PROPFIND request - /// - /// Run with: `cargo test test_propfind_calendars` - #[tokio::test] - async fn test_propfind_calendars() { - let config = CalDAVConfig::from_env() - .expect("Failed to load CalDAV config from environment"); - - let client = reqwest::Client::new(); - - // CalDAV PROPFIND request to discover calendars - let propfind_body = r#" - - - - - - - -"#; - - let response = client - .request(reqwest::Method::from_bytes(b"PROPFIND").unwrap(), &config.server_url) - .header("Authorization", format!("Basic {}", config.get_basic_auth())) - .header("Content-Type", "application/xml") - .header("Depth", "1") - .header("User-Agent", "calendar-app/0.1.0") - .body(propfind_body) - .send() - .await - .expect("Failed to send PROPFIND request"); - - let status = response.status(); - println!("PROPFIND Response status: {}", status); - - let body = response.text().await.expect("Failed to read response body"); - println!("PROPFIND Response body: {}", body); - - // We should get a 207 Multi-Status for PROPFIND - assert_eq!( - status, - reqwest::StatusCode::from_u16(207).unwrap(), - "PROPFIND should return 207 Multi-Status" - ); - - // The response should contain XML with calendar information - assert!(body.contains("calendar"), "Response should contain calendar information"); - - println!("✓ PROPFIND calendars test passed!"); - } -} \ No newline at end of file