Skip to content

Commit d3b5b15

Browse files
committed
refactor(api): [#183] Axum API, user context, ban user
1 parent 9564dec commit d3b5b15

File tree

15 files changed

+236
-24
lines changed

15 files changed

+236
-24
lines changed

src/auth.rs

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ use actix_web::HttpRequest;
55
use crate::errors::ServiceError;
66
use crate::models::user::{UserClaims, UserCompact, UserId};
77
use crate::services::authentication::JsonWebToken;
8+
use crate::web::api::v1::extractors::bearer_token::BearerToken;
9+
10+
// todo: refactor this after finishing migration to Axum.
11+
// - Extract service to handle Json Web Tokens: `new`, `sign_jwt`, `verify_jwt`.
12+
// - Move the rest to `src/web/api/v1/auth.rs`. It's a helper for Axum handlers
13+
// to get user id from request.
814

915
pub struct Authentication {
1016
json_web_token: Arc<JsonWebToken>,
@@ -30,13 +36,25 @@ impl Authentication {
3036
self.json_web_token.verify(token).await
3137
}
3238

33-
/// Get Claims from Request
39+
// Begin ActixWeb
40+
41+
/// Get User id from `ActixWeb` Request
42+
///
43+
/// # Errors
44+
///
45+
/// This function will return an error if it can get claims from the request
46+
pub async fn get_user_id_from_actix_web_request(&self, req: &HttpRequest) -> Result<UserId, ServiceError> {
47+
let claims = self.get_claims_from_actix_web_request(req).await?;
48+
Ok(claims.user.user_id)
49+
}
50+
51+
/// Get Claims from `ActixWeb` Request
3452
///
3553
/// # Errors
3654
///
37-
/// This function will return an `ServiceError::TokenNotFound` if `HeaderValue` is `None`
38-
/// This function will pass through the `ServiceError::TokenInvalid` if unable to verify the JWT.
39-
pub async fn get_claims_from_request(&self, req: &HttpRequest) -> Result<UserClaims, ServiceError> {
55+
/// - Return an `ServiceError::TokenNotFound` if `HeaderValue` is `None`.
56+
/// - Pass through the `ServiceError::TokenInvalid` if unable to verify the JWT.
57+
async fn get_claims_from_actix_web_request(&self, req: &HttpRequest) -> Result<UserClaims, ServiceError> {
4058
match req.headers().get("Authorization") {
4159
Some(auth) => {
4260
let split: Vec<&str> = auth
@@ -55,13 +73,37 @@ impl Authentication {
5573
}
5674
}
5775

58-
/// Get User id from Request
76+
// End ActixWeb
77+
78+
// Begin Axum
79+
80+
/// Get User id from bearer token
5981
///
6082
/// # Errors
6183
///
6284
/// This function will return an error if it can get claims from the request
63-
pub async fn get_user_id_from_request(&self, req: &HttpRequest) -> Result<UserId, ServiceError> {
64-
let claims = self.get_claims_from_request(req).await?;
85+
pub async fn get_user_id_from_bearer_token(&self, maybe_token: &Option<BearerToken>) -> Result<UserId, ServiceError> {
86+
let claims = self.get_claims_from_bearer_token(maybe_token).await?;
6587
Ok(claims.user.user_id)
6688
}
89+
90+
/// Get Claims from bearer token
91+
///
92+
/// # Errors
93+
///
94+
/// This function will:
95+
///
96+
/// - Return an `ServiceError::TokenNotFound` if `HeaderValue` is `None`.
97+
/// - Pass through the `ServiceError::TokenInvalid` if unable to verify the JWT.
98+
async fn get_claims_from_bearer_token(&self, maybe_token: &Option<BearerToken>) -> Result<UserClaims, ServiceError> {
99+
match maybe_token {
100+
Some(token) => match self.verify_jwt(&token.value()).await {
101+
Ok(claims) => Ok(claims),
102+
Err(e) => Err(e),
103+
},
104+
None => Err(ServiceError::TokenNotFound),
105+
}
106+
}
107+
108+
// End Axum
67109
}

src/routes/category.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub struct Category {
4141
/// This function will return an error if unable to get user.
4242
/// This function will return an error if unable to insert into the database the new category.
4343
pub async fn add(req: HttpRequest, payload: web::Json<Category>, app_data: WebAppData) -> ServiceResult<impl Responder> {
44-
let user_id = app_data.auth.get_user_id_from_request(&req).await?;
44+
let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?;
4545

4646
let _category_id = app_data.category_service.add_category(&payload.name, &user_id).await?;
4747

@@ -61,7 +61,7 @@ pub async fn delete(req: HttpRequest, payload: web::Json<Category>, app_data: We
6161
// And we should use the ID instead of the name, because the name could change
6262
// or we could add support for multiple languages.
6363

64-
let user_id = app_data.auth.get_user_id_from_request(&req).await?;
64+
let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?;
6565

6666
app_data.category_service.delete_category(&payload.name, &user_id).await?;
6767

src/routes/proxy.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub fn init(cfg: &mut web::ServiceConfig) {
2121
///
2222
/// This function will return `Ok` only for now.
2323
pub async fn get_proxy_image(req: HttpRequest, app_data: WebAppData, path: web::Path<String>) -> ServiceResult<impl Responder> {
24-
let user_id = app_data.auth.get_user_id_from_request(&req).await.ok();
24+
let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await.ok();
2525

2626
match user_id {
2727
Some(user_id) => {

src/routes/settings.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub fn init(cfg: &mut web::ServiceConfig) {
2525
///
2626
/// This function will return an error if unable to get user from database.
2727
pub async fn get_all_handler(req: HttpRequest, app_data: WebAppData) -> ServiceResult<impl Responder> {
28-
let user_id = app_data.auth.get_user_id_from_request(&req).await?;
28+
let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?;
2929

3030
let all_settings = app_data.settings_service.get_all(&user_id).await?;
3131

@@ -46,7 +46,7 @@ pub async fn update_handler(
4646
payload: web::Json<config::TorrustBackend>,
4747
app_data: WebAppData,
4848
) -> ServiceResult<impl Responder> {
49-
let user_id = app_data.auth.get_user_id_from_request(&req).await?;
49+
let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?;
5050

5151
let new_settings = app_data.settings_service.update_all(payload.into_inner(), &user_id).await?;
5252

src/routes/tag.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ pub struct Create {
4444
/// * Get the compact user from the user id.
4545
/// * Add the new tag to the database.
4646
pub async fn create(req: HttpRequest, payload: web::Json<Create>, app_data: WebAppData) -> ServiceResult<impl Responder> {
47-
let user_id = app_data.auth.get_user_id_from_request(&req).await?;
47+
let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?;
4848

4949
app_data.tag_service.add_tag(&payload.name, &user_id).await?;
5050

@@ -68,7 +68,7 @@ pub struct Delete {
6868
/// * Get the compact user from the user id.
6969
/// * Delete the tag from the database.
7070
pub async fn delete(req: HttpRequest, payload: web::Json<Delete>, app_data: WebAppData) -> ServiceResult<impl Responder> {
71-
let user_id = app_data.auth.get_user_id_from_request(&req).await?;
71+
let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?;
7272

7373
app_data.tag_service.delete_tag(&payload.tag_id, &user_id).await?;
7474

src/routes/torrent.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub struct Update {
7777
/// This function will return an error if there was a problem uploading the
7878
/// torrent.
7979
pub async fn upload_torrent_handler(req: HttpRequest, payload: Multipart, app_data: WebAppData) -> ServiceResult<impl Responder> {
80-
let user_id = app_data.auth.get_user_id_from_request(&req).await?;
80+
let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?;
8181

8282
let torrent_request = get_torrent_request_from_payload(payload).await?;
8383

@@ -99,7 +99,7 @@ pub async fn upload_torrent_handler(req: HttpRequest, payload: Multipart, app_da
9999
/// Returns `ServiceError::BadRequest` if the torrent info-hash is invalid.
100100
pub async fn download_torrent_handler(req: HttpRequest, app_data: WebAppData) -> ServiceResult<impl Responder> {
101101
let info_hash = get_torrent_info_hash_from_request(&req)?;
102-
let user_id = app_data.auth.get_user_id_from_request(&req).await.ok();
102+
let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await.ok();
103103

104104
let torrent = app_data.torrent_service.get_torrent(&info_hash, user_id).await?;
105105

@@ -115,7 +115,7 @@ pub async fn download_torrent_handler(req: HttpRequest, app_data: WebAppData) ->
115115
/// This function will return an error if unable to get torrent info.
116116
pub async fn get_torrent_info_handler(req: HttpRequest, app_data: WebAppData) -> ServiceResult<impl Responder> {
117117
let info_hash = get_torrent_info_hash_from_request(&req)?;
118-
let user_id = app_data.auth.get_user_id_from_request(&req).await.ok();
118+
let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await.ok();
119119

120120
let torrent_response = app_data.torrent_service.get_torrent_info(&info_hash, user_id).await?;
121121

@@ -137,7 +137,7 @@ pub async fn update_torrent_info_handler(
137137
app_data: WebAppData,
138138
) -> ServiceResult<impl Responder> {
139139
let info_hash = get_torrent_info_hash_from_request(&req)?;
140-
let user_id = app_data.auth.get_user_id_from_request(&req).await?;
140+
let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?;
141141

142142
let torrent_response = app_data
143143
.torrent_service
@@ -158,7 +158,7 @@ pub async fn update_torrent_info_handler(
158158
/// * Delete the torrent.
159159
pub async fn delete_torrent_handler(req: HttpRequest, app_data: WebAppData) -> ServiceResult<impl Responder> {
160160
let info_hash = get_torrent_info_hash_from_request(&req)?;
161-
let user_id = app_data.auth.get_user_id_from_request(&req).await?;
161+
let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?;
162162

163163
let deleted_torrent_response = app_data.torrent_service.delete_torrent(&info_hash, &user_id).await?;
164164

src/routes/user.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ pub async fn email_verification_handler(req: HttpRequest, app_data: WebAppData)
134134
///
135135
/// This function will return if the user could not be banned.
136136
pub async fn ban_handler(req: HttpRequest, app_data: WebAppData) -> ServiceResult<impl Responder> {
137-
let user_id = app_data.auth.get_user_id_from_request(&req).await?;
137+
let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?;
138138
let to_be_banned_username = req.match_info().get("user").ok_or(ServiceError::InternalServerError)?;
139139

140140
app_data.ban_service.ban_user(to_be_banned_username, &user_id).await?;

src/web/api/v1/auth.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,16 @@
7878
//! "data": "new category"
7979
//! }
8080
//! ```
81+
82+
use hyper::http::HeaderValue;
83+
84+
/// Parses the token from the `Authorization` header.
85+
pub fn parse_token(authorization: &HeaderValue) -> String {
86+
let split: Vec<&str> = authorization
87+
.to_str()
88+
.expect("variable `auth` contains data that is not visible ASCII chars.")
89+
.split("Bearer")
90+
.collect();
91+
let token = split[1].trim();
92+
token.to_string()
93+
}

src/web/api/v1/contexts/user/handlers.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use super::forms::{JsonWebToken, LoginForm, RegistrationForm};
1010
use super::responses::{self, NewUser, TokenResponse};
1111
use crate::common::AppData;
1212
use crate::errors::ServiceError;
13+
use crate::web::api::v1::extractors::bearer_token::Extract;
1314
use crate::web::api::v1::responses::OkResponse;
1415

1516
// Registration
@@ -99,6 +100,9 @@ pub async fn verify_token_handler(
99100
}
100101
}
101102

103+
#[derive(Deserialize)]
104+
pub struct UsernameParam(pub String);
105+
102106
/// It renews the JWT.
103107
///
104108
/// # Errors
@@ -118,6 +122,32 @@ pub async fn renew_token_handler(
118122
}
119123
}
120124

125+
/// It bans a user from the index.
126+
///
127+
/// # Errors
128+
///
129+
/// This function will return if:
130+
///
131+
/// - The JWT provided by the banning authority was not valid.
132+
/// - The user could not be banned: it does not exist, etcetera.
133+
#[allow(clippy::unused_async)]
134+
pub async fn ban_handler(
135+
State(app_data): State<Arc<AppData>>,
136+
Path(to_be_banned_username): Path<UsernameParam>,
137+
Extract(maybe_bearer_token): Extract,
138+
) -> Result<Json<OkResponse<String>>, ServiceError> {
139+
// todo: add reason and `date_expiry` parameters to request
140+
141+
let user_id = app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await?;
142+
143+
match app_data.ban_service.ban_user(&to_be_banned_username.0, &user_id).await {
144+
Ok(_) => Ok(axum::Json(OkResponse {
145+
data: format!("Banned user: {}", to_be_banned_username.0),
146+
})),
147+
Err(error) => Err(error),
148+
}
149+
}
150+
121151
/// It returns the base API URL without the port. For example: `http://localhost`.
122152
fn api_base_url(host: &str) -> String {
123153
// HTTPS is not supported yet.

src/web/api/v1/contexts/user/routes.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
//! Refer to the [API endpoint documentation](crate::web::api::v1::contexts::user).
44
use std::sync::Arc;
55

6-
use axum::routing::{get, post};
6+
use axum::routing::{delete, get, post};
77
use axum::Router;
88

99
use super::handlers::{
10-
email_verification_handler, login_handler, registration_handler, renew_token_handler, verify_token_handler,
10+
ban_handler, email_verification_handler, login_handler, registration_handler, renew_token_handler, verify_token_handler,
1111
};
1212
use crate::common::AppData;
1313

@@ -27,5 +27,8 @@ pub fn router(app_data: Arc<AppData>) -> Router {
2727
// Authentication
2828
.route("/login", post(login_handler).with_state(app_data.clone()))
2929
.route("/token/verify", post(verify_token_handler).with_state(app_data.clone()))
30-
.route("/token/renew", post(renew_token_handler).with_state(app_data))
30+
.route("/token/renew", post(renew_token_handler).with_state(app_data.clone()))
31+
// User ban
32+
// code-review: should not this be a POST method? We add the user to the blacklist. We do not delete the user.
33+
.route("/ban/:user", delete(ban_handler).with_state(app_data))
3134
}

0 commit comments

Comments
 (0)