- Remove discovery response XML dumps that flood console - Remove calendar collection checking logs - Remove authentication success messages - Remove API call password length logging - Fix unused variable warning Backend now runs with minimal essential logging only. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
141 lines
4.4 KiB
Rust
141 lines
4.4 KiB
Rust
use axum::{extract::State, http::HeaderMap, response::Json};
|
|
use std::sync::Arc;
|
|
|
|
use crate::calendar::CalDAVClient;
|
|
use crate::{
|
|
models::{ApiError, AuthResponse, CalDAVLoginRequest, CalendarInfo, UserInfo},
|
|
AppState,
|
|
};
|
|
|
|
pub fn extract_bearer_token(headers: &HeaderMap) -> Result<String, ApiError> {
|
|
let auth_header = headers
|
|
.get("authorization")
|
|
.ok_or_else(|| ApiError::Unauthorized("Missing Authorization header".to_string()))?;
|
|
|
|
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 ") {
|
|
Ok(token.to_string())
|
|
} else {
|
|
Err(ApiError::BadRequest(
|
|
"Authorization header must be Bearer token".to_string(),
|
|
))
|
|
}
|
|
}
|
|
|
|
pub fn extract_password_header(headers: &HeaderMap) -> Result<String, ApiError> {
|
|
let password_header = headers
|
|
.get("x-caldav-password")
|
|
.ok_or_else(|| ApiError::BadRequest("Missing X-CalDAV-Password header".to_string()))?;
|
|
|
|
password_header
|
|
.to_str()
|
|
.map(|s| s.to_string())
|
|
.map_err(|_| ApiError::BadRequest("Invalid X-CalDAV-Password header".to_string()))
|
|
}
|
|
|
|
pub async fn login(
|
|
State(state): State<Arc<AppState>>,
|
|
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());
|
|
|
|
// Use the auth service login method which now handles database, sessions, and preferences
|
|
let response = state.auth_service.login(request).await?;
|
|
|
|
println!("✅ Login successful with session management");
|
|
|
|
Ok(Json(response))
|
|
}
|
|
|
|
pub async fn verify_token(
|
|
State(state): State<Arc<AppState>>,
|
|
headers: HeaderMap,
|
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
|
let token = extract_bearer_token(&headers)?;
|
|
let is_valid = state.auth_service.verify_token(&token).is_ok();
|
|
|
|
Ok(Json(serde_json::json!({ "valid": is_valid })))
|
|
}
|
|
|
|
pub async fn get_user_info(
|
|
State(state): State<Arc<AppState>>,
|
|
headers: HeaderMap,
|
|
) -> Result<Json<UserInfo>, ApiError> {
|
|
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.clone());
|
|
|
|
// Discover calendars
|
|
let calendar_paths = client
|
|
.discover_calendars()
|
|
.await
|
|
.map_err(|e| ApiError::Internal(format!("Failed to discover calendars: {}", e)))?;
|
|
|
|
|
|
let calendars: Vec<CalendarInfo> = calendar_paths
|
|
.iter()
|
|
.map(|path| CalendarInfo {
|
|
path: path.clone(),
|
|
display_name: extract_calendar_name(path),
|
|
color: generate_calendar_color(path),
|
|
is_visible: true, // Default to visible
|
|
})
|
|
.collect();
|
|
|
|
Ok(Json(UserInfo {
|
|
username: config.username,
|
|
server_url: config.server_url,
|
|
calendars,
|
|
}))
|
|
}
|
|
|
|
fn generate_calendar_color(path: &str) -> String {
|
|
// Generate a consistent color based on the calendar path
|
|
// This is a simple hash-based approach
|
|
let mut hash: u32 = 0;
|
|
for byte in path.bytes() {
|
|
hash = hash.wrapping_mul(31).wrapping_add(byte as u32);
|
|
}
|
|
|
|
// Define a set of pleasant colors
|
|
let colors = [
|
|
"#3B82F6", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6", "#06B6D4", "#84CC16", "#F97316",
|
|
"#EC4899", "#6366F1", "#14B8A6", "#F3B806", "#8B5A2B", "#6B7280", "#DC2626", "#7C3AED",
|
|
"#059669", "#D97706", "#BE185D", "#4F46E5",
|
|
];
|
|
|
|
colors[(hash as usize) % colors.len()].to_string()
|
|
}
|
|
|
|
fn extract_calendar_name(path: &str) -> String {
|
|
// Extract calendar name from path
|
|
// E.g., "/calendars/user/calendar-name/" -> "Calendar Name"
|
|
path.split('/')
|
|
.filter(|s| !s.is_empty())
|
|
.last()
|
|
.unwrap_or("Calendar")
|
|
.replace('-', " ")
|
|
.replace('_', " ")
|
|
.split_whitespace()
|
|
.map(|word| {
|
|
let mut chars = word.chars();
|
|
match chars.next() {
|
|
None => String::new(),
|
|
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
|
|
}
|
|
})
|
|
.collect::<Vec<String>>()
|
|
.join(" ")
|
|
}
|