Skip to content

Commit f3892cf

Browse files
committed
Merge #208: Axum HTTP tracker: extract Axum extractor for URL path auth key param
a9e3a33 refactor(http): extract Axum extractor for the URL path param key (Jose Celano) Pull request description: Axum extractor to extract the `key` path param from the URLs: ```rust Router::new() // Announce request .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_without_key).with_state(tracker.clone())) .route("/scrape/:key", get(scrape::handle_with_key).with_state(tracker.clone())) // Add extension to get the client IP from the connection info .layer(SecureClientIpSource::ConnectInfo.into_extension()) ``` It extracts the `:key` param. I'm using an extractor because we have a custom error response, and the handlers are much cleaner this way and we remove the duplicate code. Top commit has no ACKs. Tree-SHA512: a414de88ecda5ddba589d7716821238dcfd8fd179ec96fca195578cf0f7c3558949cb403da7845ee36fa52f338c3187ad4b9744e7dc05d92d4e404cc7a8f02c0
2 parents 94fbdeb + a9e3a33 commit f3892cf

File tree

5 files changed

+66
-30
lines changed

5 files changed

+66
-30
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use std::panic::Location;
2+
3+
use axum::async_trait;
4+
use axum::extract::{FromRequestParts, Path};
5+
use axum::http::request::Parts;
6+
use axum::response::{IntoResponse, Response};
7+
8+
use crate::http::axum_implementation::handlers::auth::{self, KeyIdParam};
9+
use crate::http::axum_implementation::responses;
10+
use crate::tracker::auth::KeyId;
11+
12+
pub struct ExtractKeyId(pub KeyId);
13+
14+
#[async_trait]
15+
impl<S> FromRequestParts<S> for ExtractKeyId
16+
where
17+
S: Send + Sync,
18+
{
19+
type Rejection = Response;
20+
21+
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
22+
match Path::<KeyIdParam>::from_request_parts(parts, state).await {
23+
Ok(key_id_param) => {
24+
let Ok(key_id) = key_id_param.0.value().parse::<KeyId>() else {
25+
return Err(responses::error::Error::from(
26+
auth::Error::InvalidKeyFormat {
27+
location: Location::caller()
28+
})
29+
.into_response())
30+
};
31+
Ok(ExtractKeyId(key_id))
32+
}
33+
Err(rejection) => match rejection {
34+
axum::extract::rejection::PathRejection::FailedToDeserializePathParams(_) => {
35+
return Err(responses::error::Error::from(auth::Error::InvalidKeyFormat {
36+
location: Location::caller(),
37+
})
38+
.into_response())
39+
}
40+
axum::extract::rejection::PathRejection::MissingPathParams(_) => {
41+
return Err(responses::error::Error::from(auth::Error::MissingAuthKey {
42+
location: Location::caller(),
43+
})
44+
.into_response())
45+
}
46+
_ => {
47+
return Err(responses::error::Error::from(auth::Error::CannotExtractKeyParam {
48+
location: Location::caller(),
49+
})
50+
.into_response())
51+
}
52+
},
53+
}
54+
}
55+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod announce_request;
2+
pub mod key;
23
pub mod peer_ip;
34
pub mod remote_client_ip;
45
pub mod scrape_request;

src/http/axum_implementation/handlers/announce.rs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,19 @@ use std::panic::Location;
33
use std::sync::Arc;
44

55
use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes};
6-
use axum::extract::{Path, State};
6+
use axum::extract::State;
77
use axum::response::{IntoResponse, Response};
88
use log::debug;
99

10-
use super::auth::KeyIdParam;
1110
use crate::http::axum_implementation::extractors::announce_request::ExtractRequest;
11+
use crate::http::axum_implementation::extractors::key::ExtractKeyId;
1212
use crate::http::axum_implementation::extractors::peer_ip;
1313
use crate::http::axum_implementation::extractors::remote_client_ip::RemoteClientIp;
1414
use crate::http::axum_implementation::handlers::auth;
1515
use crate::http::axum_implementation::requests::announce::{Announce, Compact, Event};
1616
use crate::http::axum_implementation::responses::{self, announce};
1717
use crate::http::axum_implementation::services;
1818
use crate::protocol::clock::{Current, Time};
19-
use crate::tracker::auth::KeyId;
2019
use crate::tracker::peer::Peer;
2120
use crate::tracker::Tracker;
2221

