Register endpoint finished

This commit is contained in:
2026-01-14 01:37:46 +01:00
Unverified
parent 2b9ddef055
commit 831394ae0e
9 changed files with 64 additions and 31 deletions

View File

@@ -1,8 +1,7 @@
namespace TimetableDesigner.Backend.Services.Authentication.DTO.API; namespace TimetableDesigner.Backend.Services.Authentication.DTO.API;
public class RegisterRequest public record RegisterRequest(
{ string Email,
public string Email { get; set; } = null!; string Password,
public string Password { get; set; } = null!; string PasswordConfirmation
public string PasswordConfirmation { get; set; } = null!; );
}

View File

@@ -1,6 +1,6 @@
namespace TimetableDesigner.Backend.Services.Authentication.DTO.API; namespace TimetableDesigner.Backend.Services.Authentication.DTO.API;
public class RegisterResponse public record RegisterResponse(
{ long Id,
string Email
} );

View File

@@ -1,5 +1,8 @@
using MediatR; using FluentValidation;
using FluentValidation.Results;
using MediatR;
using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register; using TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register;
using TimetableDesigner.Backend.Services.Authentication.DTO.API; using TimetableDesigner.Backend.Services.Authentication.DTO.API;
@@ -10,6 +13,10 @@ public static class Endpoints
public static IEndpointRouteBuilder MapEndpoints(this IEndpointRouteBuilder app) public static IEndpointRouteBuilder MapEndpoints(this IEndpointRouteBuilder app)
{ {
app.MapPost("/register", Register) app.MapPost("/register", Register)
.AllowAnonymous()
.Produces<RegisterResponse>(201)
.Produces<HttpValidationProblemDetails>(400)
.Produces(500)
.WithName("Register"); .WithName("Register");
app.MapPost("/authenticate_password", AuthenticatePassword) app.MapPost("/authenticate_password", AuthenticatePassword)
.WithName("AuthenticatePassword"); .WithName("AuthenticatePassword");
@@ -19,19 +26,27 @@ public static class Endpoints
return app; return app;
} }
public static async Task<Results<Created<RegisterResponse>, InternalServerError>> Register(RegisterRequest request, CancellationToken cancellationToken, IMediator mediator) private static async Task<Results<Created<RegisterResponse>, ValidationProblem>> Register(IMediator mediator, IValidator<RegisterRequest> validator, RegisterRequest request)
{ {
RegisterCommand registerCommand = request.ToCommand(); ValidationResult validationResult = await validator.ValidateAsync(request);
RegisterData data = await mediator.Send(registerCommand, cancellationToken); if (!validationResult.IsValid)
return Results.Created<RegisterResponse>($"accounts/0", null); {
return TypedResults.ValidationProblem(validationResult.ToDictionary());
} }
public static async Task<Results<Ok<AuthenticateResponse>, ProblemHttpResult>> AuthenticatePassword(AuthenticatePasswordRequest request, CancellationToken cancellationToken) RegisterCommand registerCommand = request.ToCommand();
RegisterResult result = await mediator.Send(registerCommand);
RegisterResponse response = result.ToResponse();
return TypedResults.Created($"accounts/{response.Id}", response);
}
public static async Task<Results<Ok<AuthenticateResponse>, ProblemHttpResult>> AuthenticatePassword(AuthenticatePasswordRequest request)
{ {
return null; return null;
} }
public static async Task<Results<Ok<AuthenticateResponse>, ProblemHttpResult>> AuthenticateToken(AuthenticateTokenRequest request, CancellationToken cancellationToken) public static async Task<Results<Ok<AuthenticateResponse>, ProblemHttpResult>> AuthenticateToken(AuthenticateTokenRequest request)
{ {
return null; return null;
} }

View File

@@ -2,4 +2,7 @@
namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register; namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register;
public record RegisterCommand(string Email, string Password) : IRequest<RegisterData>; public record RegisterCommand(
string Email,
string Password
) : IRequest<RegisterResult>;

