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:
13
.env.example
Normal file
13
.env.example
Normal 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
7
.gitignore
vendored
@@ -11,4 +11,9 @@ dist/
|
|||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
# Environment variables (secrets)
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
30
Cargo.toml
30
Cargo.toml
@@ -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
182
src/config.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user