diff --git a/crates/application/src/auth/deps.rs b/crates/application/src/auth/deps.rs new file mode 100644 index 0000000..e628373 --- /dev/null +++ b/crates/application/src/auth/deps.rs @@ -0,0 +1,33 @@ +use std::sync::Arc; + +use domain::ports::{AuthService, PasswordHasher, RefreshSessionRepository, UserRepository}; + +use crate::config::AppConfig; + +pub struct LoginDeps { + pub user: Arc, + pub password_hasher: Arc, + pub auth: Arc, + pub refresh_session: Arc, + pub config: AppConfig, +} + +pub struct RegisterDeps { + pub user: Arc, + pub password_hasher: Arc, + pub config: AppConfig, +} + +pub struct RefreshDeps { + pub refresh_session: Arc, + pub auth: Arc, + pub config: AppConfig, +} + +pub struct RegisterAndLoginDeps { + pub user: Arc, + pub password_hasher: Arc, + pub auth: Arc, + pub refresh_session: Arc, + pub config: AppConfig, +} diff --git a/crates/application/src/auth/login.rs b/crates/application/src/auth/login.rs index bdd6d15..54a43c8 100644 --- a/crates/application/src/auth/login.rs +++ b/crates/application/src/auth/login.rs @@ -3,7 +3,7 @@ use uuid::Uuid; use domain::{errors::DomainError, models::RefreshSession, value_objects::Email}; -use crate::{auth::queries::LoginQuery, context::AppContext}; +use crate::auth::{deps::LoginDeps, queries::LoginQuery}; pub struct LoginResult { pub token: String, @@ -14,17 +14,15 @@ pub struct LoginResult { pub role: String, } -pub async fn execute(ctx: &AppContext, query: LoginQuery) -> Result { +pub async fn execute(deps: &LoginDeps, query: LoginQuery) -> Result { let email = Email::new(query.email)?; - let user = ctx - .repos + let user = deps .user .find_by_email(&email) .await? .ok_or_else(|| DomainError::Unauthorized("Invalid credentials".into()))?; - let valid = ctx - .services + let valid = deps .password_hasher .verify(&query.password, user.password_hash()) .await?; @@ -32,10 +30,10 @@ pub async fn execute(ctx: &AppContext, query: LoginQuery) -> Result Result Result<(), DomainError> { - ctx.repos.refresh_session.revoke(refresh_token).await +pub async fn execute( + refresh_session: Arc, + refresh_token: &str, +) -> Result<(), DomainError> { + refresh_session.revoke(refresh_token).await } #[cfg(test)] diff --git a/crates/application/src/auth/mod.rs b/crates/application/src/auth/mod.rs index 7b4409f..8bf84b8 100644 --- a/crates/application/src/auth/mod.rs +++ b/crates/application/src/auth/mod.rs @@ -1,4 +1,5 @@ pub mod commands; +pub mod deps; pub mod login; pub mod logout; pub mod queries; diff --git a/crates/application/src/auth/refresh.rs b/crates/application/src/auth/refresh.rs index 186d301..ec354e7 100644 --- a/crates/application/src/auth/refresh.rs +++ b/crates/application/src/auth/refresh.rs @@ -3,7 +3,7 @@ use uuid::Uuid; use domain::{errors::DomainError, models::RefreshSession}; -use crate::context::AppContext; +use crate::auth::deps::RefreshDeps; pub struct RefreshResult { pub token: String, @@ -12,30 +12,29 @@ pub struct RefreshResult { } pub async fn execute( - ctx: &AppContext, + deps: &RefreshDeps, old_refresh_token: &str, ) -> Result { - let session = ctx - .repos + let session = deps .refresh_session .get_by_token(old_refresh_token) .await? .ok_or_else(|| DomainError::Unauthorized("Invalid refresh token".into()))?; if session.expires_at < Utc::now() { - ctx.repos.refresh_session.revoke(old_refresh_token).await?; + deps.refresh_session.revoke(old_refresh_token).await?; return Err(DomainError::Unauthorized("Refresh token expired".into())); } // Revoke old token (rotation) - ctx.repos.refresh_session.revoke(old_refresh_token).await?; + deps.refresh_session.revoke(old_refresh_token).await?; // Generate new access token - let generated = ctx.services.auth.generate_token(&session.user_id).await?; + let generated = deps.auth.generate_token(&session.user_id).await?; // Create new refresh session let new_refresh_token = Uuid::new_v4().to_string(); - let refresh_expires = Utc::now() + Duration::seconds(ctx.config.refresh_ttl_seconds as i64); + let refresh_expires = Utc::now() + Duration::seconds(deps.config.refresh_ttl_seconds as i64); let new_session = RefreshSession { id: Uuid::new_v4(), user_id: session.user_id, @@ -43,7 +42,7 @@ pub async fn execute( expires_at: refresh_expires, created_at: Utc::now(), }; - ctx.repos.refresh_session.create(&new_session).await?; + deps.refresh_session.create(&new_session).await?; Ok(RefreshResult { token: generated.token, diff --git a/crates/application/src/auth/register.rs b/crates/application/src/auth/register.rs index cd6a8a8..2d398d7 100644 --- a/crates/application/src/auth/register.rs +++ b/crates/application/src/auth/register.rs @@ -4,10 +4,10 @@ use domain::{ value_objects::{Email, Password, Username}, }; -use crate::{auth::commands::RegisterCommand, context::AppContext}; +use crate::auth::{commands::RegisterCommand, deps::RegisterDeps}; -pub async fn execute(ctx: &AppContext, cmd: RegisterCommand) -> Result<(), DomainError> { - if !ctx.config.allow_registration { +pub async fn execute(deps: &RegisterDeps, cmd: RegisterCommand) -> Result<(), DomainError> { + if !deps.config.allow_registration { return Err(DomainError::Unauthorized("Registration is disabled".into())); } @@ -15,21 +15,20 @@ pub async fn execute(ctx: &AppContext, cmd: RegisterCommand) -> Result<(), Domai let email = Email::new(cmd.email)?; let username = Username::new(cmd.username)?; - if ctx.repos.user.find_by_email(&email).await?.is_some() { + if deps.user.find_by_email(&email).await?.is_some() { return Err(DomainError::ValidationError( "Email already registered".into(), )); } - if ctx.repos.user.find_by_username(&username).await?.is_some() { + if deps.user.find_by_username(&username).await?.is_some() { return Err(DomainError::ValidationError( "Username already taken".into(), )); } - let hash = ctx.services.password_hasher.hash(password.value()).await?; - ctx.repos - .user + let hash = deps.password_hasher.hash(password.value()).await?; + deps.user .save(&User::new(email, username, hash, cmd.role)) .await } diff --git a/crates/application/src/auth/register_and_login.rs b/crates/application/src/auth/register_and_login.rs index b5031d4..1168478 100644 --- a/crates/application/src/auth/register_and_login.rs +++ b/crates/application/src/auth/register_and_login.rs @@ -1,18 +1,25 @@ use domain::errors::DomainError; -use crate::{ - auth::commands::RegisterAndLoginCommand, - auth::{login, register}, - context::AppContext, +use crate::auth::{ + commands::{RegisterAndLoginCommand, RegisterCommand}, + deps::{LoginDeps, RegisterAndLoginDeps, RegisterDeps}, + login::{self, LoginResult}, + queries::LoginQuery, + register, }; pub async fn execute( - ctx: &AppContext, + deps: &RegisterAndLoginDeps, cmd: RegisterAndLoginCommand, -) -> Result { +) -> Result { + let reg_deps = RegisterDeps { + user: deps.user.clone(), + password_hasher: deps.password_hasher.clone(), + config: deps.config.clone(), + }; register::execute( - ctx, - crate::auth::commands::RegisterCommand { + ®_deps, + RegisterCommand { email: cmd.email.clone(), username: cmd.username, password: cmd.password.clone(), @@ -21,9 +28,16 @@ pub async fn execute( ) .await?; + let log_deps = LoginDeps { + user: deps.user.clone(), + password_hasher: deps.password_hasher.clone(), + auth: deps.auth.clone(), + refresh_session: deps.refresh_session.clone(), + config: deps.config.clone(), + }; login::execute( - ctx, - crate::auth::queries::LoginQuery { + &log_deps, + LoginQuery { email: cmd.email, password: cmd.password, }, diff --git a/crates/application/src/auth/tests/login.rs b/crates/application/src/auth/tests/login.rs index c3d7294..4dbb465 100644 --- a/crates/application/src/auth/tests/login.rs +++ b/crates/application/src/auth/tests/login.rs @@ -4,15 +4,24 @@ use domain::models::UserRole; use domain::testing::InMemoryUserRepository; use crate::{ - auth::commands::RegisterCommand, - auth::queries::LoginQuery, - auth::{login, register}, + auth::{ + commands::RegisterCommand, + deps::{LoginDeps, RegisterDeps}, + login, + queries::LoginQuery, + register, + }, test_helpers::TestContextBuilder, }; -async fn setup_user(ctx: &crate::context::AppContext, email: &str, password: &str) { +async fn setup_user(b: &TestContextBuilder, email: &str, password: &str) { + let deps = RegisterDeps { + user: b.user_repo.clone(), + password_hasher: b.password_hasher.clone(), + config: b.config.clone(), + }; register::execute( - ctx, + &deps, RegisterCommand { email: email.to_string(), username: "testuser".to_string(), @@ -27,14 +36,18 @@ async fn setup_user(ctx: &crate::context::AppContext, email: &str, password: &st #[tokio::test] async fn test_login_valid_credentials_returns_token() { let users = InMemoryUserRepository::new(); - let ctx = TestContextBuilder::new() - .with_users(Arc::clone(&users) as _) - .build(); - - setup_user(&ctx, "carol@example.com", "secret123").await; + let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _); + setup_user(&b, "carol@example.com", "secret123").await; + let deps = LoginDeps { + user: b.user_repo.clone(), + password_hasher: b.password_hasher.clone(), + auth: b.auth_service.clone(), + refresh_session: b.refresh_session_repo.clone(), + config: b.config.clone(), + }; let result = login::execute( - &ctx, + &deps, LoginQuery { email: "carol@example.com".into(), password: "secret123".into(), @@ -51,14 +64,18 @@ async fn test_login_valid_credentials_returns_token() { #[tokio::test] async fn test_login_wrong_password_fails() { let users = InMemoryUserRepository::new(); - let ctx = TestContextBuilder::new() - .with_users(Arc::clone(&users) as _) - .build(); - - setup_user(&ctx, "dave@example.com", "correct_password").await; + let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _); + setup_user(&b, "dave@example.com", "correct_password").await; + let deps = LoginDeps { + user: b.user_repo.clone(), + password_hasher: b.password_hasher.clone(), + auth: b.auth_service.clone(), + refresh_session: b.refresh_session_repo.clone(), + config: b.config.clone(), + }; let result = login::execute( - &ctx, + &deps, LoginQuery { email: "dave@example.com".into(), password: "wrong_password".into(), @@ -71,10 +88,16 @@ async fn test_login_wrong_password_fails() { #[tokio::test] async fn test_login_unknown_email_fails() { - let ctx = TestContextBuilder::new().build(); - + let b = TestContextBuilder::new(); + let deps = LoginDeps { + user: b.user_repo.clone(), + password_hasher: b.password_hasher.clone(), + auth: b.auth_service.clone(), + refresh_session: b.refresh_session_repo.clone(), + config: b.config.clone(), + }; let result = login::execute( - &ctx, + &deps, LoginQuery { email: "nobody@example.com".into(), password: "anything".into(), diff --git a/crates/application/src/auth/tests/logout.rs b/crates/application/src/auth/tests/logout.rs index 2a7e903..51fef4c 100644 --- a/crates/application/src/auth/tests/logout.rs +++ b/crates/application/src/auth/tests/logout.rs @@ -4,21 +4,27 @@ use domain::models::UserRole; use domain::testing::InMemoryUserRepository; use crate::{ - auth::commands::RegisterCommand, - auth::queries::LoginQuery, - auth::{login, logout, refresh, register}, + auth::{ + commands::RegisterCommand, + deps::{LoginDeps, RefreshDeps, RegisterDeps}, + login, logout, refresh, register, + queries::LoginQuery, + }, test_helpers::TestContextBuilder, }; #[tokio::test] async fn logout_revokes_refresh_token() { let users = InMemoryUserRepository::new(); - let ctx = TestContextBuilder::new() - .with_users(Arc::clone(&users) as _) - .build(); + let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _); + let reg_deps = RegisterDeps { + user: b.user_repo.clone(), + password_hasher: b.password_hasher.clone(), + config: b.config.clone(), + }; register::execute( - &ctx, + ®_deps, RegisterCommand { email: "bob@example.com".to_string(), username: "bob".to_string(), @@ -29,8 +35,15 @@ async fn logout_revokes_refresh_token() { .await .unwrap(); + let login_deps = LoginDeps { + user: b.user_repo.clone(), + password_hasher: b.password_hasher.clone(), + auth: b.auth_service.clone(), + refresh_session: b.refresh_session_repo.clone(), + config: b.config.clone(), + }; let login_result = login::execute( - &ctx, + &login_deps, LoginQuery { email: "bob@example.com".into(), password: "password123".into(), @@ -39,17 +52,25 @@ async fn logout_revokes_refresh_token() { .await .unwrap(); - logout::execute(&ctx, &login_result.refresh_token) - .await - .unwrap(); + logout::execute( + b.refresh_session_repo.clone(), + &login_result.refresh_token, + ) + .await + .unwrap(); - let refresh_attempt = refresh::execute(&ctx, &login_result.refresh_token).await; + let refresh_deps = RefreshDeps { + refresh_session: b.refresh_session_repo.clone(), + auth: b.auth_service.clone(), + config: b.config.clone(), + }; + let refresh_attempt = refresh::execute(&refresh_deps, &login_result.refresh_token).await; assert!(refresh_attempt.is_err()); } #[tokio::test] async fn logout_with_unknown_token_succeeds() { - let ctx = TestContextBuilder::new().build(); - let result = logout::execute(&ctx, "nonexistent-token").await; + let b = TestContextBuilder::new(); + let result = logout::execute(b.refresh_session_repo.clone(), "nonexistent-token").await; assert!(result.is_ok()); } diff --git a/crates/application/src/auth/tests/refresh.rs b/crates/application/src/auth/tests/refresh.rs index 7e57081..7a33b23 100644 --- a/crates/application/src/auth/tests/refresh.rs +++ b/crates/application/src/auth/tests/refresh.rs @@ -4,15 +4,23 @@ use domain::models::UserRole; use domain::testing::InMemoryUserRepository; use crate::{ - auth::commands::RegisterCommand, - auth::queries::LoginQuery, - auth::{login, refresh, register}, + auth::{ + commands::RegisterCommand, + deps::{LoginDeps, RefreshDeps, RegisterDeps}, + login, refresh, register, + queries::LoginQuery, + }, test_helpers::TestContextBuilder, }; -async fn login_user(ctx: &crate::context::AppContext) -> login::LoginResult { +async fn login_user(b: &TestContextBuilder) -> login::LoginResult { + let reg_deps = RegisterDeps { + user: b.user_repo.clone(), + password_hasher: b.password_hasher.clone(), + config: b.config.clone(), + }; register::execute( - ctx, + ®_deps, RegisterCommand { email: "alice@example.com".to_string(), username: "alice".to_string(), @@ -23,8 +31,15 @@ async fn login_user(ctx: &crate::context::AppContext) -> login::LoginResult { .await .unwrap(); + let login_deps = LoginDeps { + user: b.user_repo.clone(), + password_hasher: b.password_hasher.clone(), + auth: b.auth_service.clone(), + refresh_session: b.refresh_session_repo.clone(), + config: b.config.clone(), + }; login::execute( - ctx, + &login_deps, LoginQuery { email: "alice@example.com".into(), password: "password123".into(), @@ -37,13 +52,15 @@ async fn login_user(ctx: &crate::context::AppContext) -> login::LoginResult { #[tokio::test] async fn refresh_returns_new_tokens() { let users = InMemoryUserRepository::new(); - let ctx = TestContextBuilder::new() - .with_users(Arc::clone(&users) as _) - .build(); + let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _); + let login_result = login_user(&b).await; - let login_result = login_user(&ctx).await; - - let result = refresh::execute(&ctx, &login_result.refresh_token) + let deps = RefreshDeps { + refresh_session: b.refresh_session_repo.clone(), + auth: b.auth_service.clone(), + config: b.config.clone(), + }; + let result = refresh::execute(&deps, &login_result.refresh_token) .await .unwrap(); @@ -55,33 +72,37 @@ async fn refresh_returns_new_tokens() { #[tokio::test] async fn refresh_rotates_token_old_one_invalid() { let users = InMemoryUserRepository::new(); - let ctx = TestContextBuilder::new() - .with_users(Arc::clone(&users) as _) - .build(); - - let login_result = login_user(&ctx).await; + let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _); + let login_result = login_user(&b).await; let old_token = login_result.refresh_token.clone(); - refresh::execute(&ctx, &old_token).await.unwrap(); + let deps = RefreshDeps { + refresh_session: b.refresh_session_repo.clone(), + auth: b.auth_service.clone(), + config: b.config.clone(), + }; + refresh::execute(&deps, &old_token).await.unwrap(); - let retry = refresh::execute(&ctx, &old_token).await; + let retry = refresh::execute(&deps, &old_token).await; assert!(retry.is_err()); } #[tokio::test] async fn refresh_with_new_token_works() { let users = InMemoryUserRepository::new(); - let ctx = TestContextBuilder::new() - .with_users(Arc::clone(&users) as _) - .build(); + let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _); + let login_result = login_user(&b).await; - let login_result = login_user(&ctx).await; - - let first = refresh::execute(&ctx, &login_result.refresh_token) + let deps = RefreshDeps { + refresh_session: b.refresh_session_repo.clone(), + auth: b.auth_service.clone(), + config: b.config.clone(), + }; + let first = refresh::execute(&deps, &login_result.refresh_token) .await .unwrap(); - let second = refresh::execute(&ctx, &first.refresh_token).await.unwrap(); + let second = refresh::execute(&deps, &first.refresh_token).await.unwrap(); assert!(!second.token.is_empty()); assert_ne!(second.refresh_token, first.refresh_token); @@ -89,8 +110,12 @@ async fn refresh_with_new_token_works() { #[tokio::test] async fn refresh_with_unknown_token_fails() { - let ctx = TestContextBuilder::new().build(); - - let result = refresh::execute(&ctx, "nonexistent-token").await; + let b = TestContextBuilder::new(); + let deps = RefreshDeps { + refresh_session: b.refresh_session_repo.clone(), + auth: b.auth_service.clone(), + config: b.config.clone(), + }; + let result = refresh::execute(&deps, "nonexistent-token").await; assert!(result.is_err()); } diff --git a/crates/application/src/auth/tests/register.rs b/crates/application/src/auth/tests/register.rs index ead99a4..c67c7f9 100644 --- a/crates/application/src/auth/tests/register.rs +++ b/crates/application/src/auth/tests/register.rs @@ -5,7 +5,10 @@ use domain::ports::UserRepository; use domain::testing::InMemoryUserRepository; use domain::value_objects::Email; -use crate::{auth::commands::RegisterCommand, auth::register, test_helpers::TestContextBuilder}; +use crate::{ + auth::{commands::RegisterCommand, deps::RegisterDeps, register}, + test_helpers::TestContextBuilder, +}; fn cmd(email: &str) -> RegisterCommand { RegisterCommand { @@ -19,11 +22,14 @@ fn cmd(email: &str) -> RegisterCommand { #[tokio::test] async fn test_register_creates_user() { let users = InMemoryUserRepository::new(); - let ctx = TestContextBuilder::new() - .with_users(Arc::clone(&users) as _) - .build(); + let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _); + let deps = RegisterDeps { + user: b.user_repo.clone(), + password_hasher: b.password_hasher.clone(), + config: b.config.clone(), + }; - register::execute(&ctx, cmd("alice@example.com")) + register::execute(&deps, cmd("alice@example.com")) .await .unwrap(); @@ -36,22 +42,30 @@ async fn test_register_creates_user() { #[tokio::test] async fn test_register_duplicate_email_fails() { let users = InMemoryUserRepository::new(); - let ctx = TestContextBuilder::new() - .with_users(Arc::clone(&users) as _) - .build(); + let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _); + let deps = RegisterDeps { + user: b.user_repo.clone(), + password_hasher: b.password_hasher.clone(), + config: b.config.clone(), + }; - register::execute(&ctx, cmd("bob@example.com")) + register::execute(&deps, cmd("bob@example.com")) .await .unwrap(); - let result = register::execute(&ctx, cmd("bob@example.com")).await; + let result = register::execute(&deps, cmd("bob@example.com")).await; assert!(result.is_err(), "duplicate email should fail"); } #[tokio::test] async fn test_register_short_password_fails() { - let ctx = TestContextBuilder::new().build(); + let b = TestContextBuilder::new(); + let deps = RegisterDeps { + user: b.user_repo.clone(), + password_hasher: b.password_hasher.clone(), + config: b.config.clone(), + }; let result = register::execute( - &ctx, + &deps, RegisterCommand { email: "x@y.com".to_string(), username: "testuser".to_string(), diff --git a/crates/application/src/auth/tests/register_and_login.rs b/crates/application/src/auth/tests/register_and_login.rs index c9c8a5d..79eadaf 100644 --- a/crates/application/src/auth/tests/register_and_login.rs +++ b/crates/application/src/auth/tests/register_and_login.rs @@ -1,13 +1,21 @@ use crate::auth::commands::RegisterAndLoginCommand; +use crate::auth::deps::RegisterAndLoginDeps; use crate::auth::register_and_login; use crate::test_helpers::TestContextBuilder; #[tokio::test] async fn registers_and_returns_token() { - let ctx = TestContextBuilder::new().build(); + let b = TestContextBuilder::new(); + let deps = RegisterAndLoginDeps { + user: b.user_repo.clone(), + password_hasher: b.password_hasher.clone(), + auth: b.auth_service.clone(), + refresh_session: b.refresh_session_repo.clone(), + config: b.config.clone(), + }; let result = register_and_login::execute( - &ctx, + &deps, RegisterAndLoginCommand { email: "new@example.com".into(), username: "newuser".into(), diff --git a/crates/application/src/jobs/refresh_session_cleanup.rs b/crates/application/src/jobs/refresh_session_cleanup.rs index 48b35d1..32d6abd 100644 --- a/crates/application/src/jobs/refresh_session_cleanup.rs +++ b/crates/application/src/jobs/refresh_session_cleanup.rs @@ -1,28 +1,30 @@ +use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; -use domain::{errors::DomainError, ports::PeriodicJob}; - -use crate::context::AppContext; +use domain::{ + errors::DomainError, + ports::{PeriodicJob, RefreshSessionRepository}, +}; pub struct RefreshSessionCleanupJob { - ctx: AppContext, + refresh_session: Arc, } impl RefreshSessionCleanupJob { - pub fn new(ctx: AppContext) -> Self { - Self { ctx } + pub fn new(refresh_session: Arc) -> Self { + Self { refresh_session } } } #[async_trait] impl PeriodicJob for RefreshSessionCleanupJob { fn interval(&self) -> Duration { - Duration::from_secs(86400) + Duration::from_secs(3600) } async fn run(&self) -> Result<(), DomainError> { - let n = self.ctx.repos.refresh_session.delete_expired().await?; + let n = self.refresh_session.delete_expired().await?; if n > 0 { tracing::info!("refresh session cleanup: removed {n} expired sessions"); } diff --git a/crates/application/src/users/tests/get_current_profile.rs b/crates/application/src/users/tests/get_current_profile.rs index 91b2c22..d216bf5 100644 --- a/crates/application/src/users/tests/get_current_profile.rs +++ b/crates/application/src/users/tests/get_current_profile.rs @@ -7,7 +7,7 @@ use domain::value_objects::{Email, PasswordHash, UserId, Username}; use uuid::Uuid; use crate::{ - auth::{commands::RegisterCommand, register}, + auth::{commands::RegisterCommand, deps::RegisterDeps, register}, test_helpers::TestContextBuilder, users::{get_current_profile, queries::GetCurrentProfileQuery}, }; @@ -17,10 +17,14 @@ async fn returns_profile_for_existing_user() { let users = InMemoryUserRepository::new(); let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _); let user_repo = b.user_repo.clone(); - let ctx = b.build(); + let reg_deps = RegisterDeps { + user: b.user_repo.clone(), + password_hasher: b.password_hasher.clone(), + config: b.config.clone(), + }; register::execute( - &ctx, + ®_deps, RegisterCommand { email: "alice@example.com".into(), username: "alice".into(), diff --git a/crates/application/src/users/tests/get_profile.rs b/crates/application/src/users/tests/get_profile.rs index 7a2548e..55da51b 100644 --- a/crates/application/src/users/tests/get_profile.rs +++ b/crates/application/src/users/tests/get_profile.rs @@ -2,12 +2,32 @@ use domain::models::UserRole; use domain::value_objects::Email; use crate::auth::commands::RegisterCommand; +use crate::auth::deps::RegisterDeps; use crate::auth::register; use crate::test_helpers::TestContextBuilder; use crate::users::deps::GetProfileDeps; use crate::users::get_profile; use crate::users::queries::{GetUserProfileQuery, ProfileView}; +async fn setup_user(b: &TestContextBuilder, email: &str, username: &str) { + let deps = RegisterDeps { + user: b.user_repo.clone(), + password_hasher: b.password_hasher.clone(), + config: b.config.clone(), + }; + register::execute( + &deps, + RegisterCommand { + email: email.into(), + username: username.into(), + password: "password123".into(), + role: UserRole::Standard, + }, + ) + .await + .unwrap(); +} + #[tokio::test] async fn returns_profile_with_empty_stats() { let b = TestContextBuilder::new(); @@ -17,19 +37,8 @@ async fn returns_profile_with_empty_stats() { diary: b.diary_repo.clone(), social_query: b.social_query.clone(), }; - let ctx = b.build(); - register::execute( - &ctx, - RegisterCommand { - email: "profile@test.com".into(), - username: "profuser".into(), - password: "password123".into(), - role: UserRole::Standard, - }, - ) - .await - .unwrap(); + setup_user(&b, "profile@test.com", "profuser").await; let email = Email::new("profile@test.com".into()).unwrap(); let user = user_repo.find_by_email(&email).await.unwrap().unwrap(); @@ -62,19 +71,8 @@ async fn returns_history_view() { diary: b.diary_repo.clone(), social_query: b.social_query.clone(), }; - let ctx = b.build(); - register::execute( - &ctx, - RegisterCommand { - email: "hist@test.com".into(), - username: "histuser".into(), - password: "password123".into(), - role: UserRole::Standard, - }, - ) - .await - .unwrap(); + setup_user(&b, "hist@test.com", "histuser").await; let email = Email::new("hist@test.com".into()).unwrap(); let user = user_repo.find_by_email(&email).await.unwrap().unwrap(); @@ -109,19 +107,8 @@ async fn returns_trends_view() { diary: b.diary_repo.clone(), social_query: b.social_query.clone(), }; - let ctx = b.build(); - register::execute( - &ctx, - RegisterCommand { - email: "trends@test.com".into(), - username: "trendsuser".into(), - password: "password123".into(), - role: UserRole::Standard, - }, - ) - .await - .unwrap(); + setup_user(&b, "trends@test.com", "trendsuser").await; let email = Email::new("trends@test.com".into()).unwrap(); let user = user_repo.find_by_email(&email).await.unwrap().unwrap(); @@ -156,19 +143,8 @@ async fn returns_ratings_view() { diary: b.diary_repo.clone(), social_query: b.social_query.clone(), }; - let ctx = b.build(); - register::execute( - &ctx, - RegisterCommand { - email: "ratings@test.com".into(), - username: "ratingsuser".into(), - password: "password123".into(), - role: UserRole::Standard, - }, - ) - .await - .unwrap(); + setup_user(&b, "ratings@test.com", "ratingsuser").await; let email = Email::new("ratings@test.com".into()).unwrap(); let user = user_repo.find_by_email(&email).await.unwrap().unwrap(); @@ -201,19 +177,8 @@ async fn returns_recent_with_search() { diary: b.diary_repo.clone(), social_query: b.social_query.clone(), }; - let ctx = b.build(); - register::execute( - &ctx, - RegisterCommand { - email: "search@test.com".into(), - username: "searchuser".into(), - password: "password123".into(), - role: UserRole::Standard, - }, - ) - .await - .unwrap(); + setup_user(&b, "search@test.com", "searchuser").await; let email = Email::new("search@test.com".into()).unwrap(); let user = user_repo.find_by_email(&email).await.unwrap().unwrap(); @@ -246,19 +211,8 @@ async fn non_own_profile_skips_pending_followers() { diary: b.diary_repo.clone(), social_query: b.social_query.clone(), }; - let ctx = b.build(); - register::execute( - &ctx, - RegisterCommand { - email: "other@test.com".into(), - username: "otheruser".into(), - password: "password123".into(), - role: UserRole::Standard, - }, - ) - .await - .unwrap(); + setup_user(&b, "other@test.com", "otheruser").await; let email = Email::new("other@test.com".into()).unwrap(); let user = user_repo.find_by_email(&email).await.unwrap().unwrap(); diff --git a/crates/application/src/users/tests/update_profile.rs b/crates/application/src/users/tests/update_profile.rs index 6afd2ec..157171e 100644 --- a/crates/application/src/users/tests/update_profile.rs +++ b/crates/application/src/users/tests/update_profile.rs @@ -7,17 +7,19 @@ use domain::testing::{InMemoryUserRepository, NoopEventPublisher}; use uuid::Uuid; use crate::{ - auth::{commands::RegisterCommand, register}, + auth::{commands::RegisterCommand, deps::RegisterDeps, register}, test_helpers::TestContextBuilder, users::{commands::UpdateProfileCommand, deps::UpdateProfileDeps, update_profile}, }; -async fn register_user( - ctx: &crate::context::AppContext, - users: &Arc, -) -> Uuid { +async fn register_user(b: &TestContextBuilder, users: &Arc) -> Uuid { + let reg_deps = RegisterDeps { + user: b.user_repo.clone(), + password_hasher: b.password_hasher.clone(), + config: b.config.clone(), + }; register::execute( - ctx, + ®_deps, RegisterCommand { email: "alice@example.com".into(), username: "alice".into(), @@ -48,9 +50,7 @@ async fn updates_display_name() { object_storage: b.object_storage.clone(), event_publisher: b.event_publisher.clone(), }; - let ctx = b.build(); - - let uid = register_user(&ctx, &users).await; + let uid = register_user(&b, &users).await; update_profile::execute( &deps, @@ -85,9 +85,7 @@ async fn rejects_invalid_avatar_content_type() { object_storage: b.object_storage.clone(), event_publisher: b.event_publisher.clone(), }; - let ctx = b.build(); - - let uid = register_user(&ctx, &users).await; + let uid = register_user(&b, &users).await; let result = update_profile::execute( &deps, @@ -119,9 +117,7 @@ async fn uploads_avatar() { object_storage: b.object_storage.clone(), event_publisher: b.event_publisher.clone(), }; - let ctx = b.build(); - - let uid = register_user(&ctx, &users).await; + let uid = register_user(&b, &users).await; update_profile::execute( &deps, @@ -164,9 +160,7 @@ async fn uploads_banner() { object_storage: b.object_storage.clone(), event_publisher: b.event_publisher.clone(), }; - let ctx = b.build(); - - let uid = register_user(&ctx, &users).await; + let uid = register_user(&b, &users).await; update_profile::execute( &deps, @@ -233,9 +227,7 @@ async fn rejects_invalid_banner_content_type() { object_storage: b.object_storage.clone(), event_publisher: b.event_publisher.clone(), }; - let ctx = b.build(); - - let uid = register_user(&ctx, &users).await; + let uid = register_user(&b, &users).await; let result = update_profile::execute( &deps, @@ -267,9 +259,7 @@ async fn text_only_update_emits_user_updated_no_image_stored() { object_storage: b.object_storage.clone(), event_publisher: b.event_publisher.clone(), }; - let ctx = b.build(); - - let uid = register_user(&ctx, &users).await; + let uid = register_user(&b, &users).await; update_profile::execute( &deps, diff --git a/crates/presentation/src/handlers/auth.rs b/crates/presentation/src/handlers/auth.rs index 1f493b1..4431ba9 100644 --- a/crates/presentation/src/handlers/auth.rs +++ b/crates/presentation/src/handlers/auth.rs @@ -7,7 +7,11 @@ use axum::{ use chrono::Utc; use application::auth::{ - commands::RegisterCommand, login as login_uc, queries::LoginQuery, register as register_uc, + commands::RegisterCommand, + deps::{LoginDeps, RefreshDeps, RegisterAndLoginDeps, RegisterDeps}, + login as login_uc, + queries::LoginQuery, + register as register_uc, }; use crate::{ @@ -60,8 +64,15 @@ pub async fn login( State(state): State, Json(req): Json, ) -> Result, ApiError> { + let deps = LoginDeps { + user: state.app_ctx.repos.user.clone(), + password_hasher: state.app_ctx.services.password_hasher.clone(), + auth: state.app_ctx.services.auth.clone(), + refresh_session: state.app_ctx.repos.refresh_session.clone(), + config: state.app_ctx.config.clone(), + }; let result = login_uc::execute( - &state.app_ctx, + &deps, LoginQuery { email: req.email, password: req.password, @@ -90,8 +101,13 @@ pub async fn register( State(state): State, Json(req): Json, ) -> Result { + let deps = RegisterDeps { + user: state.app_ctx.repos.user.clone(), + password_hasher: state.app_ctx.services.password_hasher.clone(), + config: state.app_ctx.config.clone(), + }; register_uc::execute( - &state.app_ctx, + &deps, RegisterCommand { email: req.email, username: req.username, @@ -115,7 +131,12 @@ pub async fn refresh( State(state): State, Json(req): Json, ) -> Result, ApiError> { - let result = application::auth::refresh::execute(&state.app_ctx, &req.refresh_token).await?; + let deps = RefreshDeps { + refresh_session: state.app_ctx.repos.refresh_session.clone(), + auth: state.app_ctx.services.auth.clone(), + config: state.app_ctx.config.clone(), + }; + let result = application::auth::refresh::execute(&deps, &req.refresh_token).await?; Ok(Json(RefreshResponse { token: result.token, refresh_token: result.refresh_token, @@ -134,7 +155,11 @@ pub async fn api_logout( State(state): State, Json(req): Json, ) -> StatusCode { - let _ = application::auth::logout::execute(&state.app_ctx, &req.refresh_token).await; + let _ = application::auth::logout::execute( + state.app_ctx.repos.refresh_session.clone(), + &req.refresh_token, + ) + .await; StatusCode::NO_CONTENT } @@ -170,8 +195,15 @@ pub async fn post_login( if crate::csrf::mismatch(&csrf, &form.csrf_token) { return StatusCode::FORBIDDEN.into_response(); } + let deps = LoginDeps { + user: state.app_ctx.repos.user.clone(), + password_hasher: state.app_ctx.services.password_hasher.clone(), + auth: state.app_ctx.services.auth.clone(), + refresh_session: state.app_ctx.repos.refresh_session.clone(), + config: state.app_ctx.config.clone(), + }; match login_uc::execute( - &state.app_ctx, + &deps, LoginQuery { email: form.email, password: form.password, @@ -237,8 +269,15 @@ pub async fn post_register( if crate::csrf::mismatch(&csrf, &form.csrf_token) { return StatusCode::FORBIDDEN.into_response(); } + let deps = RegisterAndLoginDeps { + user: state.app_ctx.repos.user.clone(), + password_hasher: state.app_ctx.services.password_hasher.clone(), + auth: state.app_ctx.services.auth.clone(), + refresh_session: state.app_ctx.repos.refresh_session.clone(), + config: state.app_ctx.config.clone(), + }; match application::auth::register_and_login::execute( - &state.app_ctx, + &deps, application::auth::commands::RegisterAndLoginCommand { email: form.email, username: form.username, diff --git a/crates/worker/src/main.rs b/crates/worker/src/main.rs index c3dd86c..45d309f 100644 --- a/crates/worker/src/main.rs +++ b/crates/worker/src/main.rs @@ -191,7 +191,7 @@ async fn main() -> anyhow::Result<()> { Arc::clone(&ctx.repos.wrapup_repo), )), Arc::new(application::jobs::RefreshSessionCleanupJob::new( - ctx.clone(), + Arc::clone(&ctx.repos.refresh_session), )), ]; if let Some(job) = enrichment_job {