View File

@@ -1,10 +1,11 @@
using MediatR; using MediatR;
using TimetableDesigner.Backend.Services.Authentication.Application.Helpers; using TimetableDesigner.Backend.Services.Authentication.Application.Helpers;
using TimetableDesigner.Backend.Services.Authentication.Database; using TimetableDesigner.Backend.Services.Authentication.Database;
using TimetableDesigner.Backend.Services.Authentication.Database.Model;
namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register; namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register;
public class RegisterHandler : IRequestHandler<RegisterCommand, RegisterData> public class RegisterHandler : IRequestHandler<RegisterCommand, RegisterResult>
{ {
private readonly DatabaseContext _databaseContext; private readonly DatabaseContext _databaseContext;
private readonly IPasswordHasher _passwordHasher; private readonly IPasswordHasher _passwordHasher;
@@ -15,9 +16,22 @@ public class RegisterHandler : IRequestHandler<RegisterCommand, RegisterData>
_passwordHasher = passwordHasher; _passwordHasher = passwordHasher;
} }
public async Task<RegisterData> Handle(RegisterCommand command, CancellationToken cancellationToken) public async Task<RegisterResult> Handle(RegisterCommand command, CancellationToken cancellationToken)
{ {
PasswordHashData hash = _passwordHasher.CreateHash(command.Password); PasswordHashData hash = _passwordHasher.CreateHash(command.Password);
Account account = new Account
{
Email = command.Email,
Password = hash.Hash,
PasswordSalt = hash.Salt,
};
await _databaseContext.Accounts.AddAsync(account, cancellationToken);
await _databaseContext.SaveChangesAsync(cancellationToken);
// Publish event (probably in transaction)
RegisterResult result = account.ToResult();
return result;
} }
} }

View File

@@ -1,4 +1,5 @@
using TimetableDesigner.Backend.Services.Authentication.DTO.API; using TimetableDesigner.Backend.Services.Authentication.Database.Model;
using TimetableDesigner.Backend.Services.Authentication.DTO.API;
namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register; namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register;
@@ -6,4 +7,10 @@ public static class RegisterMappers
{ {
public static RegisterCommand ToCommand(this RegisterRequest request) => public static RegisterCommand ToCommand(this RegisterRequest request) =>
new RegisterCommand(request.Email, request.Password); new RegisterCommand(request.Email, request.Password);
public static RegisterResult ToResult(this Account account) =>
new RegisterResult(account.Id, account.Email);
public static RegisterResponse ToResponse(this RegisterResult result) =>
new RegisterResponse(result.Id, result.Email);
} }

View File

@@ -1,6 +1,6 @@
namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register; namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register;
public class RegisterData public record RegisterResult(
{ long Id,
string Email
} );

View File

@@ -23,11 +23,7 @@ public class AccountConfiguration : IEntityTypeConfiguration<Account>
.HasMaxLength(1000) .HasMaxLength(1000)
.IsRequired(); .IsRequired();
builder.Property(x => x.PasswordSaltLeft) builder.Property(x => x.PasswordSalt)
.HasMaxLength(20)
.IsRequired();
builder.Property(x => x.PasswordSaltRight)
.HasMaxLength(20) .HasMaxLength(20)
.IsRequired(); .IsRequired();

View File

@@ -5,8 +5,7 @@ public class Account
public long Id { get; set; } public long Id { get; set; }
public string Email { get; set; } = null!; public string Email { get; set; } = null!;
public byte[] Password { get; set; } = null!; public byte[] Password { get; set; } = null!;
public string PasswordSaltLeft { get; set; } = null!; public string PasswordSalt { get; set; } = null!;
public string PasswordSaltRight { get; set; } = null!;
public uint Version { get; set; } public uint Version { get; set; }
public virtual IEnumerable<RefreshToken> RefreshTokens { get; set; } = new List<RefreshToken>(); public virtual IEnumerable<RefreshToken> RefreshTokens { get; set; } = new List<RefreshToken>();