diff --git a/src/interfaces/nextcloud/basic_auth_middleware.rs b/src/interfaces/nextcloud/basic_auth_middleware.rs index 4ebeb140..52d65529 100644 --- a/src/interfaces/nextcloud/basic_auth_middleware.rs +++ b/src/interfaces/nextcloud/basic_auth_middleware.rs @@ -114,6 +114,15 @@ pub async fn basic_auth_middleware( ); return Err(NextcloudAuthError::Unauthorized); } + // Populate the deferred `user_id` field on the request + // tracing span (declared in `middleware/trace_span.rs::ClientIpMakeSpan`). + // Mirrors what `interfaces/middleware/auth.rs` does for the + // JWT path so the two auth surfaces produce log lines with + // the same structured shape — without this, every NC + // request would appear in the logs with `user_id=-`, + // making it harder to correlate WebDAV / OCS activity to + // a specific principal. + tracing::Span::current().record("user_id", user_id.to_string()); request.extensions_mut().insert(Arc::new(CurrentUser { id: user_id, username: uname, diff --git a/src/main.rs b/src/main.rs index 96cf18c5..192b4146 100644 --- a/src/main.rs +++ b/src/main.rs @@ -451,21 +451,20 @@ async fn main() -> Result<(), Box> { .merge(caldav_protected) .merge(carddav_protected) .merge(webdav_protected) - .merge(web_routes) - .layer( - TraceLayer::new_for_http() - .make_span_with(ClientIpMakeSpan) - .on_response(LogBadRequest), - ) - .layer(PropagateRequestIdLayer::x_request_id()) - .layer(SetRequestIdLayer::x_request_id(UuidRequestId)); + .merge(web_routes); - // Mount Nextcloud routes (uses its own Basic Auth middleware) + // Mount Nextcloud routes (uses its own Basic Auth middleware). + // **Merged BEFORE the trace + request-id layers** so NC requests + // get the same `request_id` / `user_id` / `client_ip` span + // fields as every other surface — see + // `interfaces/middleware/trace_span.rs::ClientIpMakeSpan`. if let Some(nc_router) = nextcloud_router { app = app.merge(nc_router.with_state(app_state.clone())); } - // Mount WOPI routes (protocol routes use own token auth, API routes behind auth middleware) + // Mount WOPI routes (protocol routes use own token auth, API routes behind auth middleware). + // Same reasoning as NC above: merge before the trace layer so + // WOPI requests appear in the structured log channel. if let Some((wopi_protocol, wopi_api)) = wopi_routes { let wopi_api_protected = wopi_api .layer(axum::middleware::from_fn(csrf_middleware)) @@ -477,6 +476,20 @@ async fn main() -> Result<(), Box> { .nest("/wopi", wopi_protocol) .nest("/api/wopi", wopi_api_protected); } + + // ── Trace + request-id layers applied LAST so every route + // merged above (including the conditional NC and WOPI + // surfaces) is wrapped. New protocol routers added later + // only have to be merged before this point to get tracing + // for free — no second site to remember to update. + app = app + .layer( + TraceLayer::new_for_http() + .make_span_with(ClientIpMakeSpan) + .on_response(LogBadRequest), + ) + .layer(PropagateRequestIdLayer::x_request_id()) + .layer(SetRequestIdLayer::x_request_id(UuidRequestId)); } else { // Auth disabled — no middleware applied tracing::warn!("Authentication is DISABLED — all API routes are publicly accessible"); @@ -491,7 +504,24 @@ async fn main() -> Result<(), Box> { .merge(caldav_router) .merge(carddav_router) .merge(webdav_router) - .merge(web_routes) + .merge(web_routes); + + // Mount Nextcloud routes — merged BEFORE the trace + request-id + // layers so NC requests get the same span fields as every + // other surface (matches the auth-enabled branch above). + if let Some(nc_router) = nextcloud_router { + app = app.merge(nc_router.with_state(app_state.clone())); + } + + // Mount WOPI routes (no auth middleware when auth is disabled). + // Same reasoning: merge before the trace layer. + if let Some((wopi_protocol, wopi_api)) = wopi_routes { + app = app.nest("/wopi", wopi_protocol).nest("/api/wopi", wopi_api); + } + + // ── Trace + request-id layers applied LAST. See the + // auth-enabled branch above for the rationale. + app = app .layer( TraceLayer::new_for_http() .make_span_with(ClientIpMakeSpan) @@ -499,16 +529,6 @@ async fn main() -> Result<(), Box> { ) .layer(PropagateRequestIdLayer::x_request_id()) .layer(SetRequestIdLayer::x_request_id(UuidRequestId)); - - // Mount Nextcloud routes - if let Some(nc_router) = nextcloud_router { - app = app.merge(nc_router.with_state(app_state.clone())); - } - - // Mount WOPI routes (no auth middleware when auth is disabled) - if let Some((wopi_protocol, wopi_api)) = wopi_routes { - app = app.nest("/wopi", wopi_protocol).nest("/api/wopi", wopi_api); - } } // Increase the default body limit to allow large file uploads.