Clean up obsolete CalDAV environment variables
Some checks failed
Build and Push Docker Image / docker (push) Failing after 2s
Some checks failed
Build and Push Docker Image / docker (push) Failing after 2s
## Removed Obsolete Environment Variables: - `CALDAV_SERVER_URL` - provided by user login - `CALDAV_USERNAME` - provided by user login - `CALDAV_PASSWORD` - provided by user login - `CALDAV_TASKS_PATH` - not used in any features ## Kept with Intelligent Discovery: - `CALDAV_CALENDAR_PATH` - optional override, defaults to smart discovery ## Changes: ### Backend - Remove `CalDAVConfig::from_env()` method (not used in main app) - Add `CalDAVConfig::new()` constructor with credentials - Remove `tasks_path` field from CalDAVConfig - Update auth service to use new constructor - Update tests to use hardcoded test values instead of env vars - Update debug tools to use test credentials ### Frontend - Remove unused `config.rs` file entirely (frontend uses backend API) ## Current Authentication Flow: 1. User provides CalDAV credentials via login API 2. Backend creates CalDAVConfig dynamically from login request 3. Backend tests authentication via calendar discovery 4. Optional `CALDAV_CALENDAR_PATH` env var can override discovery 5. No environment variables required for normal operation This simplifies deployment - users only need to provide CalDAV credentials through the web interface, no server-side configuration required. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -31,13 +31,11 @@ impl AuthService {
|
|||||||
println!("✅ Input validation passed");
|
println!("✅ Input validation passed");
|
||||||
|
|
||||||
// Create CalDAV config with provided credentials
|
// Create CalDAV config with provided credentials
|
||||||
let caldav_config = CalDAVConfig {
|
let caldav_config = CalDAVConfig::new(
|
||||||
server_url: request.server_url.clone(),
|
request.server_url.clone(),
|
||||||
username: request.username.clone(),
|
request.username.clone(),
|
||||||
password: request.password.clone(),
|
request.password.clone()
|
||||||
calendar_path: None,
|
);
|
||||||
tasks_path: None,
|
|
||||||
};
|
|
||||||
println!("📝 Created CalDAV config");
|
println!("📝 Created CalDAV config");
|
||||||
|
|
||||||
// Test authentication against CalDAV server
|
// Test authentication against CalDAV server
|
||||||
@@ -74,13 +72,11 @@ impl AuthService {
|
|||||||
pub fn caldav_config_from_token(&self, token: &str, password: &str) -> Result<CalDAVConfig, ApiError> {
|
pub fn caldav_config_from_token(&self, token: &str, password: &str) -> Result<CalDAVConfig, ApiError> {
|
||||||
let claims = self.verify_token(token)?;
|
let claims = self.verify_token(token)?;
|
||||||
|
|
||||||
Ok(CalDAVConfig {
|
Ok(CalDAVConfig::new(
|
||||||
server_url: claims.server_url,
|
claims.server_url,
|
||||||
username: claims.username,
|
claims.username,
|
||||||
password: password.to_string(),
|
password.to_string()
|
||||||
calendar_path: None,
|
))
|
||||||
tasks_path: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_login(&self, request: &CalDAVLoginRequest) -> Result<(), ApiError> {
|
fn validate_login(&self, request: &CalDAVLoginRequest) -> Result<(), ApiError> {
|
||||||
|
|||||||
@@ -17,14 +17,16 @@ use base64::prelude::*;
|
|||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use calendar_backend::config::CalDAVConfig;
|
/// # use calendar_backend::config::CalDAVConfig;
|
||||||
/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
|
/// let config = CalDAVConfig {
|
||||||
/// // Load configuration from environment variables
|
/// server_url: "https://caldav.example.com".to_string(),
|
||||||
/// let config = CalDAVConfig::from_env()?;
|
/// username: "user@example.com".to_string(),
|
||||||
|
/// password: "password".to_string(),
|
||||||
|
/// calendar_path: None,
|
||||||
|
/// tasks_path: None,
|
||||||
|
/// };
|
||||||
///
|
///
|
||||||
/// // Use the configuration for HTTP requests
|
/// // Use the configuration for HTTP requests
|
||||||
/// let auth_header = format!("Basic {}", config.get_basic_auth());
|
/// let auth_header = format!("Basic {}", config.get_basic_auth());
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct CalDAVConfig {
|
pub struct CalDAVConfig {
|
||||||
@@ -41,74 +43,37 @@ pub struct CalDAVConfig {
|
|||||||
|
|
||||||
/// Optional path to the calendar collection on the server
|
/// 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
|
/// through CalDAV PROPFIND requests
|
||||||
pub calendar_path: Option<String>,
|
pub calendar_path: Option<String>,
|
||||||
|
|
||||||
/// Optional path to the tasks/todo collection on the server
|
|
||||||
///
|
|
||||||
/// Some CalDAV servers store tasks separately from calendar events
|
|
||||||
pub tasks_path: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CalDAVConfig {
|
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
|
/// # Arguments
|
||||||
/// and then read the following required environment variables:
|
|
||||||
///
|
///
|
||||||
/// - `CALDAV_SERVER_URL`: The CalDAV server base URL
|
/// * `server_url` - The base URL of the CalDAV server
|
||||||
/// - `CALDAV_USERNAME`: Username for authentication
|
/// * `username` - Username for authentication
|
||||||
/// - `CALDAV_PASSWORD`: Password for authentication
|
/// * `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
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use calendar_backend::config::CalDAVConfig;
|
/// # use calendar_backend::config::CalDAVConfig;
|
||||||
///
|
/// let config = CalDAVConfig::new(
|
||||||
/// match CalDAVConfig::from_env() {
|
/// "https://caldav.example.com".to_string(),
|
||||||
/// Ok(config) => {
|
/// "user@example.com".to_string(),
|
||||||
/// println!("Loaded config for server: {}", config.server_url);
|
/// "password".to_string()
|
||||||
/// }
|
/// );
|
||||||
/// Err(e) => {
|
|
||||||
/// eprintln!("Failed to load config: {}", e);
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn from_env() -> Result<Self, ConfigError> {
|
pub fn new(server_url: String, username: String, password: String) -> Self {
|
||||||
// Attempt to load .env file, but don't fail if it doesn't exist
|
Self {
|
||||||
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,
|
server_url,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
calendar_path,
|
calendar_path: env::var("CALDAV_CALENDAR_PATH").ok(), // Optional override from env
|
||||||
tasks_path,
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a Base64-encoded string for HTTP Basic Authentication.
|
/// Generates a Base64-encoded string for HTTP Basic Authentication.
|
||||||
@@ -192,9 +157,12 @@ mod tests {
|
|||||||
/// Run with: `cargo test test_baikal_auth`
|
/// Run with: `cargo test test_baikal_auth`
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_baikal_auth() {
|
async fn test_baikal_auth() {
|
||||||
// Load config from .env
|
// Use test config - update these values to test with real server
|
||||||
let config = CalDAVConfig::from_env()
|
let config = CalDAVConfig::new(
|
||||||
.expect("Failed to load CalDAV config from environment");
|
"https://example.com".to_string(),
|
||||||
|
"test_user".to_string(),
|
||||||
|
"test_password".to_string()
|
||||||
|
);
|
||||||
|
|
||||||
println!("Testing authentication to: {}", config.server_url);
|
println!("Testing authentication to: {}", config.server_url);
|
||||||
|
|
||||||
@@ -238,8 +206,12 @@ mod tests {
|
|||||||
/// Run with: `cargo test test_propfind_calendars`
|
/// Run with: `cargo test test_propfind_calendars`
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_propfind_calendars() {
|
async fn test_propfind_calendars() {
|
||||||
let config = CalDAVConfig::from_env()
|
// Use test config - update these values to test with real server
|
||||||
.expect("Failed to load CalDAV config from environment");
|
let config = CalDAVConfig::new(
|
||||||
|
"https://example.com".to_string(),
|
||||||
|
"test_user".to_string(),
|
||||||
|
"test_password".to_string()
|
||||||
|
);
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ use crate::calendar::CalDAVClient;
|
|||||||
use crate::config::CalDAVConfig;
|
use crate::config::CalDAVConfig;
|
||||||
|
|
||||||
pub async fn debug_caldav_fetch() -> Result<(), Box<dyn std::error::Error>> {
|
pub async fn debug_caldav_fetch() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
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);
|
let client = CalDAVClient::new(config);
|
||||||
|
|
||||||
println!("=== DEBUG: CalDAV Fetch ===");
|
println!("=== DEBUG: CalDAV Fetch ===");
|
||||||
|
|||||||
@@ -52,13 +52,11 @@ pub async fn login(
|
|||||||
println!("📝 Created CalDAV config");
|
println!("📝 Created CalDAV config");
|
||||||
|
|
||||||
// First verify the credentials are valid by attempting to discover calendars
|
// First verify the credentials are valid by attempting to discover calendars
|
||||||
let config = CalDAVConfig {
|
let config = CalDAVConfig::new(
|
||||||
server_url: request.server_url.clone(),
|
request.server_url.clone(),
|
||||||
username: request.username.clone(),
|
request.username.clone(),
|
||||||
password: request.password.clone(),
|
request.password.clone()
|
||||||
calendar_path: None,
|
);
|
||||||
tasks_path: None,
|
|
||||||
};
|
|
||||||
let client = CalDAVClient::new(config);
|
let client = CalDAVClient::new(config);
|
||||||
client.discover_calendars()
|
client.discover_calendars()
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -72,9 +72,9 @@ mod test_utils {
|
|||||||
|
|
||||||
pub async fn login(&self) -> String {
|
pub async fn login(&self) -> String {
|
||||||
let login_payload = json!({
|
let login_payload = json!({
|
||||||
"username": std::env::var("CALDAV_USERNAME").unwrap_or("test".to_string()),
|
"username": "test".to_string(),
|
||||||
"password": std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string()),
|
"password": "test".to_string(),
|
||||||
"server_url": std::env::var("CALDAV_SERVER_URL").unwrap_or("https://example.com".to_string())
|
"server_url": "https://example.com".to_string()
|
||||||
});
|
});
|
||||||
|
|
||||||
let response = self.client
|
let response = self.client
|
||||||
@@ -134,12 +134,10 @@ mod tests {
|
|||||||
async fn test_auth_login() {
|
async fn test_auth_login() {
|
||||||
let server = TestServer::start().await;
|
let server = TestServer::start().await;
|
||||||
|
|
||||||
// Load credentials from .env
|
// Use test credentials
|
||||||
dotenvy::dotenv().ok();
|
let username = "test".to_string();
|
||||||
let username = std::env::var("CALDAV_USERNAME").unwrap_or("test".to_string());
|
let password = "test".to_string();
|
||||||
let password = std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string());
|
let server_url = "https://example.com".to_string();
|
||||||
|
|
||||||
let server_url = std::env::var("CALDAV_SERVER_URL").unwrap_or("https://example.com".to_string());
|
|
||||||
|
|
||||||
let login_payload = json!({
|
let login_payload = json!({
|
||||||
"username": username,
|
"username": username,
|
||||||
@@ -196,7 +194,7 @@ mod tests {
|
|||||||
|
|
||||||
// Load password from env for CalDAV requests
|
// Load password from env for CalDAV requests
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
let password = std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string());
|
let password = "test".to_string();
|
||||||
|
|
||||||
let response = server.client
|
let response = server.client
|
||||||
.get(&format!("{}/api/user/info", server.base_url))
|
.get(&format!("{}/api/user/info", server.base_url))
|
||||||
@@ -226,7 +224,7 @@ mod tests {
|
|||||||
|
|
||||||
// Load password from env for CalDAV requests
|
// Load password from env for CalDAV requests
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
let password = std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string());
|
let password = "test".to_string();
|
||||||
|
|
||||||
let response = server.client
|
let response = server.client
|
||||||
.get(&format!("{}/api/calendar/events?year=2024&month=12", server.base_url))
|
.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
|
// Load password from env for CalDAV requests
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
let password = std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string());
|
let password = "test".to_string();
|
||||||
|
|
||||||
let create_payload = json!({
|
let create_payload = json!({
|
||||||
"title": "Integration Test Event",
|
"title": "Integration Test Event",
|
||||||
@@ -308,7 +306,7 @@ mod tests {
|
|||||||
|
|
||||||
// Load password from env for CalDAV requests
|
// Load password from env for CalDAV requests
|
||||||
dotenvy::dotenv().ok();
|
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
|
// Use a dummy UID for testing - this will likely return 404 but we're testing the endpoint structure
|
||||||
let test_uid = "test-event-uid";
|
let test_uid = "test-event-uid";
|
||||||
@@ -373,7 +371,7 @@ mod tests {
|
|||||||
|
|
||||||
// Load password from env for CalDAV requests
|
// Load password from env for CalDAV requests
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
let password = std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string());
|
let password = "test".to_string();
|
||||||
|
|
||||||
let create_payload = json!({
|
let create_payload = json!({
|
||||||
"title": "Integration Test Series",
|
"title": "Integration Test Series",
|
||||||
@@ -431,7 +429,7 @@ mod tests {
|
|||||||
|
|
||||||
// Load password from env for CalDAV requests
|
// Load password from env for CalDAV requests
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
let password = std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string());
|
let password = "test".to_string();
|
||||||
|
|
||||||
let update_payload = json!({
|
let update_payload = json!({
|
||||||
"series_uid": "test-series-uid",
|
"series_uid": "test-series-uid",
|
||||||
@@ -493,7 +491,7 @@ mod tests {
|
|||||||
|
|
||||||
// Load password from env for CalDAV requests
|
// Load password from env for CalDAV requests
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
let password = std::env::var("CALDAV_PASSWORD").unwrap_or("test".to_string());
|
let password = "test".to_string();
|
||||||
|
|
||||||
let delete_payload = json!({
|
let delete_payload = json!({
|
||||||
"series_uid": "test-series-to-delete",
|
"series_uid": "test-series-to-delete",
|
||||||
|
|||||||
@@ -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<String>,
|
|
||||||
|
|
||||||
/// Optional path to the tasks/todo collection on the server
|
|
||||||
///
|
|
||||||
/// Some CalDAV servers store tasks separately from calendar events
|
|
||||||
pub tasks_path: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Self, ConfigError> {
|
|
||||||
// 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 <returned_value>`
|
|
||||||
///
|
|
||||||
/// # 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#"<?xml version="1.0" encoding="utf-8" ?>
|
|
||||||
<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
|
|
||||||
<d:prop>
|
|
||||||
<d:resourcetype />
|
|
||||||
<d:displayname />
|
|
||||||
<c:calendar-description />
|
|
||||||
<c:supported-calendar-component-set />
|
|
||||||
</d:prop>
|
|
||||||
</d:propfind>"#;
|
|
||||||
|
|
||||||
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!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user