 d85898cae7
			
		
	
	d85898cae7
	
	
	
		
			
			Major architectural change to simplify authentication by authenticating directly against CalDAV servers instead of maintaining a local user database. Backend changes: - Remove SQLite database dependencies and user storage - Refactor AuthService to authenticate directly against CalDAV servers - Update JWT tokens to store CalDAV server info instead of user IDs - Implement proper CalDAV calendar discovery with XML parsing - Fix URL construction for CalDAV REPORT requests - Add comprehensive debug logging for authentication flow Frontend changes: - Add server URL input field to login form - Remove registration functionality entirely - Update calendar service to pass CalDAV passwords via headers - Store CalDAV credentials in localStorage for API calls Key improvements: - Simplified architecture eliminates database complexity - Direct CalDAV authentication ensures credentials always work - Proper calendar discovery automatically finds user calendars - Robust error handling and debug logging for troubleshooting 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			138 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			138 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use chrono::{Duration, Utc};
 | |
| use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
 | |
| use serde::{Deserialize, Serialize};
 | |
| 
 | |
| use crate::models::{CalDAVLoginRequest, AuthResponse, ApiError};
 | |
| use crate::config::CalDAVConfig;
 | |
| use crate::calendar::CalDAVClient;
 | |
| 
 | |
| #[derive(Debug, Serialize, Deserialize)]
 | |
| pub struct Claims {
 | |
|     pub username: String,
 | |
|     pub server_url: String,
 | |
|     pub exp: i64,     // Expiration time
 | |
|     pub iat: i64,     // Issued at
 | |
| }
 | |
| 
 | |
| #[derive(Clone)]
 | |
| pub struct AuthService {
 | |
|     jwt_secret: String,
 | |
| }
 | |
| 
 | |
| impl AuthService {
 | |
|     pub fn new(jwt_secret: String) -> Self {
 | |
|         Self { jwt_secret }
 | |
|     }
 | |
| 
 | |
|     /// Authenticate user directly against CalDAV server
 | |
|     pub async fn login(&self, request: CalDAVLoginRequest) -> Result<AuthResponse, ApiError> {
 | |
|         // Validate input
 | |
|         self.validate_login(&request)?;
 | |
|         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,
 | |
|         };
 | |
|         println!("📝 Created CalDAV config");
 | |
| 
 | |
|         // Test authentication against CalDAV server
 | |
|         let caldav_client = CalDAVClient::new(caldav_config.clone());
 | |
|         println!("🔗 Created CalDAV client, attempting to discover calendars...");
 | |
|         
 | |
|         // Try to discover calendars as an authentication test
 | |
|         match caldav_client.discover_calendars().await {
 | |
|             Ok(calendars) => {
 | |
|                 println!("✅ Authentication successful! Found {} calendars", calendars.len());
 | |
|                 // Authentication successful, generate JWT token
 | |
|                 let token = self.generate_token(&request.username, &request.server_url)?;
 | |
|                 
 | |
|                 Ok(AuthResponse {
 | |
|                     token,
 | |
|                     username: request.username,
 | |
|                     server_url: request.server_url,
 | |
|                 })
 | |
|             }
 | |
|             Err(err) => {
 | |
|                 println!("❌ Authentication failed: {:?}", err);
 | |
|                 // Authentication failed
 | |
|                 Err(ApiError::Unauthorized("Invalid CalDAV credentials or server unavailable".to_string()))
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Verify JWT token and extract CalDAV credentials info
 | |
|     pub fn verify_token(&self, token: &str) -> Result<Claims, ApiError> {
 | |
|         self.decode_token(token)
 | |
|     }
 | |
| 
 | |
|     /// Create CalDAV config from token
 | |
|     pub fn caldav_config_from_token(&self, token: &str, password: &str) -> Result<CalDAVConfig, ApiError> {
 | |
|         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,
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     fn validate_login(&self, request: &CalDAVLoginRequest) -> Result<(), ApiError> {
 | |
|         if request.username.trim().is_empty() {
 | |
|             return Err(ApiError::BadRequest("Username is required".to_string()));
 | |
|         }
 | |
| 
 | |
|         if request.password.trim().is_empty() {
 | |
|             return Err(ApiError::BadRequest("Password is required".to_string()));
 | |
|         }
 | |
| 
 | |
|         if request.server_url.trim().is_empty() {
 | |
|             return Err(ApiError::BadRequest("Server URL is required".to_string()));
 | |
|         }
 | |
| 
 | |
|         // Basic URL validation
 | |
|         if !request.server_url.starts_with("http://") && !request.server_url.starts_with("https://") {
 | |
|             return Err(ApiError::BadRequest("Server URL must start with http:// or https://".to_string()));
 | |
|         }
 | |
| 
 | |
|         Ok(())
 | |
|     }
 | |
| 
 | |
|     fn generate_token(&self, username: &str, server_url: &str) -> Result<String, ApiError> {
 | |
|         let now = Utc::now();
 | |
|         let expires_at = now + Duration::hours(24); // Token valid for 24 hours
 | |
| 
 | |
|         let claims = Claims {
 | |
|             username: username.to_string(),
 | |
|             server_url: server_url.to_string(),
 | |
|             exp: expires_at.timestamp(),
 | |
|             iat: now.timestamp(),
 | |
|         };
 | |
| 
 | |
|         let token = encode(
 | |
|             &Header::default(),
 | |
|             &claims,
 | |
|             &EncodingKey::from_secret(self.jwt_secret.as_bytes()),
 | |
|         )
 | |
|         .map_err(|e| ApiError::Internal(format!("Token generation failed: {}", e)))?;
 | |
| 
 | |
|         Ok(token)
 | |
|     }
 | |
| 
 | |
|     fn decode_token(&self, token: &str) -> Result<Claims, ApiError> {
 | |
|         let token_data = decode::<Claims>(
 | |
|             token,
 | |
|             &DecodingKey::from_secret(self.jwt_secret.as_bytes()),
 | |
|             &Validation::new(Algorithm::HS256),
 | |
|         )
 | |
|         .map_err(|_| ApiError::Unauthorized("Invalid token".to_string()))?;
 | |
| 
 | |
|         Ok(token_data.claims)
 | |
|     }
 | |
| } |