mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
Update namespace organization in service layer
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
using BusinessLayer.Services;
|
|
||||||
using DataAccessLayer.Entities;
|
using DataAccessLayer.Entities;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using ServiceCore.Services;
|
||||||
|
|
||||||
namespace WebAPI.Controllers
|
namespace WebAPI.Controllers
|
||||||
{
|
{
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
public class AuthController(IAuthService auth) : ControllerBase
|
public class AuthController(IAuthService auth, IJwtService jwtService) : ControllerBase
|
||||||
{
|
{
|
||||||
public record RegisterRequest(
|
public record RegisterRequest(
|
||||||
string Username,
|
string Username,
|
||||||
@@ -39,9 +39,15 @@ namespace WebAPI.Controllers
|
|||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
public async Task<ActionResult> Login([FromBody] LoginRequest req)
|
public async Task<ActionResult> Login([FromBody] LoginRequest req)
|
||||||
{
|
{
|
||||||
var ok = await auth.LoginAsync(req.UsernameOrEmail, req.Password);
|
var userAccount = await auth.LoginAsync(req.UsernameOrEmail, req.Password);
|
||||||
if (!ok) return Unauthorized();
|
if (userAccount is null)
|
||||||
return Ok(new { success = true });
|
{
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
var jwt = jwtService.GenerateJwt(userAccount.UserAccountId, userAccount.Username, userAccount.DateOfBirth);
|
||||||
|
|
||||||
|
return Ok(new { AccessToken = jwt, Message = "Logged in successfully." });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using BusinessLayer.Services;
|
|
||||||
using DataAccessLayer.Entities;
|
using DataAccessLayer.Entities;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using ServiceCore.Services;
|
||||||
|
|
||||||
namespace WebAPI.Controllers
|
namespace WebAPI.Controllers
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using BusinessLayer.Services;
|
|
||||||
using DataAccessLayer.Repositories.UserAccount;
|
using DataAccessLayer.Repositories.UserAccount;
|
||||||
using DataAccessLayer.Repositories.UserCredential;
|
using DataAccessLayer.Repositories.UserCredential;
|
||||||
using DataAccessLayer.Sql;
|
using DataAccessLayer.Sql;
|
||||||
|
using ServiceCore.Services;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ builder.Services.AddScoped<IUserAccountRepository, UserAccountRepository>();
|
|||||||
builder.Services.AddScoped<IUserService, UserService>();
|
builder.Services.AddScoped<IUserService, UserService>();
|
||||||
builder.Services.AddScoped<IUserCredentialRepository, UserCredentialRepository>();
|
builder.Services.AddScoped<IUserCredentialRepository, UserCredentialRepository>();
|
||||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||||
|
builder.Services.AddScoped<IJwtService, JwtService>();
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.UseSwagger();
|
app.UseSwagger();
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace DataAccessLayer.Repositories.UserCredential
|
|||||||
command.CommandText = "USP_RotateUserCredential";
|
command.CommandText = "USP_RotateUserCredential";
|
||||||
command.CommandType = CommandType.StoredProcedure;
|
command.CommandType = CommandType.StoredProcedure;
|
||||||
|
|
||||||
AddParameter(command, "@UserAccountId", userAccountId);
|
AddParameter(command, "@UserAccountId_", userAccountId);
|
||||||
AddParameter(command, "@Hash", credential.Hash);
|
AddParameter(command, "@Hash", credential.Hash);
|
||||||
|
|
||||||
await command.ExecuteNonQueryAsync();
|
await command.ExecuteNonQueryAsync();
|
||||||
@@ -27,7 +27,7 @@ namespace DataAccessLayer.Repositories.UserCredential
|
|||||||
command.CommandText = "USP_GetActiveUserCredentialByUserAccountId";
|
command.CommandText = "USP_GetActiveUserCredentialByUserAccountId";
|
||||||
command.CommandType = CommandType.StoredProcedure;
|
command.CommandType = CommandType.StoredProcedure;
|
||||||
|
|
||||||
AddParameter(command, "@UserAccountId", userAccountId);
|
AddParameter(command, "@UserAccountId_", userAccountId);
|
||||||
|
|
||||||
await using var reader = await command.ExecuteReaderAsync();
|
await using var reader = await command.ExecuteReaderAsync();
|
||||||
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||||
@@ -40,7 +40,7 @@ namespace DataAccessLayer.Repositories.UserCredential
|
|||||||
command.CommandText = "USP_InvalidateUserCredential";
|
command.CommandText = "USP_InvalidateUserCredential";
|
||||||
command.CommandType = CommandType.StoredProcedure;
|
command.CommandType = CommandType.StoredProcedure;
|
||||||
|
|
||||||
AddParameter(command, "@UserAccountId", userAccountId);
|
AddParameter(command, "@UserAccountId_", userAccountId);
|
||||||
await command.ExecuteNonQueryAsync();
|
await command.ExecuteNonQueryAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<RootNamespace>BusinessLayer</RootNamespace>
|
<RootNamespace>ServiceCore</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using DataAccessLayer.Entities;
|
using DataAccessLayer.Entities;
|
||||||
using DataAccessLayer.Repositories.UserAccount;
|
using DataAccessLayer.Repositories.UserAccount;
|
||||||
using DataAccessLayer.Repositories.UserCredential;
|
|
||||||
|
|
||||||
namespace BusinessLayer.Services
|
namespace ServiceCore.Services
|
||||||
{
|
{
|
||||||
public class AuthService(IUserAccountRepository userRepo, IUserCredentialRepository credRepo) : IAuthService
|
public class AuthService(IUserAccountRepository userRepo, IUserCredentialRepository credRepo) : IAuthService
|
||||||
{
|
{
|
||||||
@@ -26,18 +25,31 @@ namespace BusinessLayer.Services
|
|||||||
return userAccount;
|
return userAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> LoginAsync(string usernameOrEmail, string password)
|
public async Task<UserAccount?> LoginAsync(string usernameOrEmail, string password)
|
||||||
{
|
{
|
||||||
// Attempt lookup by username, then email
|
// Attempt lookup by username, then email
|
||||||
var user = await userRepo.GetByUsernameAsync(usernameOrEmail)
|
var user = await userRepo.GetByUsernameAsync(usernameOrEmail)
|
||||||
?? await userRepo.GetByEmailAsync(usernameOrEmail);
|
?? await userRepo.GetByEmailAsync(usernameOrEmail);
|
||||||
|
// the user was not found
|
||||||
|
if (user is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (user is null) return false;
|
// they don't have an active credential
|
||||||
|
// @todo handle expired passwords
|
||||||
var activeCred = await credRepo.GetActiveCredentialByUserAccountIdAsync(user.UserAccountId);
|
var activeCred = await credRepo.GetActiveCredentialByUserAccountIdAsync(user.UserAccountId);
|
||||||
if (activeCred is null) return false;
|
if (activeCred is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return PasswordHasher.Verify(password, activeCred.Hash);
|
if (!PasswordHasher.Verify(password, activeCred.Hash))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InvalidateAsync(Guid userAccountId)
|
public async Task InvalidateAsync(Guid userAccountId)
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
using DataAccessLayer.Entities;
|
using DataAccessLayer.Entities;
|
||||||
|
|
||||||
namespace BusinessLayer.Services
|
namespace ServiceCore.Services
|
||||||
{
|
{
|
||||||
public interface IAuthService
|
public interface IAuthService
|
||||||
{
|
{
|
||||||
Task<UserAccount> RegisterAsync(UserAccount userAccount, string password);
|
Task<UserAccount> RegisterAsync(UserAccount userAccount, string password);
|
||||||
Task<bool> LoginAsync(string usernameOrEmail, string password);
|
Task<UserAccount?> LoginAsync(string usernameOrEmail, string password);
|
||||||
Task InvalidateAsync(Guid userAccountId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
6
src/Core/Service/Service.Core/Services/IJwtService.cs
Normal file
6
src/Core/Service/Service.Core/Services/IJwtService.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace ServiceCore.Services;
|
||||||
|
|
||||||
|
public interface IJwtService
|
||||||
|
{
|
||||||
|
string GenerateJwt(Guid userId, string username, DateTime expiry);
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
using DataAccessLayer.Entities;
|
using DataAccessLayer.Entities;
|
||||||
|
|
||||||
namespace BusinessLayer.Services
|
namespace ServiceCore.Services
|
||||||
{
|
{
|
||||||
public interface IUserService
|
public interface IUserService
|
||||||
{
|
{
|
||||||
|
|||||||
38
src/Core/Service/Service.Core/Services/JwtService.cs
Normal file
38
src/Core/Service/Service.Core/Services/JwtService.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.IdentityModel.JsonWebTokens;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using JwtRegisteredClaimNames = System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames;
|
||||||
|
|
||||||
|
namespace ServiceCore.Services;
|
||||||
|
public class JwtService(IConfiguration config) : IJwtService
|
||||||
|
{
|
||||||
|
private readonly string? _secret = config["Jwt:Secret"];
|
||||||
|
|
||||||
|
public string GenerateJwt(Guid userId, string username, DateTime expiry)
|
||||||
|
{
|
||||||
|
var handler = new JsonWebTokenHandler();
|
||||||
|
|
||||||
|
var key = Encoding.UTF8.GetBytes(_secret ?? throw new InvalidOperationException("secret not set"));
|
||||||
|
|
||||||
|
// Base claims (always present)
|
||||||
|
var claims = new List<Claim>
|
||||||
|
{
|
||||||
|
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
||||||
|
new(JwtRegisteredClaimNames.UniqueName, username),
|
||||||
|
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokenDescriptor = new SecurityTokenDescriptor
|
||||||
|
{
|
||||||
|
Subject = new ClaimsIdentity(claims),
|
||||||
|
Expires = expiry,
|
||||||
|
SigningCredentials = new SigningCredentials(
|
||||||
|
new SymmetricSecurityKey(key),
|
||||||
|
SecurityAlgorithms.HmacSha256)
|
||||||
|
};
|
||||||
|
|
||||||
|
return handler.CreateToken(tokenDescriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ using System.Security.Cryptography;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Konscious.Security.Cryptography;
|
using Konscious.Security.Cryptography;
|
||||||
|
|
||||||
namespace BusinessLayer.Services
|
namespace ServiceCore.Services
|
||||||
{
|
{
|
||||||
public static class PasswordHasher
|
public static class PasswordHasher
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using DataAccessLayer.Entities;
|
using DataAccessLayer.Entities;
|
||||||
using DataAccessLayer.Repositories;
|
|
||||||
using DataAccessLayer.Repositories.UserAccount;
|
using DataAccessLayer.Repositories.UserAccount;
|
||||||
|
|
||||||
namespace BusinessLayer.Services
|
namespace ServiceCore.Services
|
||||||
{
|
{
|
||||||
public class UserService(IUserAccountRepository repository) : IUserService
|
public class UserService(IUserAccountRepository repository) : IUserService
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user