Skip to content

Commit 34f2f43

Browse files
committed
refactor: [#727] use the Authentication header in the API client
Instead of passing the `token` via GET param. The server supports both. Since we have not released any version crate for the client yet we can use the header by deafault which is more secure.
1 parent 084beb2 commit 34f2f43

File tree

2 files changed

+89
-31
lines changed

2 files changed

+89
-31
lines changed

packages/axum-rest-tracker-api-server/tests/server/v1/contract/authentication.rs

+29-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod given_that_the_token_is_only_provided_in_the_authentication_header {
22
use hyper::header;
33
use torrust_axum_rest_tracker_api_server::environment::Started;
44
use torrust_rest_tracker_api_client::common::http::Query;
5+
use torrust_rest_tracker_api_client::connection_info::ConnectionInfo;
56
use torrust_rest_tracker_api_client::v1::client::{
67
headers_with_auth_token, headers_with_request_id, Client, AUTH_BEARER_TOKEN_HEADER_PREFIX,
78
};
@@ -80,7 +81,9 @@ mod given_that_the_token_is_only_provided_in_the_authentication_header {
8081
.expect("the auth token is not a valid header value"),
8182
);
8283

83-
let response = Client::new(env.get_connection_info())
84+
let connection_info = ConnectionInfo::anonymous(env.get_connection_info().origin);
85+
86+
let response = Client::new(connection_info)
8487
.unwrap()
8588
.get_request_with_query("stats", Query::default(), Some(headers))
8689
.await;
@@ -99,7 +102,8 @@ mod given_that_the_token_is_only_provided_in_the_query_param {
99102

100103
use torrust_axum_rest_tracker_api_server::environment::Started;
101104
use torrust_rest_tracker_api_client::common::http::{Query, QueryParam};
102-
use torrust_rest_tracker_api_client::v1::client::{headers_with_request_id, Client};
105+
use torrust_rest_tracker_api_client::connection_info::ConnectionInfo;
106+
use torrust_rest_tracker_api_client::v1::client::{headers_with_request_id, Client, TOKEN_PARAM_NAME};
103107
use torrust_tracker_test_helpers::logging::logs_contains_a_line_with;
104108
use torrust_tracker_test_helpers::{configuration, logging};
105109
use uuid::Uuid;
@@ -114,9 +118,15 @@ mod given_that_the_token_is_only_provided_in_the_query_param {
114118

115119
let token = env.get_connection_info().api_token.unwrap();
116120

117-
let response = Client::new(env.get_connection_info())
121+
let connection_info = ConnectionInfo::anonymous(env.get_connection_info().origin);
122+
123+
let response = Client::new(connection_info)
118124
.unwrap()
119-
.get_request_with_query("stats", Query::params([QueryParam::new("token", &token)].to_vec()), None)
125+
.get_request_with_query(
126+
"stats",
127+
Query::params([QueryParam::new(TOKEN_PARAM_NAME, &token)].to_vec()),
128+
None,
129+
)
120130
.await;
121131

122132
assert_eq!(response.status(), 200);
@@ -132,11 +142,13 @@ mod given_that_the_token_is_only_provided_in_the_query_param {
132142

133143
let request_id = Uuid::new_v4();
134144

135-
let response = Client::new(env.get_connection_info())
145+
let connection_info = ConnectionInfo::anonymous(env.get_connection_info().origin);
146+
147+
let response = Client::new(connection_info)
136148
.unwrap()
137149
.get_request_with_query(
138150
"stats",
139-
Query::params([QueryParam::new("token", "")].to_vec()),
151+
Query::params([QueryParam::new(TOKEN_PARAM_NAME, "")].to_vec()),
140152
Some(headers_with_request_id(request_id)),
141153
)
142154
.await;
@@ -159,11 +171,13 @@ mod given_that_the_token_is_only_provided_in_the_query_param {
159171

160172
let request_id = Uuid::new_v4();
161173

162-
let response = Client::new(env.get_connection_info())
174+
let connection_info = ConnectionInfo::anonymous(env.get_connection_info().origin);
175+
176+
let response = Client::new(connection_info)
163177
.unwrap()
164178
.get_request_with_query(
165179
"stats",
166-
Query::params([QueryParam::new("token", "INVALID TOKEN")].to_vec()),
180+
Query::params([QueryParam::new(TOKEN_PARAM_NAME, "INVALID TOKEN")].to_vec()),
167181
Some(headers_with_request_id(request_id)),
168182
)
169183
.await;
@@ -186,8 +200,10 @@ mod given_that_the_token_is_only_provided_in_the_query_param {
186200

187201
let token = env.get_connection_info().api_token.unwrap();
188202

203+
let connection_info = ConnectionInfo::anonymous(env.get_connection_info().origin);
204+
189205
// At the beginning of the query component
190-
let response = Client::new(env.get_connection_info())
206+
let response = Client::new(connection_info)
191207
.unwrap()
192208
.get_request(&format!("torrents?token={token}&limit=1"))
193209
.await;
@@ -210,6 +226,7 @@ mod given_that_not_token_is_provided {
210226

211227
use torrust_axum_rest_tracker_api_server::environment::Started;
212228
use torrust_rest_tracker_api_client::common::http::Query;
229+
use torrust_rest_tracker_api_client::connection_info::ConnectionInfo;
213230
use torrust_rest_tracker_api_client::v1::client::{headers_with_request_id, Client};
214231
use torrust_tracker_test_helpers::logging::logs_contains_a_line_with;
215232
use torrust_tracker_test_helpers::{configuration, logging};
@@ -225,7 +242,9 @@ mod given_that_not_token_is_provided {
225242

226243
let request_id = Uuid::new_v4();
227244

228-
let response = Client::new(env.get_connection_info())
245+
let connection_info = ConnectionInfo::anonymous(env.get_connection_info().origin);
246+
247+
let response = Client::new(connection_info)
229248
.unwrap()
230249
.get_request_with_query("stats", Query::default(), Some(headers_with_request_id(request_id)))
231250
.await;

packages/rest-tracker-api-client/src/v1/client.rs

+60-21
Original file line numberDiff line numberDiff line change
@@ -92,69 +92,108 @@ impl Client {
9292
///
9393
/// Will panic if the request can't be sent
9494
pub async fn post_empty(&self, path: &str, headers: Option<HeaderMap>) -> Response {
95-
let builder = self
96-
.client
97-
.post(self.base_url(path).clone())
98-
.query(&ReqwestQuery::from(self.query_with_token()));
95+
let builder = self.client.post(self.base_url(path).clone());
9996

10097
let builder = match headers {
10198
Some(headers) => builder.headers(headers),
10299
None => builder,
103100
};
104101

102+
let builder = match &self.connection_info.api_token {
103+
Some(token) => builder.header(header::AUTHORIZATION, format!("{AUTH_BEARER_TOKEN_HEADER_PREFIX} {token}")),
104+
None => builder,
105+
};
106+
105107
builder.send().await.unwrap()
106108
}
107109

108110
/// # Panics
109111
///
110112
/// Will panic if the request can't be sent
111113
pub async fn post_form<T: Serialize + ?Sized>(&self, path: &str, form: &T, headers: Option<HeaderMap>) -> Response {
112-
let builder = self
113-
.client
114-
.post(self.base_url(path).clone())
115-
.query(&ReqwestQuery::from(self.query_with_token()))
116-
.json(&form);
114+
let builder = self.client.post(self.base_url(path).clone()).json(&form);
117115

118116
let builder = match headers {
119117
Some(headers) => builder.headers(headers),
120118
None => builder,
121119
};
122120

121+
let builder = match &self.connection_info.api_token {
122+
Some(token) => builder.header(header::AUTHORIZATION, format!("{AUTH_BEARER_TOKEN_HEADER_PREFIX} {token}")),
123+
None => builder,
124+
};
125+
123126
builder.send().await.unwrap()
124127
}
125128

126129
/// # Panics
127130
///
128131
/// Will panic if the request can't be sent
129132
async fn delete(&self, path: &str, headers: Option<HeaderMap>) -> Response {
130-
let builder = self
131-
.client
132-
.delete(self.base_url(path).clone())
133-
.query(&ReqwestQuery::from(self.query_with_token()));
133+
let builder = self.client.delete(self.base_url(path).clone());
134134

135135
let builder = match headers {
136136
Some(headers) => builder.headers(headers),
137137
None => builder,
138138
};
139139

140+
let builder = match &self.connection_info.api_token {
141+
Some(token) => builder.header(header::AUTHORIZATION, format!("{AUTH_BEARER_TOKEN_HEADER_PREFIX} {token}")),
142+
None => builder,
143+
};
144+
140145
builder.send().await.unwrap()
141146
}
142147

148+
/// # Panics
149+
///
150+
/// Will panic if it can't convert the authentication token to a `HeaderValue`.
143151
pub async fn get_request_with_query(&self, path: &str, params: Query, headers: Option<HeaderMap>) -> Response {
144-
get(self.base_url(path), Some(params), headers).await
152+
match &self.connection_info.api_token {
153+
Some(token) => {
154+
let headers = if let Some(headers) = headers {
155+
// Headers provided -> add auth token if not already present
156+
157+
if headers.get(header::AUTHORIZATION).is_some() {
158+
// Auth token already present -> use provided
159+
headers
160+
} else {
161+
let mut headers = headers;
162+
163+
headers.insert(
164+
header::AUTHORIZATION,
165+
format!("{AUTH_BEARER_TOKEN_HEADER_PREFIX} {token}")
166+
.parse()
167+
.expect("the auth token is not a valid header value"),
168+
);
169+
170+
headers
171+
}
172+
} else {
173+
// No headers provided -> create headers with auth token
174+
175+
let mut headers = HeaderMap::new();
176+
177+
headers.insert(
178+
header::AUTHORIZATION,
179+
format!("{AUTH_BEARER_TOKEN_HEADER_PREFIX} {token}")
180+
.parse()
181+
.expect("the auth token is not a valid header value"),
182+
);
183+
184+
headers
185+
};
186+
187+
get(self.base_url(path), Some(params), Some(headers)).await
188+
}
189+
None => get(self.base_url(path), Some(params), headers).await,
190+
}
145191
}
146192

147193
pub async fn get_request(&self, path: &str) -> Response {
148194
get(self.base_url(path), None, None).await
149195
}
150196

151-
fn query_with_token(&self) -> Query {
152-
match &self.connection_info.api_token {
153-
Some(token) => Query::params([QueryParam::new("token", token)].to_vec()),
154-
None => Query::default(),
155-
}
156-
}
157-
158197
fn base_url(&self, path: &str) -> Url {
159198
Url::parse(&format!("{}{}{path}", &self.connection_info.origin, &self.base_path)).unwrap()
160199
}

0 commit comments

Comments
 (0)