diff --git a/src/Core/API/API.Core/API.Core.csproj b/src/Core/API/API.Core/API.Core.csproj index 4ddfa09..29247b4 100644 --- a/src/Core/API/API.Core/API.Core.csproj +++ b/src/Core/API/API.Core/API.Core.csproj @@ -21,7 +21,8 @@ - + + diff --git a/src/Core/API/API.Core/Controllers/AuthController.cs b/src/Core/API/API.Core/Controllers/AuthController.cs index 62a9018..bccde9b 100644 --- a/src/Core/API/API.Core/Controllers/AuthController.cs +++ b/src/Core/API/API.Core/Controllers/AuthController.cs @@ -3,20 +3,18 @@ using API.Core.Contracts.Common; using Domain.Entities; using Infrastructure.Jwt; using Microsoft.AspNetCore.Mvc; -using Service.Core.Auth; +using Service.Auth.Auth; namespace API.Core.Controllers { [ApiController] [Route("api/[controller]")] - public class AuthController(IAuthService auth, IJwtService jwtService) : ControllerBase + public class AuthController(IRegisterService register, ILoginService login, ITokenInfrastructure tokenInfrastructure) : ControllerBase { - [HttpPost("register")] public async Task> Register([FromBody] RegisterRequest req) { - - var created = await auth.RegisterAsync(new UserAccount + var created = await register.RegisterAsync(new UserAccount { UserAccountId = Guid.Empty, Username = req.Username, @@ -27,8 +25,7 @@ namespace API.Core.Controllers }, req.Password); var jwtExpiresAt = DateTime.UtcNow.AddHours(1); - var jwt = jwtService.GenerateJwt(created.UserAccountId, created.Username, jwtExpiresAt - + var jwt = tokenInfrastructure.GenerateJwt(created.UserAccountId, created.Username, jwtExpiresAt ); var response = new ResponseBody @@ -46,7 +43,7 @@ namespace API.Core.Controllers [HttpPost("login")] public async Task Login([FromBody] LoginRequest req) { - var userAccount = await auth.LoginAsync(req.Username, req.Password); + var userAccount = await login.LoginAsync(req.Username, req.Password); if (userAccount is null) { return Unauthorized(new ResponseBody @@ -58,7 +55,7 @@ namespace API.Core.Controllers UserDTO dto = new(userAccount.UserAccountId, userAccount.Username); var jwtExpiresAt = DateTime.UtcNow.AddHours(1); - var jwt = jwtService.GenerateJwt(userAccount.UserAccountId, userAccount.Username, jwtExpiresAt); + var jwt = tokenInfrastructure.GenerateJwt(userAccount.UserAccountId, userAccount.Username, jwtExpiresAt); return Ok(new ResponseBody { @@ -67,4 +64,4 @@ namespace API.Core.Controllers }); } } -} +} \ No newline at end of file diff --git a/src/Core/API/API.Core/Controllers/UserController.cs b/src/Core/API/API.Core/Controllers/UserController.cs index 6039ce6..fe98481 100644 --- a/src/Core/API/API.Core/Controllers/UserController.cs +++ b/src/Core/API/API.Core/Controllers/UserController.cs @@ -1,6 +1,6 @@ using Domain.Entities; using Microsoft.AspNetCore.Mvc; -using Service.Core.User; +using Service.UserManagement.User; namespace API.Core.Controllers { diff --git a/src/Core/API/API.Core/Program.cs b/src/Core/API/API.Core/Program.cs index 640dbb6..82f1463 100644 --- a/src/Core/API/API.Core/Program.cs +++ b/src/Core/API/API.Core/Program.cs @@ -6,8 +6,9 @@ using Infrastructure.Repository.Auth; using Infrastructure.Repository.Sql; using Infrastructure.Repository.UserAccount; using Microsoft.AspNetCore.Mvc; -using Service.Core.Auth; -using Service.Core.User; +using Service.Auth.Auth; +using Service.UserManagement.User; + var builder = WebApplication.CreateBuilder(args); @@ -52,14 +53,20 @@ if (!builder.Environment.IsProduction()) builder.Logging.AddDebug(); } -// Dependency Injection +// Configure Dependency Injection ------------------------------------------------------------------------------------- + builder.Services.AddSingleton(); + builder.Services.AddScoped(); -builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); + var app = builder.Build(); diff --git a/src/Core/Core.slnx b/src/Core/Core.slnx index a9f0a9f..c48bc6c 100644 --- a/src/Core/Core.slnx +++ b/src/Core/Core.slnx @@ -17,6 +17,7 @@ - + + diff --git a/src/Core/Infrastructure/Infrastructure.Jwt/IJwtService.cs b/src/Core/Infrastructure/Infrastructure.Jwt/ITokenInfrastructure.cs similarity index 73% rename from src/Core/Infrastructure/Infrastructure.Jwt/IJwtService.cs rename to src/Core/Infrastructure/Infrastructure.Jwt/ITokenInfrastructure.cs index b62d2e3..7929da0 100644 --- a/src/Core/Infrastructure/Infrastructure.Jwt/IJwtService.cs +++ b/src/Core/Infrastructure/Infrastructure.Jwt/ITokenInfrastructure.cs @@ -1,6 +1,6 @@ namespace Infrastructure.Jwt; -public interface IJwtService +public interface ITokenInfrastructure { string GenerateJwt(Guid userId, string username, DateTime expiry); } diff --git a/src/Core/Infrastructure/Infrastructure.Jwt/JwtService.cs b/src/Core/Infrastructure/Infrastructure.Jwt/JwtInfrastructure.cs similarity index 96% rename from src/Core/Infrastructure/Infrastructure.Jwt/JwtService.cs rename to src/Core/Infrastructure/Infrastructure.Jwt/JwtInfrastructure.cs index d0fba84..1b093c8 100644 --- a/src/Core/Infrastructure/Infrastructure.Jwt/JwtService.cs +++ b/src/Core/Infrastructure/Infrastructure.Jwt/JwtInfrastructure.cs @@ -6,7 +6,7 @@ using JwtRegisteredClaimNames = System.IdentityModel.Tokens.Jwt.JwtRegisteredCla namespace Infrastructure.Jwt; -public class JwtService : IJwtService +public class JwtInfrastructure : ITokenInfrastructure { private readonly string? _secret = Environment.GetEnvironmentVariable( "JWT_SECRET" diff --git a/src/Core/Infrastructure/Infrastructure.PasswordHashing/Argon2Infrastructure.cs b/src/Core/Infrastructure/Infrastructure.PasswordHashing/Argon2Infrastructure.cs index ff47a77..0364900 100644 --- a/src/Core/Infrastructure/Infrastructure.PasswordHashing/Argon2Infrastructure.cs +++ b/src/Core/Infrastructure/Infrastructure.PasswordHashing/Argon2Infrastructure.cs @@ -4,7 +4,7 @@ using Konscious.Security.Cryptography; namespace Infrastructure.PasswordHashing; -public class Argon2Infrastructure : IPasswordInfra +public class Argon2Infrastructure : IPasswordInfrastructure { private const int SaltSize = 16; // 128-bit private const int HashSize = 32; // 256-bit diff --git a/src/Core/Infrastructure/Infrastructure.PasswordHashing/IPasswordInfra.cs b/src/Core/Infrastructure/Infrastructure.PasswordHashing/IPasswordInfrastructure.cs similarity index 77% rename from src/Core/Infrastructure/Infrastructure.PasswordHashing/IPasswordInfra.cs rename to src/Core/Infrastructure/Infrastructure.PasswordHashing/IPasswordInfrastructure.cs index 9a2df0b..593ed73 100644 --- a/src/Core/Infrastructure/Infrastructure.PasswordHashing/IPasswordInfra.cs +++ b/src/Core/Infrastructure/Infrastructure.PasswordHashing/IPasswordInfrastructure.cs @@ -1,6 +1,6 @@ namespace Infrastructure.PasswordHashing; -public interface IPasswordInfra +public interface IPasswordInfrastructure { public string Hash(string password); public bool Verify(string password, string stored); diff --git a/src/Core/Service/Service.Auth/Auth/ILoginService.cs b/src/Core/Service/Service.Auth/Auth/ILoginService.cs new file mode 100644 index 0000000..667a30a --- /dev/null +++ b/src/Core/Service/Service.Auth/Auth/ILoginService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; +using Domain.Entities; + +namespace Service.Auth.Auth; + +public interface ILoginService +{ + Task LoginAsync(string username, string password); +} diff --git a/src/Core/Service/Service.Auth/Auth/IRegisterService.cs b/src/Core/Service/Service.Auth/Auth/IRegisterService.cs new file mode 100644 index 0000000..0cd5743 --- /dev/null +++ b/src/Core/Service/Service.Auth/Auth/IRegisterService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; +using Domain.Entities; + +namespace Service.Auth.Auth; + +public interface IRegisterService +{ + Task RegisterAsync(UserAccount userAccount, string password); +} diff --git a/src/Core/Service/Service.Auth/Auth/LoginService.cs b/src/Core/Service/Service.Auth/Auth/LoginService.cs new file mode 100644 index 0000000..f931ce2 --- /dev/null +++ b/src/Core/Service/Service.Auth/Auth/LoginService.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using Domain.Entities; +using Infrastructure.PasswordHashing; +using Infrastructure.Repository.Auth; + +namespace Service.Auth.Auth; + +public class LoginService( + IAuthRepository authRepo, + IPasswordInfrastructure passwordInfrastructure +) : ILoginService +{ + + public async Task LoginAsync(string username, string password) + { + // Attempt lookup by username + var user = await authRepo.GetUserByUsernameAsync(username); + + // the user was not found + if (user is null) return null; + + // @todo handle expired passwords + var activeCred = await authRepo.GetActiveCredentialByUserAccountIdAsync(user.UserAccountId); + + if (activeCred is null) return null; + return !passwordInfrastructure.Verify(password, activeCred.Hash) ? null : user; + } +} diff --git a/src/Core/Service/Service.Core/Auth/AuthService.cs b/src/Core/Service/Service.Auth/Auth/RegisterService.cs similarity index 51% rename from src/Core/Service/Service.Core/Auth/AuthService.cs rename to src/Core/Service/Service.Auth/Auth/RegisterService.cs index 8756dc3..5b1493b 100644 --- a/src/Core/Service/Service.Core/Auth/AuthService.cs +++ b/src/Core/Service/Service.Auth/Auth/RegisterService.cs @@ -1,13 +1,14 @@ +using System.Threading.Tasks; using Domain.Entities; using Infrastructure.PasswordHashing; using Infrastructure.Repository.Auth; -namespace Service.Core.Auth; +namespace Service.Auth.Auth; -public class AuthService( +public class RegisterService( IAuthRepository authRepo, - IPasswordInfra passwordInfra -) : IAuthService + IPasswordInfrastructure passwordInfrastructure +) : IRegisterService { public async Task RegisterAsync(UserAccount userAccount, string password) { @@ -19,7 +20,7 @@ public class AuthService( } // password hashing - var hashed = passwordInfra.Hash(password); + var hashed = passwordInfrastructure.Hash(password); // Register user with hashed password return await authRepo.RegisterUserAsync( @@ -31,18 +32,5 @@ public class AuthService( hashed); } - public async Task LoginAsync(string username, string password) - { - // Attempt lookup by username - var user = await authRepo.GetUserByUsernameAsync(username); - - // the user was not found - if (user is null) return null; - - // @todo handle expired passwords - var activeCred = await authRepo.GetActiveCredentialByUserAccountIdAsync(user.UserAccountId); - - if (activeCred is null) return null; - return !passwordInfra.Verify(password, activeCred.Hash) ? null : user; - } + } diff --git a/src/Core/Service/Service.Core/Service.Core.csproj b/src/Core/Service/Service.Auth/Service.Auth.csproj similarity index 93% rename from src/Core/Service/Service.Core/Service.Core.csproj rename to src/Core/Service/Service.Auth/Service.Auth.csproj index c01d95b..48566dc 100644 --- a/src/Core/Service/Service.Core/Service.Core.csproj +++ b/src/Core/Service/Service.Auth/Service.Auth.csproj @@ -3,7 +3,6 @@ net10.0 enable enable - Service.Core diff --git a/src/Core/Service/Service.Core/Auth/IAuthService.cs b/src/Core/Service/Service.Core/Auth/IAuthService.cs deleted file mode 100644 index 8e99efb..0000000 --- a/src/Core/Service/Service.Core/Auth/IAuthService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Domain.Entities; - -namespace Service.Core.Auth; - -public interface IAuthService -{ - Task RegisterAsync(UserAccount userAccount, string password); - Task LoginAsync(string username, string password); -} diff --git a/src/Core/Service/Service.UserManagement/Service.UserManagement.csproj b/src/Core/Service/Service.UserManagement/Service.UserManagement.csproj new file mode 100644 index 0000000..776d954 --- /dev/null +++ b/src/Core/Service/Service.UserManagement/Service.UserManagement.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/src/Core/Service/Service.Core/User/IUserService.cs b/src/Core/Service/Service.UserManagement/User/IUserService.cs similarity index 86% rename from src/Core/Service/Service.Core/User/IUserService.cs rename to src/Core/Service/Service.UserManagement/User/IUserService.cs index a515cab..71dcfac 100644 --- a/src/Core/Service/Service.Core/User/IUserService.cs +++ b/src/Core/Service/Service.UserManagement/User/IUserService.cs @@ -1,6 +1,6 @@ using Domain.Entities; -namespace Service.Core.User; +namespace Service.UserManagement.User; public interface IUserService { diff --git a/src/Core/Service/Service.Core/User/UserService.cs b/src/Core/Service/Service.UserManagement/User/UserService.cs similarity index 93% rename from src/Core/Service/Service.Core/User/UserService.cs rename to src/Core/Service/Service.UserManagement/User/UserService.cs index 1959553..7391ec0 100644 --- a/src/Core/Service/Service.Core/User/UserService.cs +++ b/src/Core/Service/Service.UserManagement/User/UserService.cs @@ -1,7 +1,7 @@ using Domain.Entities; using Infrastructure.Repository.UserAccount; -namespace Service.Core.User; +namespace Service.UserManagement.User; public class UserService(IUserAccountRepository repository) : IUserService {