diff --git a/crates/erp-server/src/main.rs b/crates/erp-server/src/main.rs index 522a819..d443af1 100644 --- a/crates/erp-server/src/main.rs +++ b/crates/erp-server/src/main.rs @@ -503,12 +503,7 @@ async fn main() -> anyhow::Result<()> { // Layer execution order (outer → inner): account_lockout → rate_limit_by_ip // So account lockout check runs FIRST, then IP rate limiting let public_routes = Router::new() - .merge(handlers::health::health_check_router()) .merge(erp_auth::AuthModule::public_routes()) - .route( - "/docs/openapi.json", - axum::routing::get(handlers::openapi::openapi_spec), - ) .layer(axum::middleware::from_fn_with_state( state.clone(), middleware::rate_limit::account_lockout_middleware, @@ -519,6 +514,15 @@ async fn main() -> anyhow::Result<()> { )) .with_state(state.clone()); + // Unthrottled public routes (health, docs) — no rate limiting + let unthrottled_routes = Router::new() + .merge(handlers::health::health_check_router()) + .route( + "/docs/openapi.json", + axum::routing::get(handlers::openapi::openapi_spec), + ) + .with_state(state.clone()); + // Clone jwt_secret for upload auth before protected_routes closure moves it let secret_for_uploads = jwt_secret.clone(); @@ -577,7 +581,7 @@ async fn main() -> anyhow::Result<()> { async move { upload_auth_middleware(secret, req, next).await } })); let app = Router::new() - .nest("/api/v1", public_routes.merge(protected_routes)) + .nest("/api/v1", unthrottled_routes.merge(public_routes).merge(protected_routes)) .nest("/uploads", uploads_router) .layer(cors);