fix: URL-decode DAV paths with spaces + feat: app passwords for Basic Auth
Bug fix: - URL-decode paths in extract_webdav_path(), extract_caldav_path(), extract_carddav_path() so folders with spaces (e.g. 'My Folder') no longer return 404 when accessed via encoded URIs (%20) - Properly encode href values in PROPFIND/PROPPATCH/LOCK XML responses - Decode Destination header in MOVE/COPY operations New feature - App Passwords (API keys for DAV clients): - POST /api/auth/app-passwords → create (shows token once) - GET /api/auth/app-passwords → list (prefix only) - DELETE /api/auth/app-passwords/:id → revoke - Auth middleware now accepts both Bearer JWT and Basic Auth - Argon2 hashed, scoped (webdav/caldav/carddav), optional expiry - Compatible with DAVx5, Thunderbird, rclone, curl Tested: 12/12 E2E tests pass (create, list, WebDAV/CalDAV/CardDAV Basic Auth, URL-decode with spaces, wrong password 401, revoke, post- revoke 401).
This commit is contained in:
@@ -541,6 +541,7 @@ impl AppServiceFactory {
|
||||
wopi_lock_service: None,
|
||||
wopi_discovery_service: None,
|
||||
device_auth_service: None,
|
||||
app_password_service: None,
|
||||
};
|
||||
|
||||
// 9b. Wire admin settings service when auth is available
|
||||
@@ -617,6 +618,37 @@ impl AppServiceFactory {
|
||||
app_state.device_auth_service = Some(device_auth_svc);
|
||||
tracing::info!("Device Authorization Grant (RFC 8628) service initialized");
|
||||
}
|
||||
|
||||
// 9d. Wire App Password service
|
||||
{
|
||||
use crate::application::services::app_password_service::AppPasswordService;
|
||||
use crate::infrastructure::repositories::AppPasswordPgRepository;
|
||||
|
||||
let app_pw_repo: Arc<dyn crate::application::ports::auth_ports::AppPasswordStoragePort> =
|
||||
Arc::new(AppPasswordPgRepository::new(pool.clone()));
|
||||
let hasher: Arc<dyn crate::application::ports::auth_ports::PasswordHasherPort> =
|
||||
Arc::new(
|
||||
crate::infrastructure::services::password_hasher::Argon2PasswordHasher::new(
|
||||
self.config.auth.hash_memory_cost,
|
||||
self.config.auth.hash_time_cost,
|
||||
self.config.auth.hash_parallelism,
|
||||
),
|
||||
);
|
||||
let user_repo: Arc<dyn crate::application::ports::auth_ports::UserStoragePort> =
|
||||
Arc::new(crate::infrastructure::repositories::UserPgRepository::new(
|
||||
pool.clone(),
|
||||
));
|
||||
let base_url = self.config.base_url();
|
||||
|
||||
let app_pw_svc = Arc::new(AppPasswordService::new(
|
||||
app_pw_repo,
|
||||
hasher,
|
||||
user_repo,
|
||||
base_url,
|
||||
));
|
||||
app_state.app_password_service = Some(app_pw_svc);
|
||||
tracing::info!("App Password service initialized");
|
||||
}
|
||||
}
|
||||
|
||||
// 10. Wire CalDAV/CardDAV services
|
||||
@@ -812,6 +844,8 @@ pub struct AppState {
|
||||
Option<Arc<crate::infrastructure::services::wopi_discovery_service::WopiDiscoveryService>>,
|
||||
pub device_auth_service:
|
||||
Option<Arc<crate::application::services::device_auth_service::DeviceAuthService>>,
|
||||
pub app_password_service:
|
||||
Option<Arc<crate::application::services::app_password_service::AppPasswordService>>,
|
||||
}
|
||||
|
||||
// All AppState construction is done via struct literal in build_app_state().
|
||||
|
||||
Reference in New Issue
Block a user