1
1
//! Authentication middleware for the API.
2
2
//!
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:
5
18
//!
6
19
//! `http://<host>:<port>/api/v1/<context>?token=<token>`.
7
20
//!
21
34
//! All the tokes have the same permissions, so it is not possible to have
22
35
//! different permissions for different tokens. The label is only used to
23
36
//! 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.
24
43
use std:: sync:: Arc ;
25
44
26
45
use axum:: extract:: { self } ;
@@ -32,6 +51,8 @@ use torrust_tracker_configuration::AccessTokens;
32
51
33
52
use crate :: v1:: responses:: unhandled_rejection_response;
34
53
54
+ pub const AUTH_BEARER_TOKEN_HEADER_PREFIX : & str = "Bearer" ;
55
+
35
56
/// Container for the `token` extracted from the query params.
36
57
#[ derive( Deserialize , Debug ) ]
37
58
pub struct QueryParams {
@@ -43,16 +64,29 @@ pub struct State {
43
64
pub access_tokens : Arc < AccessTokens > ,
44
65
}
45
66
46
- /// Middleware for authentication using a "token" GET param.
67
+ /// Middleware for authentication.
68
+ ///
47
69
/// The token must be one of the tokens in the tracker [HTTP API configuration](torrust_tracker_configuration::HttpApi).
48
70
pub async fn auth (
49
71
extract:: State ( state) : extract:: State < State > ,
50
72
extract:: Query ( params) : extract:: Query < QueryParams > ,
51
73
request : Request < axum:: body:: Body > ,
52
74
next : Next ,
53
75
) -> 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 ( ) ,
56
90
} ;
57
91
58
92
if !authenticate ( & token, & state. access_tokens ) {
@@ -62,18 +96,50 @@ pub async fn auth(
62
96
next. run ( request) . await
63
97
}
64
98
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
+
65
126
enum AuthError {
66
127
/// Missing token for authentication.
67
128
Unauthorized ,
129
+
68
130
/// Token was provided but it is not valid.
69
131
TokenNotValid ,
132
+
133
+ /// Token was provided but it is not in a format that the server can't understands.
134
+ UnknownTokenProvided ,
70
135
}
71
136
72
137
impl IntoResponse for AuthError {
73
138
fn into_response ( self ) -> Response {
74
139
match self {
75
140
AuthError :: Unauthorized => unauthorized_response ( ) ,
76
141
AuthError :: TokenNotValid => token_not_valid_response ( ) ,
142
+ AuthError :: UnknownTokenProvided => unknown_auth_data_provided_response ( ) ,
77
143
}
78
144
}
79
145
}
@@ -93,3 +159,12 @@ pub fn unauthorized_response() -> Response {
93
159
pub fn token_not_valid_response ( ) -> Response {
94
160
unhandled_rejection_response ( "token not valid" . to_string ( ) )
95
161
}
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