mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-15 01:54:43 +00:00
Remove repository tests, usages of registration dto used in repo
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
using Features.Auth.Dtos;
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Auth.Queries.Login;
|
||||
namespace Features.Auth.Commands.Login;
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates a user using their username and password and issues new tokens. Bound directly
|
||||
/// from the request body of <c>POST /api/auth/login</c>.
|
||||
/// </summary>
|
||||
public record LoginQuery(string Username, string Password) : IRequest<LoginPayload>;
|
||||
public record LoginCommand(string Username, string Password) : IRequest<LoginPayload>;
|
||||
@@ -6,22 +6,22 @@ using Features.Auth.Services;
|
||||
using Infrastructure.PasswordHashing;
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Auth.Queries.Login;
|
||||
namespace Features.Auth.Commands.Login;
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="LoginQuery" /> by verifying credentials and issuing access/refresh tokens.
|
||||
/// Handles <see cref="LoginCommand" /> by verifying credentials and issuing access/refresh tokens.
|
||||
/// </summary>
|
||||
public class LoginHandler(
|
||||
IAuthRepository authRepo,
|
||||
IPasswordInfrastructure passwordInfrastructure,
|
||||
ITokenService tokenService
|
||||
) : IRequestHandler<LoginQuery, LoginPayload>
|
||||
) : IRequestHandler<LoginCommand, LoginPayload>
|
||||
{
|
||||
/// <exception cref="UnauthorizedException">
|
||||
/// Thrown when the username does not match any account, the account has no active credential,
|
||||
/// or the supplied password does not match the stored hash.
|
||||
/// </exception>
|
||||
public async Task<LoginPayload> Handle(LoginQuery request, CancellationToken cancellationToken)
|
||||
public async Task<LoginPayload> Handle(LoginCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
UserAccount user =
|
||||
await authRepo.GetUserByUsernameAsync(request.Username)
|
||||
@@ -1,9 +1,9 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace Features.Auth.Queries.Login;
|
||||
namespace Features.Auth.Commands.Login;
|
||||
|
||||
/// <summary>Validates that <see cref="LoginQuery.Username" /> and <see cref="LoginQuery.Password" /> are present.</summary>
|
||||
public class LoginValidator : AbstractValidator<LoginQuery>
|
||||
/// <summary>Validates that <see cref="LoginCommand.Username" /> and <see cref="LoginCommand.Password" /> are present.</summary>
|
||||
public class LoginValidator : AbstractValidator<LoginCommand>
|
||||
{
|
||||
public LoginValidator()
|
||||
{
|
||||
@@ -34,14 +34,18 @@ public class RegisterUserHandler(
|
||||
string hashed = passwordInfrastructure.Hash(request.Password);
|
||||
|
||||
UserAccount createdUser = await authRepo.RegisterUserAsync(
|
||||
new UserRegistrationDto(
|
||||
request.Username,
|
||||
request.FirstName,
|
||||
request.LastName,
|
||||
request.Email,
|
||||
request.DateOfBirth,
|
||||
hashed
|
||||
)
|
||||
new UserAccount
|
||||
{
|
||||
Username = request.Username,
|
||||
FirstName = request.FirstName,
|
||||
LastName = request.LastName,
|
||||
DateOfBirth = request.DateOfBirth,
|
||||
Email = request.Email,
|
||||
UserCredential = new UserCredential
|
||||
{
|
||||
Hash = hashed,
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
string accessToken = tokenService.GenerateAccessToken(createdUser);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Features.Auth.Commands.ConfirmUser;
|
||||
using Features.Auth.Commands.Login;
|
||||
using Features.Auth.Commands.RefreshToken;
|
||||
using Features.Auth.Commands.RegisterUser;
|
||||
using Features.Auth.Commands.ResendConfirmationEmail;
|
||||
using Features.Auth.Dtos;
|
||||
using Features.Auth.Queries.Login;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -51,9 +51,9 @@ public class AuthController(IMediator mediator) : ControllerBase
|
||||
/// </remarks>
|
||||
[AllowAnonymous]
|
||||
[HttpPost("login")]
|
||||
public async Task<ActionResult<ResponseBody<LoginPayload>>> Login([FromBody] LoginQuery query)
|
||||
public async Task<ActionResult<ResponseBody<LoginPayload>>> Login([FromBody] LoginCommand command)
|
||||
{
|
||||
LoginPayload payload = await mediator.Send(query);
|
||||
LoginPayload payload = await mediator.Send(command);
|
||||
return Ok(
|
||||
new ResponseBody<LoginPayload>
|
||||
{
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace Features.Auth.Dtos;
|
||||
|
||||
/// <summary>Data required to persist a new user account and its initial credential.</summary>
|
||||
/// <param name="PasswordHash">The already-hashed password; never a plaintext value.</param>
|
||||
public record UserRegistrationDto(
|
||||
string Username,
|
||||
string FirstName,
|
||||
string LastName,
|
||||
string Email,
|
||||
DateTime DateOfBirth,
|
||||
string PasswordHash
|
||||
);
|
||||
@@ -20,4 +20,8 @@
|
||||
<ProjectReference Include="..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
|
||||
<ProjectReference Include="..\..\Shared\Shared.Application\Shared.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Queries\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -16,17 +16,14 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory)
|
||||
: Repository<UserAccount>(connectionFactory),
|
||||
IAuthRepository
|
||||
{
|
||||
private const string SelectColumns =
|
||||
"UserAccountID, Username, FirstName, LastName, Email, CreatedAt, UpdatedAt, DateOfBirth, RowVersion";
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>The account and credential are inserted within a single database transaction.</remarks>
|
||||
public async Task<UserAccount> RegisterUserAsync(UserRegistrationDto userRegistrationDto)
|
||||
public async Task<UserAccount> RegisterUserAsync(UserAccount ua)
|
||||
{
|
||||
await using DbConnection connection = await CreateConnection();
|
||||
await using DbTransaction transaction = await connection.BeginTransactionAsync();
|
||||
|
||||
var (username, firstName, lastName, email, dateOfBirth, passwordHash) = userRegistrationDto;
|
||||
|
||||
Guid userAccountId;
|
||||
|
||||
try
|
||||
@@ -38,15 +35,18 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory)
|
||||
OUTPUT INSERTED.UserAccountID
|
||||
VALUES (@Username, @FirstName, @LastName, @DateOfBirth, @Email);
|
||||
""",
|
||||
new { username, firstName, lastName, dateOfBirth, email },
|
||||
new { ua.Username, ua.FirstName, ua.LastName, ua.DateOfBirth, ua.Email},
|
||||
transaction
|
||||
)
|
||||
);
|
||||
|
||||
int credentialRows = await connection.ExecuteAsync(
|
||||
new CommandDefinition(
|
||||
"INSERT INTO dbo.UserCredential (UserAccountId, Hash) VALUES (@UserAccountId, @Hash);",
|
||||
new { UserAccountId = userAccountId, Hash = passwordHash },
|
||||
"""
|
||||
INSERT INTO dbo.UserCredential (UserAccountId, Hash)
|
||||
VALUES (@UserAccountId, @Hash);
|
||||
""",
|
||||
new { UserAccountId = userAccountId, Hash = ua.UserCredential?.Hash },
|
||||
transaction
|
||||
)
|
||||
);
|
||||
@@ -62,6 +62,7 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory)
|
||||
throw;
|
||||
}
|
||||
|
||||
// perform round trip to get new User entity
|
||||
return await GetUserByIdAsync(userAccountId)
|
||||
?? throw new Exception("Failed to retrieve newly registered user.");
|
||||
}
|
||||
@@ -71,7 +72,11 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory)
|
||||
{
|
||||
await using DbConnection connection = await CreateConnection();
|
||||
return await connection.QueryFirstOrDefaultAsync<UserAccount>(
|
||||
$"SELECT {SelectColumns} FROM dbo.UserAccount WHERE Email = @Email",
|
||||
"""
|
||||
SELECT UserAccountID, Username, FirstName, LastName, Email, CreatedAt, UpdatedAt, DateOfBirth, RowVersion
|
||||
FROM dbo.UserAccount
|
||||
WHERE Email = @Email
|
||||
""",
|
||||
new { Email = email }
|
||||
);
|
||||
}
|
||||
@@ -81,7 +86,11 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory)
|
||||
{
|
||||
await using DbConnection connection = await CreateConnection();
|
||||
return await connection.QueryFirstOrDefaultAsync<UserAccount>(
|
||||
$"SELECT {SelectColumns} FROM dbo.UserAccount WHERE Username = @Username",
|
||||
"""
|
||||
SELECT UserAccountID, Username, FirstName, LastName, Email, CreatedAt, UpdatedAt, DateOfBirth, RowVersion
|
||||
FROM dbo.UserAccount
|
||||
WHERE Username = @Username
|
||||
""",
|
||||
new { Username = username }
|
||||
);
|
||||
}
|
||||
@@ -111,7 +120,11 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory)
|
||||
bool exists =
|
||||
await connection.ExecuteScalarAsync<int?>(
|
||||
new CommandDefinition(
|
||||
"SELECT 1 FROM dbo.UserAccount WHERE UserAccountID = @UserAccountId",
|
||||
"""
|
||||
SELECT 1
|
||||
FROM dbo.UserAccount
|
||||
WHERE UserAccountID = @UserAccountId
|
||||
""",
|
||||
new { UserAccountId = userAccountId },
|
||||
transaction
|
||||
)
|
||||
@@ -122,7 +135,11 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory)
|
||||
|
||||
await connection.ExecuteAsync(
|
||||
new CommandDefinition(
|
||||
"UPDATE dbo.UserCredential SET IsRevoked = 1, RevokedAt = GETDATE() WHERE UserAccountId = @UserAccountId",
|
||||
"""
|
||||
UPDATE dbo.UserCredential
|
||||
SET IsRevoked = 1, RevokedAt = GETDATE()
|
||||
WHERE UserAccountId = @UserAccountId
|
||||
""",
|
||||
new { UserAccountId = userAccountId },
|
||||
transaction
|
||||
)
|
||||
@@ -130,7 +147,10 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory)
|
||||
|
||||
await connection.ExecuteAsync(
|
||||
new CommandDefinition(
|
||||
"INSERT INTO dbo.UserCredential (UserAccountId, Hash) VALUES (@UserAccountId, @Hash)",
|
||||
"""
|
||||
INSERT INTO dbo.UserCredential (UserAccountId, Hash)
|
||||
VALUES (@UserAccountId, @Hash)
|
||||
""",
|
||||
new { UserAccountId = userAccountId, Hash = newPasswordHash },
|
||||
transaction
|
||||
)
|
||||
@@ -150,7 +170,11 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory)
|
||||
{
|
||||
await using DbConnection connection = await CreateConnection();
|
||||
return await connection.QueryFirstOrDefaultAsync<UserAccount>(
|
||||
$"SELECT {SelectColumns} FROM dbo.UserAccount WHERE UserAccountID = @UserAccountId",
|
||||
"""
|
||||
SELECT UserAccountID, Username, FirstName, LastName, Email, CreatedAt, UpdatedAt, DateOfBirth, RowVersion
|
||||
FROM dbo.UserAccount
|
||||
WHERE UserAccountID = @UserAccountId
|
||||
""",
|
||||
new { UserAccountId = userAccountId }
|
||||
);
|
||||
}
|
||||
@@ -179,7 +203,10 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory)
|
||||
try
|
||||
{
|
||||
await connection.ExecuteAsync(
|
||||
"INSERT INTO dbo.UserVerification (UserAccountId, VerificationDateTime) VALUES (@UserAccountId, GETDATE())",
|
||||
"""
|
||||
INSERT INTO dbo.UserVerification (UserAccountId, VerificationDateTime)
|
||||
VALUES (@UserAccountId, GETDATE())
|
||||
""",
|
||||
new { UserAccountId = userAccountId }
|
||||
);
|
||||
}
|
||||
@@ -197,7 +224,11 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory)
|
||||
{
|
||||
await using DbConnection connection = await CreateConnection();
|
||||
int? result = await connection.ExecuteScalarAsync<int?>(
|
||||
"SELECT TOP 1 1 FROM dbo.UserVerification WHERE UserAccountID = @UserAccountId",
|
||||
"""
|
||||
SELECT TOP 1 1
|
||||
FROM dbo.UserVerification
|
||||
WHERE UserAccountID = @UserAccountId
|
||||
""",
|
||||
new { UserAccountId = userAccountId }
|
||||
);
|
||||
return result.HasValue;
|
||||
|
||||
@@ -13,7 +13,7 @@ public interface IAuthRepository
|
||||
/// returns the created account.
|
||||
/// </summary>
|
||||
/// <exception cref="Exception">Thrown when the newly registered user cannot be retrieved after registration.</exception>
|
||||
Task<UserAccount> RegisterUserAsync(UserRegistrationDto userRegistrationDto);
|
||||
Task<UserAccount> RegisterUserAsync(UserAccount ua);
|
||||
|
||||
/// <summary>Retrieves a user account by email, or <see langword="null" /> if none exists.</summary>
|
||||
Task<UserAccount?> GetUserByEmailAsync(string email);
|
||||
|
||||
Reference in New Issue
Block a user