Repository navigation
fix(auth): enforce middleware on merged GraphQL and federation routes #336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4050fc3
6b756ca
48f2be4
d157627
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -850,16 +850,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, | ||
| )) | ||
|
Comment on lines
+860
to
+863
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When authentication is enabled, a Useful? React with 👍 / 👎.
Comment on lines
+860
to
+863
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When authentication is enabled, any Useful? React with 👍 / 👎.
Comment on lines
+860
to
+863
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When authentication is enabled, any Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| /// Mount the API router at both the root and under `version_prefix`. | ||
|
|
@@ -2536,6 +2537,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; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
AuthConfig.enabledis true, this outer layer rejects legitimate federation protocol requests before their handlers can validate federation credentials:heartbeataccepts the documentedX-Federation-PSKheader, and registration accepts its PSK in the request body, whereasauth_middlewarerecognizes onlyX-API-Keyor Bearer credentials. Consequently, peers following the existing federation contract now receive 401 responses unless they are also provisioned with an unrelated general API credential; exempt the PSK-authenticated peer routes or integrate federation identities into this authentication boundary.Useful? React with 👍 / 👎.