Skip to content

Commit

Permalink
refactor(api): [torrust#183] Axum API, user context, renew JWT
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed Jun 14, 2023
1 parent b15616c commit 9564dec
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 7 deletions.
24 changes: 23 additions & 1 deletion src/web/api/v1/contexts/user/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ pub async fn email_verification_handler(State(app_data): State<Arc<AppData>>, Pa
///
/// # Errors
///
/// It returns an error if the user could not be registered.
/// It returns an error if:
///
/// - Unable to verify the supplied payload as a valid JWT.
/// - The JWT is not invalid or expired.
#[allow(clippy::unused_async)]
pub async fn login_handler(
State(app_data): State<Arc<AppData>>,
Expand Down Expand Up @@ -96,6 +99,25 @@ pub async fn verify_token_handler(
}
}

/// It renews the JWT.
///
/// # Errors
///
/// It returns an error if:
///
/// - Unable to parse the supplied payload as a valid JWT.
/// - The JWT is not invalid or expired.
#[allow(clippy::unused_async)]
pub async fn renew_token_handler(
State(app_data): State<Arc<AppData>>,
extract::Json(token): extract::Json<JsonWebToken>,
) -> Result<Json<OkResponse<TokenResponse>>, ServiceError> {
match app_data.authentication_service.renew_token(&token.token).await {
Ok((token, user_compact)) => Ok(responses::renewed_token(token, user_compact)),
Err(error) => Err(error),
}
}

/// It returns the base API URL without the port. For example: `http://localhost`.
fn api_base_url(host: &str) -> String {
// HTTPS is not supported yet.
Expand Down
13 changes: 12 additions & 1 deletion src/web/api/v1/contexts/user/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub struct TokenResponse {
pub admin: bool,
}

/// Response after successfully log in a user.
/// Response after successfully logging in a user.
pub fn logged_in_user(token: String, user_compact: UserCompact) -> Json<OkResponse<TokenResponse>> {
Json(OkResponse {
data: TokenResponse {
Expand All @@ -37,3 +37,14 @@ pub fn logged_in_user(token: String, user_compact: UserCompact) -> Json<OkRespon
},
})
}

/// Response after successfully renewing a JWT.
pub fn renewed_token(token: String, user_compact: UserCompact) -> Json<OkResponse<TokenResponse>> {
Json(OkResponse {
data: TokenResponse {
token,
username: user_compact.username,
admin: user_compact.administrator,
},
})
}
7 changes: 5 additions & 2 deletions src/web/api/v1/contexts/user/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use std::sync::Arc;
use axum::routing::{get, post};
use axum::Router;

use super::handlers::{email_verification_handler, login_handler, registration_handler, verify_token_handler};
use super::handlers::{
email_verification_handler, login_handler, registration_handler, renew_token_handler, verify_token_handler,
};
use crate::common::AppData;

/// Routes for the [`user`](crate::web::api::v1::contexts::user) API context.
Expand All @@ -24,5 +26,6 @@ pub fn router(app_data: Arc<AppData>) -> Router {
)
// Authentication
.route("/login", post(login_handler).with_state(app_data.clone()))
.route("/token/verify", post(verify_token_handler).with_state(app_data))
.route("/token/verify", post(verify_token_handler).with_state(app_data.clone()))
.route("/token/renew", post(renew_token_handler).with_state(app_data))
}
21 changes: 20 additions & 1 deletion tests/common/contexts/user/asserts.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use super::forms::RegistrationForm;
use super::responses::LoggedInUserData;
use crate::common::asserts::assert_json_ok;
use crate::common::contexts::user::responses::{AddedUserResponse, SuccessfulLoginResponse, TokenVerifiedResponse};
use crate::common::contexts::user::responses::{
AddedUserResponse, SuccessfulLoginResponse, TokenRenewalData, TokenRenewalResponse, TokenVerifiedResponse,
};
use crate::common::responses::TextResponse;

pub fn assert_added_user_response(response: &TextResponse) {
Expand Down Expand Up @@ -28,3 +31,19 @@ pub fn assert_token_verified_response(response: &TextResponse) {

assert_json_ok(response);
}

pub fn assert_token_renewal_response(response: &TextResponse, logged_in_user: &LoggedInUserData) {
let token_renewal_response: TokenRenewalResponse = serde_json::from_str(&response.body)
.unwrap_or_else(|_| panic!("response {:#?} should be a TokenRenewalResponse", response.body));

assert_eq!(
token_renewal_response.data,
TokenRenewalData {
token: logged_in_user.token.clone(),
username: logged_in_user.username.clone(),
admin: logged_in_user.admin,
}
);

assert_json_ok(response);
}
36 changes: 34 additions & 2 deletions tests/e2e/contexts/user/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,10 @@ mod with_axum_implementation {
use torrust_index_backend::web::api;

use crate::common::client::Client;
use crate::common::contexts::user::asserts::{assert_successful_login_response, assert_token_verified_response};
use crate::common::contexts::user::forms::{LoginForm, TokenVerificationForm};
use crate::common::contexts::user::asserts::{
assert_successful_login_response, assert_token_renewal_response, assert_token_verified_response,
};
use crate::common::contexts::user::forms::{LoginForm, TokenRenewalForm, TokenVerificationForm};
use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_AXUM_IMPL;
use crate::e2e::contexts::user::steps::{new_logged_in_user, new_registered_user};
use crate::e2e::environment::TestEnv;
Expand Down Expand Up @@ -265,6 +267,12 @@ mod with_axum_implementation {
async fn it_should_allow_a_logged_in_user_to_verify_an_authentication_token() {
let mut env = TestEnv::new();
env.start(api::Implementation::Axum).await;

if env::var(ENV_VAR_E2E_EXCLUDE_AXUM_IMPL).is_ok() {
println!("Skipped");
return;
}

let client = Client::unauthenticated(&env.server_socket_addr().unwrap());

let logged_in_user = new_logged_in_user(&env).await;
Expand All @@ -277,5 +285,29 @@ mod with_axum_implementation {

assert_token_verified_response(&response);
}

#[tokio::test]
async fn it_should_not_allow_a_logged_in_user_to_renew_an_authentication_token_which_is_still_valid_for_more_than_one_week(
) {
let mut env = TestEnv::new();
env.start(api::Implementation::Axum).await;

if env::var(ENV_VAR_E2E_EXCLUDE_AXUM_IMPL).is_ok() {
println!("Skipped");
return;
}

let logged_in_user = new_logged_in_user(&env).await;

let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_user.token);

let response = client
.renew_token(TokenRenewalForm {
token: logged_in_user.token.clone(),
})
.await;

assert_token_renewal_response(&response, &logged_in_user);
}
}
}

0 comments on commit 9564dec

Please sign in to comment.