mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-07-16 17:47:22 +00:00
code style cleanup
This commit is contained in:
@@ -4,7 +4,7 @@ using MediatR;
|
||||
namespace Features.Auth.Commands.ConfirmUser;
|
||||
|
||||
/// <summary>
|
||||
/// Validates a confirmation token and confirms the corresponding user account.
|
||||
/// Validates a confirmation token and confirms the corresponding user account.
|
||||
/// </summary>
|
||||
/// <param name="Token">The confirmation token issued to the user, typically delivered via email.</param>
|
||||
public record ConfirmUserCommand(string Token) : IRequest<ConfirmationPayload>;
|
||||
public record ConfirmUserCommand(string Token) : IRequest<ConfirmationPayload>;
|
||||
@@ -1,3 +1,4 @@
|
||||
using Domain.Entities;
|
||||
using Domain.Exceptions;
|
||||
using Features.Auth.Dtos;
|
||||
using Features.Auth.Repository;
|
||||
@@ -7,8 +8,8 @@ using MediatR;
|
||||
namespace Features.Auth.Commands.ConfirmUser;
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="ConfirmUserCommand"/> by validating the confirmation token and marking the
|
||||
/// corresponding user account as confirmed.
|
||||
/// Handles <see cref="ConfirmUserCommand" /> by validating the confirmation token and marking the
|
||||
/// corresponding user account as confirmed.
|
||||
/// </summary>
|
||||
/// <param name="authRepository">Repository used to look up and confirm user accounts.</param>
|
||||
/// <param name="tokenService">Service used to validate the confirmation token.</param>
|
||||
@@ -18,19 +19,16 @@ public class ConfirmUserHandler(
|
||||
) : IRequestHandler<ConfirmUserCommand, ConfirmationPayload>
|
||||
{
|
||||
/// <exception cref="UnauthorizedException">
|
||||
/// Thrown when the confirmation token is invalid or expired, or when the associated user account cannot be found.
|
||||
/// Thrown when the confirmation token is invalid or expired, or when the associated user account cannot be found.
|
||||
/// </exception>
|
||||
public async Task<ConfirmationPayload> Handle(ConfirmUserCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var validatedToken = await tokenService.ValidateConfirmationTokenAsync(request.Token);
|
||||
ValidatedToken validatedToken = await tokenService.ValidateConfirmationTokenAsync(request.Token);
|
||||
|
||||
var user = await authRepository.ConfirmUserAccountAsync(validatedToken.UserId);
|
||||
UserAccount? user = await authRepository.ConfirmUserAccountAsync(validatedToken.UserId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
throw new UnauthorizedException("User account not found");
|
||||
}
|
||||
if (user == null) throw new UnauthorizedException("User account not found");
|
||||
|
||||
return new ConfirmationPayload(user.UserAccountId, DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using MediatR;
|
||||
namespace Features.Auth.Commands.RefreshToken;
|
||||
|
||||
/// <summary>
|
||||
/// Exchanges a valid refresh token for a new access/refresh token pair. Bound directly from the
|
||||
/// request body of <c>POST /api/auth/refresh</c>.
|
||||
/// Exchanges a valid refresh token for a new access/refresh token pair. Bound directly from the
|
||||
/// request body of <c>POST /api/auth/refresh</c>.
|
||||
/// </summary>
|
||||
public record RefreshTokenCommand(string RefreshToken) : IRequest<LoginPayload>;
|
||||
public record RefreshTokenCommand(string RefreshToken) : IRequest<LoginPayload>;
|
||||
@@ -5,8 +5,8 @@ using MediatR;
|
||||
namespace Features.Auth.Commands.RefreshToken;
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="RefreshTokenCommand"/> by validating the refresh token and issuing a new
|
||||
/// access/refresh token pair.
|
||||
/// Handles <see cref="RefreshTokenCommand" /> by validating the refresh token and issuing a new
|
||||
/// access/refresh token pair.
|
||||
/// </summary>
|
||||
/// <param name="tokenService">Service used to validate and exchange the refresh token.</param>
|
||||
public class RefreshTokenHandler(ITokenService tokenService)
|
||||
@@ -14,7 +14,8 @@ public class RefreshTokenHandler(ITokenService tokenService)
|
||||
{
|
||||
public async Task<LoginPayload> Handle(RefreshTokenCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await tokenService.RefreshTokenAsync(request.RefreshToken);
|
||||
return new LoginPayload(result.UserAccount.UserAccountId, result.UserAccount.Username, result.RefreshToken, result.AccessToken);
|
||||
RefreshTokenResult result = await tokenService.RefreshTokenAsync(request.RefreshToken);
|
||||
return new LoginPayload(result.UserAccount.UserAccountId, result.UserAccount.Username, result.RefreshToken,
|
||||
result.AccessToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,12 @@ using FluentValidation;
|
||||
namespace Features.Auth.Commands.RefreshToken;
|
||||
|
||||
/// <summary>
|
||||
/// Validates <see cref="RefreshTokenCommand"/> instances before they are processed.
|
||||
/// Validates <see cref="RefreshTokenCommand" /> instances before they are processed.
|
||||
/// </summary>
|
||||
public class RefreshTokenValidator : AbstractValidator<RefreshTokenCommand>
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures a validation rule requiring <see cref="RefreshTokenCommand.RefreshToken"/> to be non-empty.
|
||||
/// Configures a validation rule requiring <see cref="RefreshTokenCommand.RefreshToken" /> to be non-empty.
|
||||
/// </summary>
|
||||
public RefreshTokenValidator()
|
||||
{
|
||||
@@ -16,4 +16,4 @@ public class RefreshTokenValidator : AbstractValidator<RefreshTokenCommand>
|
||||
.NotEmpty()
|
||||
.WithMessage("Refresh token is required");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,20 @@ using MediatR;
|
||||
namespace Features.Auth.Commands.RegisterUser;
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new user account. Bound directly from the request body of <c>POST /api/auth/register</c>.
|
||||
/// Registers a new user account. Bound directly from the request body of <c>POST /api/auth/register</c>.
|
||||
/// </summary>
|
||||
/// <param name="Username">The desired username; must be 3-64 characters and contain only letters, numbers, dots, underscores, and hyphens.</param>
|
||||
/// <param name="Username">
|
||||
/// The desired username; must be 3-64 characters and contain only letters, numbers, dots,
|
||||
/// underscores, and hyphens.
|
||||
/// </param>
|
||||
/// <param name="FirstName">The user's first name; up to 128 characters.</param>
|
||||
/// <param name="LastName">The user's last name; up to 128 characters.</param>
|
||||
/// <param name="Email">The user's email address; up to 128 characters and must be a valid email format.</param>
|
||||
/// <param name="DateOfBirth">The user's date of birth; the user must be at least 19 years old.</param>
|
||||
/// <param name="Password">The desired plaintext password; must be at least 8 characters and contain an uppercase letter, a lowercase letter, a number, and a special character.</param>
|
||||
/// <param name="Password">
|
||||
/// The desired plaintext password; must be at least 8 characters and contain an uppercase letter, a
|
||||
/// lowercase letter, a number, and a special character.
|
||||
/// </param>
|
||||
public record RegisterUserCommand(
|
||||
string Username,
|
||||
string FirstName,
|
||||
@@ -19,4 +25,4 @@ public record RegisterUserCommand(
|
||||
string Email,
|
||||
DateTime DateOfBirth,
|
||||
string Password
|
||||
) : IRequest<RegistrationPayload>;
|
||||
) : IRequest<RegistrationPayload>;
|
||||
@@ -1,3 +1,4 @@
|
||||
using Domain.Entities;
|
||||
using Domain.Exceptions;
|
||||
using Features.Auth.Dtos;
|
||||
using Features.Auth.Repository;
|
||||
@@ -9,9 +10,9 @@ using Shared.Application.Emails;
|
||||
namespace Features.Auth.Commands.RegisterUser;
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="RegisterUserCommand"/>: validates uniqueness, hashes the password, persists the
|
||||
/// account, issues access/refresh/confirmation tokens, and attempts to send the registration
|
||||
/// confirmation email via Features.Emails.
|
||||
/// Handles <see cref="RegisterUserCommand" />: validates uniqueness, hashes the password, persists the
|
||||
/// account, issues access/refresh/confirmation tokens, and attempts to send the registration
|
||||
/// confirmation email via Features.Emails.
|
||||
/// </summary>
|
||||
/// <param name="authRepo">Repository used to check for existing users and persist the new account.</param>
|
||||
/// <param name="passwordInfrastructure">Infrastructure component used to hash the user's plain-text password.</param>
|
||||
@@ -25,15 +26,15 @@ public class RegisterUserHandler(
|
||||
) : IRequestHandler<RegisterUserCommand, RegistrationPayload>
|
||||
{
|
||||
/// <exception cref="ConflictException">
|
||||
/// Thrown when an existing account already has the same username or email address.
|
||||
/// Thrown when an existing account already has the same username or email address.
|
||||
/// </exception>
|
||||
public async Task<RegistrationPayload> Handle(RegisterUserCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
await ValidateUserDoesNotExist(request.Username, request.Email);
|
||||
|
||||
var hashed = passwordInfrastructure.Hash(request.Password);
|
||||
string hashed = passwordInfrastructure.Hash(request.Password);
|
||||
|
||||
var createdUser = await authRepo.RegisterUserAsync(
|
||||
UserAccount createdUser = await authRepo.RegisterUserAsync(
|
||||
request.Username,
|
||||
request.FirstName,
|
||||
request.LastName,
|
||||
@@ -42,16 +43,15 @@ public class RegisterUserHandler(
|
||||
hashed
|
||||
);
|
||||
|
||||
var accessToken = tokenService.GenerateAccessToken(createdUser);
|
||||
var refreshToken = tokenService.GenerateRefreshToken(createdUser);
|
||||
var confirmationToken = tokenService.GenerateConfirmationToken(createdUser);
|
||||
string accessToken = tokenService.GenerateAccessToken(createdUser);
|
||||
string refreshToken = tokenService.GenerateRefreshToken(createdUser);
|
||||
string confirmationToken = tokenService.GenerateConfirmationToken(createdUser);
|
||||
|
||||
if (string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(refreshToken))
|
||||
{
|
||||
return new RegistrationPayload(createdUser.UserAccountId, createdUser.Username, string.Empty, string.Empty, false);
|
||||
}
|
||||
return new RegistrationPayload(createdUser.UserAccountId, createdUser.Username, string.Empty, string.Empty,
|
||||
false);
|
||||
|
||||
var emailSent = false;
|
||||
bool emailSent = false;
|
||||
try
|
||||
{
|
||||
await mediator.Send(
|
||||
@@ -67,20 +67,19 @@ public class RegisterUserHandler(
|
||||
// ignored
|
||||
}
|
||||
|
||||
return new RegistrationPayload(createdUser.UserAccountId, createdUser.Username, refreshToken, accessToken, emailSent);
|
||||
return new RegistrationPayload(createdUser.UserAccountId, createdUser.Username, refreshToken, accessToken,
|
||||
emailSent);
|
||||
}
|
||||
|
||||
/// <exception cref="ConflictException">
|
||||
/// Thrown when an existing account already has the same username or email address.
|
||||
/// Thrown when an existing account already has the same username or email address.
|
||||
/// </exception>
|
||||
private async Task ValidateUserDoesNotExist(string username, string email)
|
||||
{
|
||||
var existingUsername = await authRepo.GetUserByUsernameAsync(username);
|
||||
var existingEmail = await authRepo.GetUserByEmailAsync(email);
|
||||
UserAccount? existingUsername = await authRepo.GetUserByUsernameAsync(username);
|
||||
UserAccount? existingEmail = await authRepo.GetUserByEmailAsync(email);
|
||||
|
||||
if (existingUsername != null || existingEmail != null)
|
||||
{
|
||||
throw new ConflictException("Username or email already exists");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,13 @@ using FluentValidation;
|
||||
namespace Features.Auth.Commands.RegisterUser;
|
||||
|
||||
/// <summary>
|
||||
/// Validates <see cref="RegisterUserCommand"/> instances before they are processed.
|
||||
/// Validates <see cref="RegisterUserCommand" /> instances before they are processed.
|
||||
/// </summary>
|
||||
public class RegisterUserValidator : AbstractValidator<RegisterUserCommand>
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures validation rules for username format and length, first/last name length, email format and
|
||||
/// length, minimum age based on date of birth, and password strength requirements.
|
||||
/// Configures validation rules for username format and length, first/last name length, email format and
|
||||
/// length, minimum age based on date of birth, and password strength requirements.
|
||||
/// </summary>
|
||||
public RegisterUserValidator()
|
||||
{
|
||||
@@ -65,4 +65,4 @@ public class RegisterUserValidator : AbstractValidator<RegisterUserCommand>
|
||||
"Password must contain at least one special character"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using MediatR;
|
||||
namespace Features.Auth.Commands.ResendConfirmationEmail;
|
||||
|
||||
/// <summary>
|
||||
/// Resends the account confirmation email to a user, generating a fresh confirmation token.
|
||||
/// Resends the account confirmation email to a user, generating a fresh confirmation token.
|
||||
/// </summary>
|
||||
/// <param name="UserId">The unique identifier of the user requesting the resend.</param>
|
||||
public record ResendConfirmationEmailCommand(Guid UserId) : IRequest;
|
||||
public record ResendConfirmationEmailCommand(Guid UserId) : IRequest;
|
||||
@@ -1,3 +1,4 @@
|
||||
using Domain.Entities;
|
||||
using Features.Auth.Repository;
|
||||
using Features.Auth.Services;
|
||||
using MediatR;
|
||||
@@ -6,15 +7,15 @@ using Shared.Application.Emails;
|
||||
namespace Features.Auth.Commands.ResendConfirmationEmail;
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="ResendConfirmationEmailCommand"/> by generating a fresh confirmation token and
|
||||
/// sending it via Features.Emails.
|
||||
/// Handles <see cref="ResendConfirmationEmailCommand" /> by generating a fresh confirmation token and
|
||||
/// sending it via Features.Emails.
|
||||
/// </summary>
|
||||
/// <param name="authRepository">Repository used to look up the user and check verification status.</param>
|
||||
/// <param name="tokenService">Service used to generate the confirmation token.</param>
|
||||
/// <param name="mediator">Used to send the cross-slice command that triggers the email.</param>
|
||||
/// <remarks>
|
||||
/// Returns silently without sending an email if the user does not exist (to prevent user enumeration)
|
||||
/// or if the user's account is already verified.
|
||||
/// Returns silently without sending an email if the user does not exist (to prevent user enumeration)
|
||||
/// or if the user's account is already verified.
|
||||
/// </remarks>
|
||||
public class ResendConfirmationEmailHandler(
|
||||
IAuthRepository authRepository,
|
||||
@@ -24,21 +25,15 @@ public class ResendConfirmationEmailHandler(
|
||||
{
|
||||
public async Task Handle(ResendConfirmationEmailCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = await authRepository.GetUserByIdAsync(request.UserId);
|
||||
if (user == null)
|
||||
{
|
||||
return; // Silent return to prevent user enumeration
|
||||
}
|
||||
UserAccount? user = await authRepository.GetUserByIdAsync(request.UserId);
|
||||
if (user == null) return; // Silent return to prevent user enumeration
|
||||
|
||||
if (await authRepository.IsUserVerifiedAsync(request.UserId))
|
||||
{
|
||||
return; // Already confirmed, no-op
|
||||
}
|
||||
if (await authRepository.IsUserVerifiedAsync(request.UserId)) return; // Already confirmed, no-op
|
||||
|
||||
var confirmationToken = tokenService.GenerateConfirmationToken(user);
|
||||
string confirmationToken = tokenService.GenerateConfirmationToken(user);
|
||||
await mediator.Send(
|
||||
new SendResendConfirmationEmailCommand(user.FirstName, user.Email, confirmationToken),
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user