diff --git a/src/middleware/app.rs b/src/middleware/app.rs index fc24f4aa69f..24ca3e78dab 100644 --- a/src/middleware/app.rs +++ b/src/middleware/app.rs @@ -1,4 +1,8 @@ use super::prelude::*; +use axum::extract::State; +use axum::middleware::Next; +use axum::response::Response; +use http::Request; use crate::app::AppState; @@ -20,6 +24,17 @@ impl Middleware for AppMiddleware { } } +/// `axum` middleware that injects the `AppState` instance into the `Request` extensions. +pub async fn add_app_state_extension( + State(app_state): State, + mut request: Request, + next: Next, +) -> Response { + request.extensions_mut().insert(app_state); + + next.run(request).await +} + /// Adds an `app()` method to the `Request` type returning the global `App` instance pub trait RequestApp { fn app(&self) -> &AppState; diff --git a/src/router.rs b/src/router.rs index d21db7814c8..ae62c21f4ec 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,23 +1,191 @@ -use axum::routing::get; +use axum::handler::Handler as AxumHandler; +use axum::middleware::from_fn_with_state; +use axum::routing::{delete, get, post, put}; use axum::Router; use conduit::{Handler, HandlerResult, RequestExt}; -use conduit_axum::CauseField; +use conduit_axum::{CauseField, ConduitAxumHandler}; use conduit_router::{RouteBuilder, RoutePattern}; use crate::app::AppState; use crate::controllers::*; -use crate::middleware::app::RequestApp; +use crate::middleware::app::{add_app_state_extension, RequestApp}; use crate::util::errors::{AppError, RouteBlocked}; use crate::util::EndpointResult; use crate::Env; pub fn build_axum_router(state: AppState) -> Router { + let conduit = |handler| { + ConduitAxumHandler::wrap(C(handler)) + .layer(from_fn_with_state(state.clone(), add_app_state_extension)) + }; + let mut router = Router::new() + // Route used by both `cargo search` and the frontend + .route("/api/v1/crates", get(conduit(krate::search::search))) + // Routes used by `cargo` + .route("/api/v1/crates/new", put(conduit(krate::publish::publish))) + .route( + "/api/v1/crates/:crate_id/owners", + get(conduit(krate::owners::owners)) + .put(conduit(krate::owners::add_owners)) + .delete(conduit(krate::owners::remove_owners)), + ) + .route( + "/api/v1/crates/:crate_id/:version/yank", + delete(conduit(version::yank::yank)), + ) + .route( + "/api/v1/crates/:crate_id/:version/unyank", + put(conduit(version::yank::unyank)), + ) + .route( + "/api/v1/crates/:crate_id/:version/download", + get(conduit(version::downloads::download)), + ) + // Routes that appear to be unused + .route("/api/v1/versions", get(conduit(version::deprecated::index))) + .route( + "/api/v1/versions/:version_id", + get(conduit(version::deprecated::show_by_id)), + ) + // Routes used by the frontend + .route( + "/api/v1/crates/:crate_id", + get(conduit(krate::metadata::show)), + ) + .route( + "/api/v1/crates/:crate_id/:version", + get(conduit(version::metadata::show)), + ) + .route( + "/api/v1/crates/:crate_id/:version/readme", + get(conduit(krate::metadata::readme)), + ) + .route( + "/api/v1/crates/:crate_id/:version/dependencies", + get(conduit(version::metadata::dependencies)), + ) + .route( + "/api/v1/crates/:crate_id/:version/downloads", + get(conduit(version::downloads::downloads)), + ) + .route( + "/api/v1/crates/:crate_id/:version/authors", + get(conduit(version::metadata::authors)), + ) + .route( + "/api/v1/crates/:crate_id/downloads", + get(conduit(krate::downloads::downloads)), + ) + .route( + "/api/v1/crates/:crate_id/versions", + get(conduit(krate::metadata::versions)), + ) + .route( + "/api/v1/crates/:crate_id/follow", + put(conduit(krate::follow::follow)).delete(conduit(krate::follow::unfollow)), + ) + .route( + "/api/v1/crates/:crate_id/following", + get(conduit(krate::follow::following)), + ) + .route( + "/api/v1/crates/:crate_id/owner_team", + get(conduit(krate::owners::owner_team)), + ) + .route( + "/api/v1/crates/:crate_id/owner_user", + get(conduit(krate::owners::owner_user)), + ) + .route( + "/api/v1/crates/:crate_id/reverse_dependencies", + get(conduit(krate::metadata::reverse_dependencies)), + ) + .route("/api/v1/keywords", get(conduit(keyword::index))) + .route("/api/v1/keywords/:keyword_id", get(keyword::show)) + .route("/api/v1/categories", get(conduit(category::index))) + .route( + "/api/v1/categories/:category_id", + get(conduit(category::show)), + ) + .route("/api/v1/category_slugs", get(conduit(category::slugs))) + .route( + "/api/v1/users/:user_id", + get(conduit(user::other::show)).put(conduit(user::me::update_user)), + ) + .route( + "/api/v1/users/:user_id/stats", + get(conduit(user::other::stats)), + ) + .route("/api/v1/teams/:team_id", get(conduit(team::show_team))) + .route("/api/v1/me", get(conduit(user::me::me))) + .route("/api/v1/me/updates", get(conduit(user::me::updates))) + .route( + "/api/v1/me/tokens", + get(conduit(token::list)).put(conduit(token::new)), + ) + .route("/api/v1/me/tokens/:id", delete(conduit(token::revoke))) + .route( + "/api/v1/tokens/current", + delete(conduit(token::revoke_current)), + ) + .route( + "/api/v1/me/crate_owner_invitations", + get(conduit(crate_owner_invitation::list)), + ) + .route( + "/api/v1/me/crate_owner_invitations/:crate_id", + put(conduit(crate_owner_invitation::handle_invite)), + ) + .route( + "/api/v1/me/crate_owner_invitations/accept/:token", + put(conduit(crate_owner_invitation::handle_invite_with_token)), + ) + .route( + "/api/v1/me/email_notifications", + put(conduit(user::me::update_email_notifications)), + ) + .route("/api/v1/summary", get(conduit(krate::metadata::summary))) + .route( + "/api/v1/confirm/:email_token", + put(conduit(user::me::confirm_user_email)), + ) + .route( + "/api/v1/users/:user_id/resend", + put(conduit(user::me::regenerate_token_and_send)), + ) .route( "/api/v1/site_metadata", get(site_metadata::show_deployed_sha), ) - .route("/api/v1/keywords/:keyword_id", get(keyword::show)); + // Session management + .route( + "/api/private/session/begin", + get(conduit(user::session::begin)), + ) + .route( + "/api/private/session/authorize", + get(conduit(user::session::authorize)), + ) + .route( + "/api/private/session", + delete(conduit(user::session::logout)), + ) + // Metrics + .route( + "/api/private/metrics/:kind", + get(conduit(metrics::prometheus)), + ) + // Crate ownership invitations management in the frontend + .route( + "/api/private/crate_owner_invitations", + get(conduit(crate_owner_invitation::private_list)), + ) + // Alerts from GitHub scanning for exposed API tokens + .route( + "/api/github/secret-scanning/verify", + post(conduit(github::secret_scanning::verify)), + ); // Only serve the local checkout of the git index in development mode. // In production, for crates.io, cargo gets the index from @@ -33,157 +201,7 @@ pub fn build_axum_router(state: AppState) -> Router { } pub fn build_router() -> RouteBuilder { - let mut router = RouteBuilder::new(); - - // Route used by both `cargo search` and the frontend - router.get("/api/v1/crates", C(krate::search::search)); - - // Routes used by `cargo` - router.put("/api/v1/crates/new", C(krate::publish::publish)); - router.get("/api/v1/crates/:crate_id/owners", C(krate::owners::owners)); - router.put( - "/api/v1/crates/:crate_id/owners", - C(krate::owners::add_owners), - ); - router.delete( - "/api/v1/crates/:crate_id/owners", - C(krate::owners::remove_owners), - ); - router.delete( - "/api/v1/crates/:crate_id/:version/yank", - C(version::yank::yank), - ); - router.put( - "/api/v1/crates/:crate_id/:version/unyank", - C(version::yank::unyank), - ); - router.get( - "/api/v1/crates/:crate_id/:version/download", - C(version::downloads::download), - ); - - // Routes that appear to be unused - router.get("/api/v1/versions", C(version::deprecated::index)); - router.get( - "/api/v1/versions/:version_id", - C(version::deprecated::show_by_id), - ); - - // Routes used by the frontend - router.get("/api/v1/crates/:crate_id", C(krate::metadata::show)); - router.get( - "/api/v1/crates/:crate_id/:version", - C(version::metadata::show), - ); - router.get( - "/api/v1/crates/:crate_id/:version/readme", - C(krate::metadata::readme), - ); - router.get( - "/api/v1/crates/:crate_id/:version/dependencies", - C(version::metadata::dependencies), - ); - router.get( - "/api/v1/crates/:crate_id/:version/downloads", - C(version::downloads::downloads), - ); - router.get( - "/api/v1/crates/:crate_id/:version/authors", - C(version::metadata::authors), - ); - router.get( - "/api/v1/crates/:crate_id/downloads", - C(krate::downloads::downloads), - ); - router.get( - "/api/v1/crates/:crate_id/versions", - C(krate::metadata::versions), - ); - router.put("/api/v1/crates/:crate_id/follow", C(krate::follow::follow)); - router.delete( - "/api/v1/crates/:crate_id/follow", - C(krate::follow::unfollow), - ); - router.get( - "/api/v1/crates/:crate_id/following", - C(krate::follow::following), - ); - router.get( - "/api/v1/crates/:crate_id/owner_team", - C(krate::owners::owner_team), - ); - router.get( - "/api/v1/crates/:crate_id/owner_user", - C(krate::owners::owner_user), - ); - router.get( - "/api/v1/crates/:crate_id/reverse_dependencies", - C(krate::metadata::reverse_dependencies), - ); - router.get("/api/v1/keywords", C(keyword::index)); - router.get("/api/v1/categories", C(category::index)); - router.get("/api/v1/categories/:category_id", C(category::show)); - router.get("/api/v1/category_slugs", C(category::slugs)); - router.get("/api/v1/users/:user_id", C(user::other::show)); - router.put("/api/v1/users/:user_id", C(user::me::update_user)); - router.get("/api/v1/users/:user_id/stats", C(user::other::stats)); - router.get("/api/v1/teams/:team_id", C(team::show_team)); - router.get("/api/v1/me", C(user::me::me)); - router.get("/api/v1/me/updates", C(user::me::updates)); - router.get("/api/v1/me/tokens", C(token::list)); - router.put("/api/v1/me/tokens", C(token::new)); - router.delete("/api/v1/me/tokens/:id", C(token::revoke)); - router.delete("/api/v1/tokens/current", C(token::revoke_current)); - router.get( - "/api/v1/me/crate_owner_invitations", - C(crate_owner_invitation::list), - ); - router.put( - "/api/v1/me/crate_owner_invitations/:crate_id", - C(crate_owner_invitation::handle_invite), - ); - router.put( - "/api/v1/me/crate_owner_invitations/accept/:token", - C(crate_owner_invitation::handle_invite_with_token), - ); - router.put( - "/api/v1/me/email_notifications", - C(user::me::update_email_notifications), - ); - router.get("/api/v1/summary", C(krate::metadata::summary)); - router.put( - "/api/v1/confirm/:email_token", - C(user::me::confirm_user_email), - ); - router.put( - "/api/v1/users/:user_id/resend", - C(user::me::regenerate_token_and_send), - ); - - // Session management - router.get("/api/private/session/begin", C(user::session::begin)); - router.get( - "/api/private/session/authorize", - C(user::session::authorize), - ); - router.delete("/api/private/session", C(user::session::logout)); - - // Metrics - router.get("/api/private/metrics/:kind", C(metrics::prometheus)); - - // Crate ownership invitations management in the frontend - router.get( - "/api/private/crate_owner_invitations", - C(crate_owner_invitation::private_list), - ); - - // Alerts from GitHub scanning for exposed API tokens - router.post( - "/api/github/secret-scanning/verify", - C(github::secret_scanning::verify), - ); - - router + RouteBuilder::new() } struct C(pub fn(&mut dyn RequestExt) -> EndpointResult);