fix(security): patch critical IDOR & auth vulnerabilities

- Fix logout no-op: extract refresh token from cookie/body (auth_handler)
- Secure all 12 WebDAV handlers with AuthUser + resolve_path_for_user
- Secure all 7 batch handlers with caller_id ownership checks
- Add _owned variants: copy_file_owned, delete_file_owned, get_file_stream_owned, get_folder_owned
- Secure list_files_query: add AuthUser, SQL-level user_id filter, tenant-isolated ETag
- Remove deprecated unscoped resolve_path() and exists() from PathResolverService
- Remove dead list_files handler (unmounted, no auth)
- Add list_files_for_owner (SQL) and list_files_owned across trait chain
This commit is contained in:
Dionisio
2026-03-05 10:30:39 +01:00
parent ee86c3a128
commit fdbb2bf60a
14 changed files with 585 additions and 174 deletions
+12 -11
View File
@@ -1,7 +1,7 @@
use axum::{
Router,
extract::{Json, Query, State},
http::{HeaderMap, StatusCode, header},
http::{HeaderMap, StatusCode},
response::{IntoResponse, Redirect, Response},
routing::{get, post, put},
};
@@ -319,26 +319,27 @@ async fn change_password(
async fn logout(
State(state): State<Arc<AppState>>,
headers: HeaderMap,
CurrentUserId(user_id): CurrentUserId,
headers: HeaderMap,
body: axum::body::Bytes,
) -> Result<Response, AppError> {
let auth_service = state
.auth_service
.as_ref()
.ok_or_else(|| AppError::internal_error("Authentication service not configured"))?;
// Obtain the raw access token from Bearer header OR cookie
let token = headers
.get(header::AUTHORIZATION)
.and_then(|v| v.to_str().ok())
.and_then(|v| v.strip_prefix("Bearer "))
.map(String::from)
.or_else(|| cookie_auth::extract_cookie_value(&headers, cookie_auth::ACCESS_COOKIE))
.ok_or_else(|| AppError::unauthorized("Authorization token not found"))?;
// Extract the REFRESH token (not the access token) so the service can
// look up and revoke the correct session.
// Strategy: try JSON body first (API clients), then HttpOnly cookie (browsers).
let refresh_token = serde_json::from_slice::<RefreshTokenDto>(&body)
.ok()
.map(|dto| dto.refresh_token)
.or_else(|| cookie_auth::extract_cookie_value(&headers, cookie_auth::REFRESH_COOKIE))
.ok_or_else(|| AppError::unauthorized("Refresh token required for logout (JSON body or cookie)"))?;
auth_service
.auth_application_service
.logout(&user_id, &token)
.logout(&user_id, &refresh_token)
.await?;
// Clear HttpOnly + CSRF cookies so the browser forgets the session