Refactor authentication from database to direct CalDAV authentication
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>
This commit is contained in:
@@ -7,9 +7,8 @@ use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use chrono::Datelike;
|
||||
|
||||
use crate::{AppState, models::{LoginRequest, RegisterRequest, AuthResponse, ApiError}};
|
||||
use crate::{AppState, models::{CalDAVLoginRequest, AuthResponse, ApiError}};
|
||||
use crate::calendar::{CalDAVClient, CalendarEvent};
|
||||
use crate::config::CalDAVConfig;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CalendarQuery {
|
||||
@@ -18,31 +17,17 @@ pub struct CalendarQuery {
|
||||
}
|
||||
|
||||
pub async fn get_calendar_events(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Query(params): Query<CalendarQuery>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<Json<Vec<CalendarEvent>>, ApiError> {
|
||||
// Verify authentication (extract token from Authorization header)
|
||||
let _token = if let Some(auth_header) = headers.get("authorization") {
|
||||
let auth_str = auth_header
|
||||
.to_str()
|
||||
.map_err(|_| ApiError::Unauthorized("Invalid authorization header".to_string()))?;
|
||||
|
||||
if auth_str.starts_with("Bearer ") {
|
||||
auth_str.strip_prefix("Bearer ").unwrap().to_string()
|
||||
} else {
|
||||
return Err(ApiError::Unauthorized("Invalid authorization format".to_string()));
|
||||
}
|
||||
} else {
|
||||
return Err(ApiError::Unauthorized("Missing authorization header".to_string()));
|
||||
};
|
||||
|
||||
// TODO: Validate JWT token here
|
||||
|
||||
// Load CalDAV configuration
|
||||
let config = CalDAVConfig::from_env()
|
||||
.map_err(|e| ApiError::Internal(format!("Failed to load CalDAV config: {}", e)))?;
|
||||
// Extract and verify token
|
||||
let token = extract_bearer_token(&headers)?;
|
||||
let password = extract_password_header(&headers)?;
|
||||
println!("🔑 API call with password length: {}", password.len());
|
||||
|
||||
// Create CalDAV config from token and password
|
||||
let config = state.auth_service.caldav_config_from_token(&token, &password)?;
|
||||
let client = CalDAVClient::new(config);
|
||||
|
||||
// Discover calendars if needed
|
||||
@@ -74,31 +59,16 @@ pub async fn get_calendar_events(
|
||||
}
|
||||
|
||||
pub async fn refresh_event(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(uid): Path<String>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<Json<Option<CalendarEvent>>, ApiError> {
|
||||
// Verify authentication (extract token from Authorization header)
|
||||
let _token = if let Some(auth_header) = headers.get("authorization") {
|
||||
let auth_str = auth_header
|
||||
.to_str()
|
||||
.map_err(|_| ApiError::Unauthorized("Invalid authorization header".to_string()))?;
|
||||
|
||||
if auth_str.starts_with("Bearer ") {
|
||||
auth_str.strip_prefix("Bearer ").unwrap().to_string()
|
||||
} else {
|
||||
return Err(ApiError::Unauthorized("Invalid authorization format".to_string()));
|
||||
}
|
||||
} else {
|
||||
return Err(ApiError::Unauthorized("Missing authorization header".to_string()));
|
||||
};
|
||||
|
||||
// TODO: Validate JWT token here
|
||||
|
||||
// Load CalDAV configuration
|
||||
let config = CalDAVConfig::from_env()
|
||||
.map_err(|e| ApiError::Internal(format!("Failed to load CalDAV config: {}", e)))?;
|
||||
// Extract and verify token
|
||||
let token = extract_bearer_token(&headers)?;
|
||||
let password = extract_password_header(&headers)?;
|
||||
|
||||
// Create CalDAV config from token and password
|
||||
let config = state.auth_service.caldav_config_from_token(&token, &password)?;
|
||||
let client = CalDAVClient::new(config);
|
||||
|
||||
// Discover calendars if needed
|
||||
@@ -119,18 +89,15 @@ pub async fn refresh_event(
|
||||
Ok(Json(event))
|
||||
}
|
||||
|
||||
pub async fn register(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(request): Json<RegisterRequest>,
|
||||
) -> Result<Json<AuthResponse>, ApiError> {
|
||||
let response = state.auth_service.register(request).await?;
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
pub async fn login(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(request): Json<LoginRequest>,
|
||||
Json(request): Json<CalDAVLoginRequest>,
|
||||
) -> Result<Json<AuthResponse>, ApiError> {
|
||||
println!("🔐 Login attempt:");
|
||||
println!(" Server URL: {}", request.server_url);
|
||||
println!(" Username: {}", request.username);
|
||||
println!(" Password length: {}", request.password.len());
|
||||
|
||||
let response = state.auth_service.login(request).await?;
|
||||
Ok(Json(response))
|
||||
}
|
||||
@@ -139,25 +106,40 @@ pub async fn verify_token(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
// Try to get token from Authorization header
|
||||
let token = if let Some(auth_header) = headers.get("authorization") {
|
||||
let auth_str = auth_header
|
||||
.to_str()
|
||||
.map_err(|_| ApiError::BadRequest("Invalid authorization header".to_string()))?;
|
||||
|
||||
if let Some(token) = auth_str.strip_prefix("Bearer ") {
|
||||
token.to_string()
|
||||
} else {
|
||||
return Err(ApiError::BadRequest("Authorization header must start with 'Bearer '".to_string()));
|
||||
}
|
||||
} else {
|
||||
return Err(ApiError::Unauthorized("Authorization header required".to_string()));
|
||||
};
|
||||
|
||||
let user_info = state.auth_service.verify_token(&token).await?;
|
||||
let token = extract_bearer_token(&headers)?;
|
||||
let claims = state.auth_service.verify_token(&token)?;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"valid": true,
|
||||
"user": user_info
|
||||
"username": claims.username,
|
||||
"server_url": claims.server_url
|
||||
})))
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
fn extract_bearer_token(headers: &HeaderMap) -> Result<String, ApiError> {
|
||||
if let Some(auth_header) = headers.get("authorization") {
|
||||
let auth_str = auth_header
|
||||
.to_str()
|
||||
.map_err(|_| ApiError::Unauthorized("Invalid authorization header".to_string()))?;
|
||||
|
||||
if let Some(token) = auth_str.strip_prefix("Bearer ") {
|
||||
Ok(token.to_string())
|
||||
} else {
|
||||
Err(ApiError::Unauthorized("Authorization header must start with 'Bearer '".to_string()))
|
||||
}
|
||||
} else {
|
||||
Err(ApiError::Unauthorized("Authorization header required".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_password_header(headers: &HeaderMap) -> Result<String, ApiError> {
|
||||
if let Some(password_header) = headers.get("x-caldav-password") {
|
||||
let password = password_header
|
||||
.to_str()
|
||||
.map_err(|_| ApiError::BadRequest("Invalid password header".to_string()))?;
|
||||
Ok(password.to_string())
|
||||
} else {
|
||||
Err(ApiError::BadRequest("X-CalDAV-Password header required".to_string()))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user