@@ -42,20 +41,11 @@ pub async fn handle_without_key(
4241
pub async fn handle_with_key(
4342
State(tracker): State<Arc<Tracker>>,
4443
ExtractRequest(announce_request): ExtractRequest,
45-
Path(key_id_param): Path<KeyIdParam>,
44+
ExtractKeyId(key_id): ExtractKeyId,
4645
remote_client_ip: RemoteClientIp,
4746
) -> Response {
4847
debug!("http announce request: {:#?}", announce_request);
4948

50-
// todo: extract to Axum extractor. Duplicate code in `scrape` handler.
51-
let Ok(key_id) = key_id_param.value().parse::<KeyId>() else {
52-
return responses::error::Error::from(
53-
auth::Error::InvalidKeyFormat {
54-
location: Location::caller()
55-
})
56-
.into_response()
57-
};
58-
5949
match tracker.authenticate(&key_id).await {
6050
Ok(_) => (),
6151
Err(error) => return responses::error::Error::from(error).into_response(),

src/http/axum_implementation/handlers/auth.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ impl KeyIdParam {
1818

1919
#[derive(Debug, Error)]
2020
pub enum Error {
21-
#[error("Missing authentication key for private tracker. Error in {location}")]
21+
#[error("Missing authentication key param for private tracker. Error in {location}")]
2222
MissingAuthKey { location: &'static Location<'static> },
23-
#[error("Invalid format authentication key. Error in {location}")]
23+
#[error("Invalid format for authentication key param. Error in {location}")]
2424
InvalidKeyFormat { location: &'static Location<'static> },
25+
#[error("Cannot extract authentication key param from URL path. Error in {location}")]
26+
CannotExtractKeyParam { location: &'static Location<'static> },
2527
}
2628

2729
impl From<Error> for responses::error::Error {

src/http/axum_implementation/handlers/scrape.rs

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
1-
use std::panic::Location;
21
use std::sync::Arc;
32

4-
use axum::extract::{Path, State};
3+
use axum::extract::State;
54
use axum::response::{IntoResponse, Response};
65
use log::debug;
76

8-
use super::auth::KeyIdParam;
7+
use crate::http::axum_implementation::extractors::key::ExtractKeyId;
98
use crate::http::axum_implementation::extractors::peer_ip;
109
use crate::http::axum_implementation::extractors::remote_client_ip::RemoteClientIp;
1110
use crate::http::axum_implementation::extractors::scrape_request::ExtractRequest;
12-
use crate::http::axum_implementation::handlers::auth;
1311
use crate::http::axum_implementation::requests::scrape::Scrape;
1412
use crate::http::axum_implementation::{responses, services};
15-
use crate::tracker::auth::KeyId;
1613
use crate::tracker::Tracker;
1714

1815
#[allow(clippy::unused_async)]
@@ -34,20 +31,11 @@ pub async fn handle_without_key(
3431
pub async fn handle_with_key(
3532
State(tracker): State<Arc<Tracker>>,
3633
ExtractRequest(scrape_request): ExtractRequest,
37-
Path(key_id_param): Path<KeyIdParam>,
34+
ExtractKeyId(key_id): ExtractKeyId,
3835
remote_client_ip: RemoteClientIp,
3936
) -> Response {
4037
debug!("http scrape request: {:#?}", &scrape_request);
4138

42-
// todo: extract to Axum extractor. Duplicate code in `announce` handler.
43-
let Ok(key_id) = key_id_param.value().parse::<KeyId>() else {
44-
return responses::error::Error::from(
45-
auth::Error::InvalidKeyFormat {
46-
location: Location::caller()
47-
})
48-
.into_response()
49-
};
50-
5139
match tracker.authenticate(&key_id).await {
5240
Ok(_) => (),
5341
Err(_) => return handle_fake_scrape(&tracker, &scrape_request, &remote_client_ip).await,

0 commit comments

Comments
 (0)