Add CalDAV dependencies and secure configuration

- Added comprehensive CalDAV/calendar dependencies:
  - reqwest for HTTP requests
  - ical for calendar parsing
  - chrono for date/time handling
  - serde for serialization
  - anyhow/thiserror for error handling
  - uuid for event generation
- Implemented secure config management:
  - dotenvy for environment variable loading
  - base64 for Basic Auth encoding
  - .env.example template for development
  - .gitignore updated to exclude secret files
- Created config.rs module with extensive documentation:
  - CalDAVConfig struct for server credentials
  - Environment-based configuration loading
  - HTTP Basic Auth helper methods
  - Comprehensive error handling
  - Full rustdoc documentation with examples

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Connor Johnstone
2025-08-28 15:13:36 -04:00
parent 0f4b505acd
commit 01411f76c4
5 changed files with 232 additions and 2 deletions

13
.env.example Normal file
View File

@@ -0,0 +1,13 @@
# CalDAV Server Configuration
CALDAV_SERVER_URL=https://your-caldav-server.com/dav/
CALDAV_USERNAME=your-username
CALDAV_PASSWORD=your-password
# Optional: Calendar collection path (if different from default)
CALDAV_CALENDAR_PATH=/calendars/your-username/personal/
# Optional: Task/Todo collection path
CALDAV_TASKS_PATH=/calendars/your-username/tasks/
# Development settings
RUST_LOG=info

7
.gitignore vendored
View File

@@ -11,4 +11,9 @@ dist/
.idea/ .idea/
# Logs # Logs
*.log *.log
# Environment variables (secrets)
.env
.env.local
.env.*.local

View File

@@ -6,4 +6,32 @@ edition = "2021"
[dependencies] [dependencies]
yew = { version = "0.21", features = ["csr"] } yew = { version = "0.21", features = ["csr"] }
web-sys = "0.3" web-sys = "0.3"
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
# HTTP client for CalDAV requests
reqwest = { version = "0.11", features = ["json"] }
wasm-bindgen-futures = "0.4"
# Calendar and iCal parsing
ical = "0.7"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# Date and time handling
chrono = { version = "0.4", features = ["serde", "wasm-bindgen"] }
chrono-tz = "0.8"
# Error handling
anyhow = "1.0"
thiserror = "1.0"
# Logging
log = "0.4"
console_log = "1.0"
# UUID generation for calendar events
uuid = { version = "1.0", features = ["v4", "wasm-bindgen"] }
# Environment variable handling
dotenvy = "0.15"
base64 = "0.21"

182
src/config.rs Normal file
View File

@@ -0,0 +1,182 @@
use serde::{Deserialize, Serialize};
use std::env;
/// 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::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::*;
use std::env;
#[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::encode("testuser:testpass");
assert_eq!(auth, expected);
}
}

View File

@@ -1,6 +1,8 @@
use yew::prelude::*; use yew::prelude::*;
mod app; mod app;
mod config;
use app::App; use app::App;
fn main() { fn main() {