Skip to content

Commit

Permalink
refactor: [torrust#157] extract authentication service
Browse files Browse the repository at this point in the history
Decoupling services from actix-web framework.
  • Loading branch information
josecelano committed May 22, 2023
1 parent 8101048 commit 5396301
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 188 deletions.
31 changes: 22 additions & 9 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ use actix_web::dev::Server;
use actix_web::{middleware, web, App, HttpServer};
use log::info;

use crate::auth::AuthorizationService;
use crate::auth::Authentication;
use crate::bootstrap::logging;
use crate::cache::image::manager::ImageCacheService;
use crate::common::AppData;
use crate::config::Configuration;
use crate::databases::database;
use crate::services::authentication::{DbUserAuthenticationRepository, JsonWebToken, Service};
use crate::services::category::{self, DbCategoryRepository};
use crate::services::torrent::{
DbTorrentAnnounceUrlRepository, DbTorrentFileRepository, DbTorrentInfoRepository, DbTorrentListingGenerator,
Expand Down Expand Up @@ -50,23 +51,27 @@ pub async fn run(configuration: Configuration) -> Running {
// Build app dependencies

let database = Arc::new(database::connect(&database_connect_url).await.expect("Database error."));
let auth = Arc::new(AuthorizationService::new(configuration.clone(), database.clone()));
let tracker_service = Arc::new(tracker::service::Service::new(configuration.clone(), database.clone()).await);
let tracker_statistics_importer =
Arc::new(StatisticsImporter::new(configuration.clone(), tracker_service.clone(), database.clone()).await);
let mailer_service = Arc::new(mailer::Service::new(configuration.clone()).await);
let image_cache_service: Arc<ImageCacheService> = Arc::new(ImageCacheService::new(configuration.clone()).await);
let json_web_token = Arc::new(JsonWebToken::new(configuration.clone()));
let auth = Arc::new(Authentication::new(json_web_token.clone()));

// Repositories
let category_repository = Arc::new(DbCategoryRepository::new(database.clone()));
let user_repository = Arc::new(DbUserRepository::new(database.clone()));
let user_authentication_repository = Arc::new(DbUserAuthenticationRepository::new(database.clone()));
let user_profile_repository = Arc::new(DbUserProfileRepository::new(database.clone()));
let torrent_repository = Arc::new(DbTorrentRepository::new(database.clone()));
let torrent_info_repository = Arc::new(DbTorrentInfoRepository::new(database.clone()));
let torrent_file_repository = Arc::new(DbTorrentFileRepository::new(database.clone()));
let torrent_announce_url_repository = Arc::new(DbTorrentAnnounceUrlRepository::new(database.clone()));
let torrent_listing_generator = Arc::new(DbTorrentListingGenerator::new(database.clone()));
let banned_user_list = Arc::new(DbBannedUserList::new(database.clone()));

// Services
let tracker_service = Arc::new(tracker::service::Service::new(configuration.clone(), database.clone()).await);
let tracker_statistics_importer =
Arc::new(StatisticsImporter::new(configuration.clone(), tracker_service.clone(), database.clone()).await);
let mailer_service = Arc::new(mailer::Service::new(configuration.clone()).await);
let image_cache_service: Arc<ImageCacheService> = Arc::new(ImageCacheService::new(configuration.clone()).await);
let category_service = Arc::new(category::Service::new(category_repository.clone(), user_repository.clone()));
let proxy_service = Arc::new(proxy::Service::new(image_cache_service.clone(), user_repository.clone()));
let settings_service = Arc::new(settings::Service::new(configuration.clone(), user_repository.clone()));
Expand All @@ -93,28 +98,36 @@ pub async fn run(configuration: Configuration) -> Running {
user_profile_repository.clone(),
banned_user_list.clone(),
));
let authentication_service = Arc::new(Service::new(
configuration.clone(),
json_web_token.clone(),
user_repository.clone(),
user_profile_repository.clone(),
user_authentication_repository.clone(),
));

// Build app container

let app_data = Arc::new(AppData::new(
configuration.clone(),
database.clone(),
json_web_token.clone(),
auth.clone(),
authentication_service,
tracker_service.clone(),
tracker_statistics_importer.clone(),
mailer_service,
image_cache_service,
// Repositories
category_repository,
user_repository,
user_authentication_repository,
user_profile_repository,
torrent_repository,
torrent_info_repository,
torrent_file_repository,
torrent_announce_url_repository,
torrent_listing_generator,
banned_user_list,
// Services
category_service,
proxy_service,
settings_service,
Expand Down
58 changes: 9 additions & 49 deletions src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,24 @@
use std::sync::Arc;

use actix_web::HttpRequest;
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};

use crate::config::Configuration;
use crate::databases::database::Database;
use crate::errors::ServiceError;
use crate::models::user::{UserClaims, UserCompact, UserId};
use crate::utils::clock;
use crate::services::authentication::JsonWebToken;

pub struct AuthorizationService {
cfg: Arc<Configuration>,
database: Arc<Box<dyn Database>>,
pub struct Authentication {
json_web_token: Arc<JsonWebToken>,
}

impl AuthorizationService {
pub fn new(cfg: Arc<Configuration>, database: Arc<Box<dyn Database>>) -> AuthorizationService {
AuthorizationService { cfg, database }
impl Authentication {
#[must_use]
pub fn new(json_web_token: Arc<JsonWebToken>) -> Self {
Self { json_web_token }
}

/// Create Json Web Token
pub async fn sign_jwt(&self, user: UserCompact) -> String {
let settings = self.cfg.settings.read().await;

// create JWT that expires in two weeks
let key = settings.auth.secret_key.as_bytes();
// TODO: create config option for setting the token validity in seconds
let exp_date = clock::now() + 1_209_600; // two weeks from now

let claims = UserClaims { user, exp: exp_date };

encode(&Header::default(), &claims, &EncodingKey::from_secret(key)).expect("argument `Header` should match `EncodingKey`")
self.json_web_token.sign(user).await
}

/// Verify Json Web Token
Expand All @@ -39,21 +27,7 @@ impl AuthorizationService {
///
/// This function will return an error if the JWT is not good or expired.
pub async fn verify_jwt(&self, token: &str) -> Result<UserClaims, ServiceError> {
let settings = self.cfg.settings.read().await;

match decode::<UserClaims>(
token,
&DecodingKey::from_secret(settings.auth.secret_key.as_bytes()),
&Validation::new(Algorithm::HS256),
) {
Ok(token_data) => {
if token_data.claims.exp < clock::now() {
return Err(ServiceError::TokenExpired);
}
Ok(token_data.claims)
}
Err(_) => Err(ServiceError::TokenInvalid),
}
self.json_web_token.verify(token).await
}

/// Get Claims from Request
Expand Down Expand Up @@ -81,20 +55,6 @@ impl AuthorizationService {
}
}

/// Get User (in compact form) from Request
///
/// # Errors
///
/// This function will return an `ServiceError::UserNotFound` if unable to get user from database.
pub async fn get_user_compact_from_request(&self, req: &HttpRequest) -> Result<UserCompact, ServiceError> {
let claims = self.get_claims_from_request(req).await?;

self.database
.get_user_compact_from_id(claims.user.user_id)
.await
.map_err(|_| ServiceError::UserNotFound)
}

/// Get User id from Request
///
/// # Errors
Expand Down
16 changes: 13 additions & 3 deletions src/common.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::sync::Arc;

use crate::auth::AuthorizationService;
use crate::auth::Authentication;
use crate::cache::image::manager::ImageCacheService;
use crate::config::Configuration;
use crate::databases::database::Database;
use crate::services::authentication::{DbUserAuthenticationRepository, JsonWebToken, Service};
use crate::services::category::{self, DbCategoryRepository};
use crate::services::torrent::{
DbTorrentAnnounceUrlRepository, DbTorrentFileRepository, DbTorrentInfoRepository, DbTorrentListingGenerator,
Expand All @@ -20,14 +21,17 @@ pub type WebAppData = actix_web::web::Data<Arc<AppData>>;
pub struct AppData {
pub cfg: Arc<Configuration>,
pub database: Arc<Box<dyn Database>>,
pub auth: Arc<AuthorizationService>,
pub json_web_token: Arc<JsonWebToken>,
pub auth: Arc<Authentication>,
pub authentication_service: Arc<Service>,
pub tracker_service: Arc<tracker::service::Service>,
pub tracker_statistics_importer: Arc<StatisticsImporter>,
pub mailer: Arc<mailer::Service>,
pub image_cache_manager: Arc<ImageCacheService>,
// Repositories
pub category_repository: Arc<DbCategoryRepository>,
pub user_repository: Arc<DbUserRepository>,
pub user_authentication_repository: Arc<DbUserAuthenticationRepository>,
pub user_profile_repository: Arc<DbUserProfileRepository>,
pub torrent_repository: Arc<DbTorrentRepository>,
pub torrent_info_repository: Arc<DbTorrentInfoRepository>,
Expand All @@ -49,14 +53,17 @@ impl AppData {
pub fn new(
cfg: Arc<Configuration>,
database: Arc<Box<dyn Database>>,
auth: Arc<AuthorizationService>,
json_web_token: Arc<JsonWebToken>,
auth: Arc<Authentication>,
authentication_service: Arc<Service>,
tracker_service: Arc<tracker::service::Service>,
tracker_statistics_importer: Arc<StatisticsImporter>,
mailer: Arc<mailer::Service>,
image_cache_manager: Arc<ImageCacheService>,
// Repositories
category_repository: Arc<DbCategoryRepository>,
user_repository: Arc<DbUserRepository>,
user_authentication_repository: Arc<DbUserAuthenticationRepository>,
user_profile_repository: Arc<DbUserProfileRepository>,
torrent_repository: Arc<DbTorrentRepository>,
torrent_info_repository: Arc<DbTorrentInfoRepository>,
Expand All @@ -75,14 +82,17 @@ impl AppData {
AppData {
cfg,
database,
json_web_token,
auth,
authentication_service,
tracker_service,
tracker_statistics_importer,
mailer,
image_cache_manager,
// Repositories
category_repository,
user_repository,
user_authentication_repository,
user_profile_repository,
torrent_repository,
torrent_info_repository,
Expand Down
2 changes: 1 addition & 1 deletion src/databases/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ pub trait Database: Sync + Send {
async fn get_user_from_id(&self, user_id: i64) -> Result<User, Error>;

/// Get `UserAuthentication` from `user_id`.
async fn get_user_authentication_from_id(&self, user_id: i64) -> Result<UserAuthentication, Error>;
async fn get_user_authentication_from_id(&self, user_id: UserId) -> Result<UserAuthentication, Error>;

/// Get `UserProfile` from `username`.
async fn get_user_profile_from_username(&self, username: &str) -> Result<UserProfile, Error>;
Expand Down
2 changes: 1 addition & 1 deletion src/databases/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ impl Database for Mysql {
.map_err(|_| database::Error::UserNotFound)
}

async fn get_user_authentication_from_id(&self, user_id: i64) -> Result<UserAuthentication, database::Error> {
async fn get_user_authentication_from_id(&self, user_id: UserId) -> Result<UserAuthentication, database::Error> {
query_as::<_, UserAuthentication>("SELECT * FROM torrust_user_authentication WHERE user_id = ?")
.bind(user_id)
.fetch_one(&self.pool)
Expand Down
2 changes: 1 addition & 1 deletion src/databases/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ impl Database for Sqlite {
.map_err(|_| database::Error::UserNotFound)
}

async fn get_user_authentication_from_id(&self, user_id: i64) -> Result<UserAuthentication, database::Error> {
async fn get_user_authentication_from_id(&self, user_id: UserId) -> Result<UserAuthentication, database::Error> {
query_as::<_, UserAuthentication>("SELECT * FROM torrust_user_authentication WHERE user_id = ?")
.bind(user_id)
.fetch_one(&self.pool)
Expand Down
Loading

0 comments on commit 5396301

Please sign in to comment.