refactor(api): ensure trace are for all HTTP req.

- ensure Nextcloud handler has also traces
    - ensure that user_id is provisionned by the nextcloud auth method
This commit is contained in:
Edouard Vanbelle
2026-06-06 11:54:23 +02:00
parent e4bb5b064a
commit 04a4868cd2
2 changed files with 50 additions and 21 deletions
@@ -114,6 +114,15 @@ pub async fn basic_auth_middleware(
); );
return Err(NextcloudAuthError::Unauthorized); 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 { request.extensions_mut().insert(Arc::new(CurrentUser {
id: user_id, id: user_id,
username: uname, username: uname,
+41 -21
View File
@@ -451,21 +451,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.merge(caldav_protected) .merge(caldav_protected)
.merge(carddav_protected) .merge(carddav_protected)
.merge(webdav_protected) .merge(webdav_protected)
.merge(web_routes) .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));
// 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 { if let Some(nc_router) = nextcloud_router {
app = app.merge(nc_router.with_state(app_state.clone())); 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 { if let Some((wopi_protocol, wopi_api)) = wopi_routes {
let wopi_api_protected = wopi_api let wopi_api_protected = wopi_api
.layer(axum::middleware::from_fn(csrf_middleware)) .layer(axum::middleware::from_fn(csrf_middleware))
@@ -477,6 +476,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.nest("/wopi", wopi_protocol) .nest("/wopi", wopi_protocol)
.nest("/api/wopi", wopi_api_protected); .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 { } else {
// Auth disabled — no middleware applied // Auth disabled — no middleware applied
tracing::warn!("Authentication is DISABLED — all API routes are publicly accessible"); tracing::warn!("Authentication is DISABLED — all API routes are publicly accessible");
@@ -491,7 +504,24 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.merge(caldav_router) .merge(caldav_router)
.merge(carddav_router) .merge(carddav_router)
.merge(webdav_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( .layer(
TraceLayer::new_for_http() TraceLayer::new_for_http()
.make_span_with(ClientIpMakeSpan) .make_span_with(ClientIpMakeSpan)
@@ -499,16 +529,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
) )
.layer(PropagateRequestIdLayer::x_request_id()) .layer(PropagateRequestIdLayer::x_request_id())
.layer(SetRequestIdLayer::x_request_id(UuidRequestId)); .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. // Increase the default body limit to allow large file uploads.