From 4050fc335f61f649ff5bbc8dce5434c295ba8a19 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 29 Oct 2026 20:24:28 +0000 Subject: [PATCH] fix(auth): cover merged GraphQL and federation routes --- rust-core/verisim-api/src/lib.rs | 61 +++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/rust-core/verisim-api/src/lib.rs b/rust-core/verisim-api/src/lib.rs index a807765..1e7bb68 100644 --- a/rust-core/verisim-api/src/lib.rs +++ b/rust-core/verisim-api/src/lib.rs @@ -840,16 +840,17 @@ pub fn build_router(state: AppState) -> Router { "/mv_prover_success_by_class", get(proof_attempts::success_by_class_handler), ) - // Authentication middleware layer - .layer(axum_middleware::from_fn_with_state( - auth_state, - auth::auth_middleware, - )) .with_state(state.clone()) // GraphQL endpoint .merge(graphql::graphql_router(state)) // Federation endpoints (separate state) .merge(federation_routes) + // Apply authorization to the complete router, including merged + // GraphQL and federation routes (not only the initial REST routes). + .layer(axum_middleware::from_fn_with_state( + auth_state, + auth::auth_middleware, + )) } /// Mount the API router at both the root and under `version_prefix`. @@ -2526,6 +2527,56 @@ mod tests { AppState::new_async(config).await.unwrap() } + #[tokio::test] + async fn test_auth_covers_merged_graphql_and_federation_routes() { + let mut state = create_test_state().await; + state.auth = auth::AuthState::new(auth::AuthConfig { + enabled: true, + allow_public_health: true, + ..Default::default() + }); + state.auth.key_registry.register("writer-key", "test writer", auth::ClientRole::Writer); + let app = build_router(state); + + // This GraphQL mutation must never reach its handler without auth. + let mutation = serde_json::json!({ + "query": "mutation { deleteOctad(id: \"missing\") }" + }).to_string(); + let response = app.clone().oneshot( + Request::builder() + .method("POST") + .uri("/graphql") + .header("content-type", "application/json") + .body(Body::from(mutation.clone())) + .unwrap(), + ).await.unwrap(); + assert_eq!(response.status(), StatusCode::UNAUTHORIZED); + + // The federation router was previously merged after auth middleware. + let response = app.clone().oneshot( + Request::builder().uri("/federation/peers").body(Body::empty()).unwrap(), + ).await.unwrap(); + assert_eq!(response.status(), StatusCode::UNAUTHORIZED); + + // Valid writer credentials pass the middleware and reach GraphQL. + let response = app.clone().oneshot( + Request::builder() + .method("POST") + .uri("/graphql") + .header("content-type", "application/json") + .header("x-api-key", "writer-key") + .body(Body::from(mutation)) + .unwrap(), + ).await.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + + // The public health exception remains narrowly available. + let response = app.oneshot( + Request::builder().uri("/health").body(Body::empty()).unwrap(), + ).await.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + } + #[tokio::test] async fn test_health_endpoint() { let state = create_test_state().await;