diff --git a/src/http/axum_implementation/handlers/announce.rs b/src/http/axum_implementation/handlers/announce.rs index d5fa7f3a4..3ad11df51 100644 --- a/src/http/axum_implementation/handlers/announce.rs +++ b/src/http/axum_implementation/handlers/announce.rs @@ -1,39 +1,70 @@ use std::net::{IpAddr, SocketAddr}; +use std::panic::Location; use std::sync::Arc; use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes}; -use axum::extract::State; +use axum::extract::{Path, State}; use axum::response::{IntoResponse, Response}; use log::debug; use crate::http::axum_implementation::extractors::announce_request::ExtractRequest; use crate::http::axum_implementation::extractors::peer_ip; use crate::http::axum_implementation::extractors::remote_client_ip::RemoteClientIp; +use crate::http::axum_implementation::handlers::auth; use crate::http::axum_implementation::requests::announce::{Announce, Compact, Event}; -use crate::http::axum_implementation::responses::announce; +use crate::http::axum_implementation::responses::{self, announce}; use crate::http::axum_implementation::services; use crate::protocol::clock::{Current, Time}; +use crate::tracker::auth::KeyId; use crate::tracker::peer::Peer; use crate::tracker::Tracker; #[allow(clippy::unused_async)] -pub async fn handle( +pub async fn handle_without_key( State(tracker): State>, ExtractRequest(announce_request): ExtractRequest, remote_client_ip: RemoteClientIp, ) -> Response { debug!("http announce request: {:#?}", announce_request); - let peer_ip = match peer_ip::resolve(tracker.config.on_reverse_proxy, &remote_client_ip) { + if tracker.is_private() { + return responses::error::Error::from(auth::Error::MissingAuthKey { + location: Location::caller(), + }) + .into_response(); + } + + handle(&tracker, &announce_request, &remote_client_ip).await +} + +#[allow(clippy::unused_async)] +pub async fn handle_with_key( + State(tracker): State>, + ExtractRequest(announce_request): ExtractRequest, + Path(key_id): Path, + remote_client_ip: RemoteClientIp, +) -> Response { + debug!("http announce request: {:#?}", announce_request); + + match auth::authenticate(&key_id, &tracker).await { + Ok(_) => (), + Err(error) => return responses::error::Error::from(error).into_response(), + } + + handle(&tracker, &announce_request, &remote_client_ip).await +} + +async fn handle(tracker: &Arc, announce_request: &Announce, remote_client_ip: &RemoteClientIp) -> Response { + let peer_ip = match peer_ip::resolve(tracker.config.on_reverse_proxy, remote_client_ip) { Ok(peer_ip) => peer_ip, Err(err) => return err, }; - let mut peer = peer_from_request(&announce_request, &peer_ip); + let mut peer = peer_from_request(announce_request, &peer_ip); let announce_data = services::announce::invoke(tracker.clone(), announce_request.info_hash, &mut peer).await; - match announce_request.compact { + match &announce_request.compact { Some(compact) => match compact { Compact::Accepted => announce::Compact::from(announce_data).into_response(), Compact::NotAccepted => announce::NonCompact::from(announce_data).into_response(), diff --git a/src/http/axum_implementation/handlers/auth.rs b/src/http/axum_implementation/handlers/auth.rs new file mode 100644 index 000000000..13f5b27e6 --- /dev/null +++ b/src/http/axum_implementation/handlers/auth.rs @@ -0,0 +1,41 @@ +use std::panic::Location; +use std::sync::Arc; + +use thiserror::Error; + +use crate::http::axum_implementation::responses; +use crate::tracker::auth::{self, KeyId}; +use crate::tracker::Tracker; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Missing authentication key for private tracker. Error in {location}")] + MissingAuthKey { location: &'static Location<'static> }, +} + +/// # Errors +/// +/// Will return an error if the the authentication key cannot be verified. +pub async fn authenticate(key_id: &KeyId, tracker: &Arc) -> Result<(), auth::Error> { + if tracker.is_private() { + tracker.verify_auth_key(key_id).await + } else { + Ok(()) + } +} + +impl From for responses::error::Error { + fn from(err: Error) -> Self { + responses::error::Error { + failure_reason: format!("Authentication error: {err}"), + } + } +} + +impl From for responses::error::Error { + fn from(err: auth::Error) -> Self { + responses::error::Error { + failure_reason: format!("Authentication error: {err}"), + } + } +} diff --git a/src/http/axum_implementation/handlers/mod.rs b/src/http/axum_implementation/handlers/mod.rs index 4e6849534..0d8aa7f52 100644 --- a/src/http/axum_implementation/handlers/mod.rs +++ b/src/http/axum_implementation/handlers/mod.rs @@ -1,3 +1,4 @@ pub mod announce; +pub mod auth; pub mod scrape; pub mod status; diff --git a/src/http/axum_implementation/routes.rs b/src/http/axum_implementation/routes.rs index 1d4d67e73..646dd0aa3 100644 --- a/src/http/axum_implementation/routes.rs +++ b/src/http/axum_implementation/routes.rs @@ -12,7 +12,8 @@ pub fn router(tracker: &Arc) -> Router { // Status .route("/status", get(status::handle)) // Announce request - .route("/announce", get(announce::handle).with_state(tracker.clone())) + .route("/announce", get(announce::handle_without_key).with_state(tracker.clone())) + .route("/announce/:key", get(announce::handle_with_key).with_state(tracker.clone())) // Scrape request .route("/scrape", get(scrape::handle).with_state(tracker.clone())) // Add extension to get the client IP from the connection info diff --git a/tests/http/asserts.rs b/tests/http/asserts.rs index cd45571da..0d5441f89 100644 --- a/tests/http/asserts.rs +++ b/tests/http/asserts.rs @@ -140,22 +140,6 @@ pub async fn assert_torrent_not_in_whitelist_error_response(response: Response) assert_bencoded_error(&response.text().await.unwrap(), "is not whitelisted", Location::caller()); } -pub async fn assert_peer_not_authenticated_error_response(response: Response) { - assert_eq!(response.status(), 200); - - assert_bencoded_error( - &response.text().await.unwrap(), - "The peer is not authenticated", - Location::caller(), - ); -} - -pub async fn assert_invalid_authentication_key_error_response(response: Response) { - assert_eq!(response.status(), 200); - - assert_bencoded_error(&response.text().await.unwrap(), "is not valid", Location::caller()); -} - pub async fn assert_could_not_find_remote_address_on_xff_header_error_response(response: Response) { assert_eq!(response.status(), 200); @@ -199,3 +183,9 @@ pub async fn assert_cannot_parse_query_params_error_response(response: Response, Location::caller(), ); } + +pub async fn assert_authentication_error_response(response: Response) { + assert_eq!(response.status(), 200); + + assert_bencoded_error(&response.text().await.unwrap(), "Authentication error", Location::caller()); +} diff --git a/tests/http/asserts_warp.rs b/tests/http/asserts_warp.rs index 6bda82f6c..d1a936efa 100644 --- a/tests/http/asserts_warp.rs +++ b/tests/http/asserts_warp.rs @@ -1,7 +1,10 @@ +use std::panic::Location; + /// todo: this mod should be removed when we remove the Warp implementation for the HTTP tracker. use reqwest::Response; use super::responses::announce_warp::WarpAnnounce; +use crate::http::asserts::assert_bencoded_error; pub async fn assert_warp_announce_response(response: Response, expected_announce_response: &WarpAnnounce) { assert_eq!(response.status(), 200); @@ -13,3 +16,19 @@ pub async fn assert_warp_announce_response(response: Response, expected_announce assert_eq!(announce_response, *expected_announce_response); } + +pub async fn assert_warp_peer_not_authenticated_error_response(response: Response) { + assert_eq!(response.status(), 200); + + assert_bencoded_error( + &response.text().await.unwrap(), + "The peer is not authenticated", + Location::caller(), + ); +} + +pub async fn assert_warp_invalid_authentication_key_error_response(response: Response) { + assert_eq!(response.status(), 200); + + assert_bencoded_error(&response.text().await.unwrap(), "is not valid", Location::caller()); +} diff --git a/tests/http_tracker.rs b/tests/http_tracker.rs index a341e13ed..28ed252e9 100644 --- a/tests/http_tracker.rs +++ b/tests/http_tracker.rs @@ -1085,9 +1085,9 @@ mod warp_http_tracker_server { use torrust_tracker::protocol::info_hash::InfoHash; use torrust_tracker::tracker::auth::KeyId; - use crate::http::asserts::{ - assert_invalid_authentication_key_error_response, assert_is_announce_response, - assert_peer_not_authenticated_error_response, + use crate::http::asserts::assert_is_announce_response; + use crate::http::asserts_warp::{ + assert_warp_invalid_authentication_key_error_response, assert_warp_peer_not_authenticated_error_response, }; use crate::http::client::Client; use crate::http::requests::announce::QueryBuilder; @@ -1120,7 +1120,7 @@ mod warp_http_tracker_server { .announce(&QueryBuilder::default().with_info_hash(&info_hash).query()) .await; - assert_peer_not_authenticated_error_response(response).await; + assert_warp_peer_not_authenticated_error_response(response).await; } #[tokio::test] @@ -1134,7 +1134,7 @@ mod warp_http_tracker_server { .announce(&QueryBuilder::default().query()) .await; - assert_invalid_authentication_key_error_response(response).await; + assert_warp_invalid_authentication_key_error_response(response).await; } } @@ -2539,16 +2539,12 @@ mod axum_http_tracker_server { use torrust_tracker::protocol::info_hash::InfoHash; use torrust_tracker::tracker::auth::KeyId; - use crate::http::asserts::{ - assert_invalid_authentication_key_error_response, assert_is_announce_response, - assert_peer_not_authenticated_error_response, - }; + use crate::http::asserts::{assert_authentication_error_response, assert_is_announce_response}; use crate::http::client::Client; use crate::http::requests::announce::QueryBuilder; use crate::http::server::start_private_http_tracker; - //#[tokio::test] - #[allow(dead_code)] + #[tokio::test] async fn should_respond_to_authenticated_peers() { let http_tracker_server = start_private_http_tracker(Version::Axum).await; @@ -2565,8 +2561,7 @@ mod axum_http_tracker_server { assert_is_announce_response(response).await; } - //#[tokio::test] - #[allow(dead_code)] + #[tokio::test] async fn should_fail_if_the_peer_has_not_provided_the_authentication_key() { let http_tracker_server = start_private_http_tracker(Version::Axum).await; @@ -2576,11 +2571,10 @@ mod axum_http_tracker_server { .announce(&QueryBuilder::default().with_info_hash(&info_hash).query()) .await; - assert_peer_not_authenticated_error_response(response).await; + assert_authentication_error_response(response).await; } - //#[tokio::test] - #[allow(dead_code)] + #[tokio::test] async fn should_fail_if_the_peer_authentication_key_is_not_valid() { let http_tracker_server = start_private_http_tracker(Version::Axum).await; @@ -2591,7 +2585,7 @@ mod axum_http_tracker_server { .announce(&QueryBuilder::default().query()) .await; - assert_invalid_authentication_key_error_response(response).await; + assert_authentication_error_response(response).await; } }