feat: implement OAuth 2.0 Device Authorization Grant (RFC 8628) for WebDAV/CalDAV/CardDAV
Adds full Device Authorization Grant flow so DAV clients (rclone, etc.) can authenticate without browser-based OAuth redirects. New files: - Domain entity: DeviceCode with status lifecycle (pending/authorized/denied/expired) - Port: DeviceCodeStoragePort trait (7 async methods) - DTOs: request/response types for all device auth endpoints - Repository: DeviceCodePgRepository (PostgreSQL implementation) - Service: DeviceAuthService (initiate, verify, approve, deny, poll, cleanup) - Handler: 6 HTTP endpoints (2 public + 4 protected) - Static: device-verify.html verification page served at /device Flow: 1. Client POST /api/auth/device/authorize → device_code + user_code 2. User opens /device?code=XXXX in browser, approves 3. Client polls POST /api/auth/device/token → receives JWT tokens 4. Client uses Bearer token with existing WebDAV/CalDAV/CardDAV middleware Schema: auth.device_codes table + device_code_status enum added to schema.sql Closes #152
This commit is contained in:
@@ -540,6 +540,7 @@ impl AppServiceFactory {
|
||||
wopi_token_service: None,
|
||||
wopi_lock_service: None,
|
||||
wopi_discovery_service: None,
|
||||
device_auth_service: None,
|
||||
};
|
||||
|
||||
// 9b. Wire admin settings service when auth is available
|
||||
@@ -589,6 +590,33 @@ impl AppServiceFactory {
|
||||
}
|
||||
|
||||
app_state.admin_settings_service = Some(admin_svc);
|
||||
|
||||
// 9c. Wire Device Authorization Grant (RFC 8628) service
|
||||
{
|
||||
use crate::application::services::device_auth_service::DeviceAuthService;
|
||||
use crate::infrastructure::repositories::DeviceCodePgRepository;
|
||||
|
||||
let device_code_repo = Arc::new(DeviceCodePgRepository::new(pool.clone()));
|
||||
let user_repo: Arc<dyn crate::application::ports::auth_ports::UserStoragePort> =
|
||||
Arc::new(crate::infrastructure::repositories::UserPgRepository::new(
|
||||
pool.clone(),
|
||||
));
|
||||
let session_repo: Arc<dyn crate::application::ports::auth_ports::SessionStoragePort> =
|
||||
Arc::new(crate::infrastructure::repositories::SessionPgRepository::new(
|
||||
pool.clone(),
|
||||
));
|
||||
let base_url = self.config.base_url();
|
||||
|
||||
let device_auth_svc = Arc::new(DeviceAuthService::new(
|
||||
device_code_repo,
|
||||
auth_svc.token_service.clone(),
|
||||
user_repo,
|
||||
session_repo,
|
||||
base_url,
|
||||
));
|
||||
app_state.device_auth_service = Some(device_auth_svc);
|
||||
tracing::info!("Device Authorization Grant (RFC 8628) service initialized");
|
||||
}
|
||||
}
|
||||
|
||||
// 10. Wire CalDAV/CardDAV services
|
||||
@@ -782,6 +810,8 @@ pub struct AppState {
|
||||
Option<Arc<crate::application::services::wopi_lock_service::WopiLockService>>,
|
||||
pub wopi_discovery_service:
|
||||
Option<Arc<crate::infrastructure::services::wopi_discovery_service::WopiDiscoveryService>>,
|
||||
pub device_auth_service:
|
||||
Option<Arc<crate::application::services::device_auth_service::DeviceAuthService>>,
|
||||
}
|
||||
|
||||
// All AppState construction is done via struct literal in build_app_state().
|
||||
|
||||
Reference in New Issue
Block a user