Skip to content

Commit 0a09621

Browse files
authored
Merge branch 'torrust:develop' into develop
2 parents 35d248a + 3d3d0c7 commit 0a09621

File tree

17 files changed

+940
-235
lines changed

17 files changed

+940
-235
lines changed

Cargo.lock

+133-107
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ podman run -it docker.io/torrust/tracker:develop
104104
# Checkout repository into a new folder:
105105
git clone https://github.com/torrust/torrust-tracker.git
106106

107-
# Change into directory and create a empty database file:
107+
# Change into directory and create an empty database file:
108108
cd torrust-tracker
109109
mkdir -p ./storage/tracker/lib/database/
110110
touch ./storage/tracker/lib/database/sqlite3.db
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
3+
# This script is only intended to be used for local development or testing environments.
4+
5+
cargo bench --package torrust-tracker-torrent-repository
6+
7+
cargo bench --package bittorrent-http-tracker-core
8+
9+
cargo bench --package bittorrent-udp-tracker-core

packages/axum-rest-tracker-api-server/src/v1/middlewares/auth.rs

+80-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
//! Authentication middleware for the API.
22
//!
3-
//! It uses a "token" GET param to authenticate the user. URLs must be of the
4-
//! form:
3+
//! It uses a "token" to authenticate the user. The token must be one of the
4+
//! `access_tokens` in the tracker [HTTP API configuration](torrust_tracker_configuration::HttpApi).
5+
//!
6+
//! There are two ways to provide the token:
7+
//!
8+
//! 1. As a `Bearer` token in the `Authorization` header.
9+
//! 2. As a `token` GET param in the URL.
10+
//!
11+
//! Using the `Authorization` header:
12+
//!
13+
//! ```console
14+
//! curl -H "Authorization: Bearer MyAccessToken" http://<host>:<port>/api/v1/<context>
15+
//! ```
16+
//!
17+
//! Using the `token` GET param:
518
//!
619
//! `http://<host>:<port>/api/v1/<context>?token=<token>`.
720
//!
@@ -21,6 +34,12 @@
2134
//! All the tokes have the same permissions, so it is not possible to have
2235
//! different permissions for different tokens. The label is only used to
2336
//! identify the token.
37+
//!
38+
//! NOTICE: The token is not encrypted, so it is recommended to use HTTPS to
39+
//! protect the token from being intercepted.
40+
//!
41+
//! NOTICE: If both the `Authorization` header and the `token` GET param are
42+
//! provided, the `Authorization` header will be used.
2443
use std::sync::Arc;
2544

2645
use axum::extract::{self};
@@ -32,6 +51,8 @@ use torrust_tracker_configuration::AccessTokens;
3251

3352
use crate::v1::responses::unhandled_rejection_response;
3453

54+
pub const AUTH_BEARER_TOKEN_HEADER_PREFIX: &str = "Bearer";
55+
3556
/// Container for the `token` extracted from the query params.
3657
#[derive(Deserialize, Debug)]
3758
pub struct QueryParams {
@@ -43,16 +64,29 @@ pub struct State {
4364
pub access_tokens: Arc<AccessTokens>,
4465
}
4566

46-
/// Middleware for authentication using a "token" GET param.
67+
/// Middleware for authentication.
68+
///
4769
/// The token must be one of the tokens in the tracker [HTTP API configuration](torrust_tracker_configuration::HttpApi).
4870
pub async fn auth(
4971
extract::State(state): extract::State<State>,
5072
extract::Query(params): extract::Query<QueryParams>,
5173
request: Request<axum::body::Body>,
5274
next: Next,
5375
) -> Response {
54-
let Some(token) = params.token else {
55-
return AuthError::Unauthorized.into_response();
76+
let token_from_header = match extract_bearer_token_from_header(&request) {
77+
Ok(token) => token,
78+
Err(err) => return err.into_response(),
79+
};
80+
81+
let token_from_get_param = params.token.clone();
82+
83+
let provided_tokens = (token_from_header, token_from_get_param);
84+
85+
let token = match provided_tokens {
86+
(Some(token_from_header), Some(_token_from_get_param)) => token_from_header,
87+
(Some(token_from_header), None) => token_from_header,
88+
(None, Some(token_from_get_param)) => token_from_get_param,
89+
(None, None) => return AuthError::Unauthorized.into_response(),
5690
};
5791

5892
if !authenticate(&token, &state.access_tokens) {
@@ -62,18 +96,50 @@ pub async fn auth(
6296
next.run(request).await
6397
}
6498

99+
fn extract_bearer_token_from_header(request: &Request<axum::body::Body>) -> Result<Option<String>, AuthError> {
100+
let headers = request.headers();
101+
102+
let header_value = headers
103+
.get(axum::http::header::AUTHORIZATION)
104+
.and_then(|header_value| header_value.to_str().ok());
105+
106+
match header_value {
107+
None => Ok(None),
108+
Some(header_value) => {
109+
if header_value == AUTH_BEARER_TOKEN_HEADER_PREFIX {
110+
// Empty token
111+
return Ok(Some(String::new()));
112+
}
113+
114+
if !header_value.starts_with(&format!("{AUTH_BEARER_TOKEN_HEADER_PREFIX} ").to_string()) {
115+
// Invalid token type. Missing "Bearer" prefix.
116+
return Err(AuthError::UnknownTokenProvided);
117+
}
118+
119+
Ok(header_value
120+
.strip_prefix(&format!("{AUTH_BEARER_TOKEN_HEADER_PREFIX} ").to_string())
121+
.map(std::string::ToString::to_string))
122+
}
123+
}
124+
}
125+
65126
enum AuthError {
66127
/// Missing token for authentication.
67128
Unauthorized,
129+
68130
/// Token was provided but it is not valid.
69131
TokenNotValid,
132+
133+
/// Token was provided but it is not in a format that the server can't understands.
134+
UnknownTokenProvided,
70135
}
71136

72137
impl IntoResponse for AuthError {
73138
fn into_response(self) -> Response {
74139
match self {
75140
AuthError::Unauthorized => unauthorized_response(),
76141
AuthError::TokenNotValid => token_not_valid_response(),
142+
AuthError::UnknownTokenProvided => unknown_auth_data_provided_response(),
77143
}
78144
}
79145
}
@@ -93,3 +159,12 @@ pub fn unauthorized_response() -> Response {
93159
pub fn token_not_valid_response() -> Response {
94160
unhandled_rejection_response("token not valid".to_string())
95161
}
162+
163+
/// `500` error response when the provided token type is not valid.
164+
///
165+
/// The client has provided authentication information that the server does not
166+
/// understand.
167+
#[must_use]
168+
pub fn unknown_auth_data_provided_response() -> Response {
169+
unhandled_rejection_response("unknown token provided".to_string())
170+
}

0 commit comments

Comments
 (0)