main project split, authpassword endpoint created
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
using MediatR;
|
||||
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Core.Commands.AuthPassword;
|
||||
|
||||
public record AuthPasswordCommand
|
||||
(
|
||||
string Email,
|
||||
string Password,
|
||||
bool RememberMe
|
||||
)
|
||||
: IRequest<AuthPasswordResult>;
|
||||
@@ -0,0 +1,36 @@
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TimetableDesigner.Backend.Services.Authentication.Core.Helpers;
|
||||
using TimetableDesigner.Backend.Services.Authentication.Database;
|
||||
using TimetableDesigner.Backend.Services.Authentication.Database.Model;
|
||||
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Core.Commands.AuthPassword;
|
||||
|
||||
public class AuthPasswordHandler : IRequestHandler<AuthPasswordCommand, AuthPasswordResult>
|
||||
{
|
||||
private readonly DatabaseContext _databaseContext;
|
||||
private readonly IPasswordHasher _passwordHasher;
|
||||
|
||||
public AuthPasswordHandler(DatabaseContext databaseContext, IPasswordHasher passwordHasher)
|
||||
{
|
||||
_databaseContext = databaseContext;
|
||||
_passwordHasher = passwordHasher;
|
||||
}
|
||||
|
||||
public async Task<AuthPasswordResult> Handle(AuthPasswordCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
Account? account = await _databaseContext.Accounts.FirstOrDefaultAsync(x => x.Email == request.Email, cancellationToken);
|
||||
if (account is null)
|
||||
{
|
||||
return AuthPasswordResult.Failure();
|
||||
}
|
||||
|
||||
PasswordHashData hash = new PasswordHashData(account.Password, account.PasswordSalt);
|
||||
if (!_passwordHasher.ValidatePassword(hash, request.Password))
|
||||
{
|
||||
return AuthPasswordResult.Failure();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Core.Commands.AuthPassword;
|
||||
|
||||
public record AuthPasswordResult
|
||||
{
|
||||
public bool IsSuccess { get; }
|
||||
public string? AccessToken { get; }
|
||||
public string? RefreshToken { get; }
|
||||
|
||||
private AuthPasswordResult(bool isSuccess, string? accessToken, string? refreshToken)
|
||||
{
|
||||
IsSuccess = isSuccess;
|
||||
AccessToken = accessToken;
|
||||
RefreshToken = refreshToken;
|
||||
}
|
||||
|
||||
public static AuthPasswordResult Success(string accessToken, string refreshToken) =>
|
||||
new AuthPasswordResult(true, accessToken, refreshToken);
|
||||
|
||||
public static AuthPasswordResult Failure() =>
|
||||
new AuthPasswordResult(false, null, null);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using MediatR;
|
||||
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Core.Commands.Register;
|
||||
|
||||
public record RegisterCommand
|
||||
(
|
||||
string Email,
|
||||
string Password
|
||||
)
|
||||
: IRequest<RegisterResult>;
|
||||
@@ -1,23 +1,19 @@
|
||||
using MediatR;
|
||||
using TimetableDesigner.Backend.Events;
|
||||
using TimetableDesigner.Backend.Services.Authentication.Application.Helpers;
|
||||
using TimetableDesigner.Backend.Services.Authentication.Core.Helpers;
|
||||
using TimetableDesigner.Backend.Services.Authentication.Database;
|
||||
using TimetableDesigner.Backend.Services.Authentication.Database.Model;
|
||||
using TimetableDesigner.Backend.Services.Authentication.DTO.Events;
|
||||
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register;
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Core.Commands.Register;
|
||||
|
||||
public class RegisterHandler : IRequestHandler<RegisterCommand, RegisterResult>
|
||||
{
|
||||
private readonly DatabaseContext _databaseContext;
|
||||
private readonly IPasswordHasher _passwordHasher;
|
||||
private readonly IEventQueuePublisher _eventQueuePublisher;
|
||||
|
||||
public RegisterHandler(DatabaseContext databaseContext, IPasswordHasher passwordHasher, IEventQueuePublisher eventQueuePublisher)
|
||||
public RegisterHandler(DatabaseContext databaseContext, IPasswordHasher passwordHasher)
|
||||
{
|
||||
_databaseContext = databaseContext;
|
||||
_passwordHasher = passwordHasher;
|
||||
_eventQueuePublisher = eventQueuePublisher;
|
||||
}
|
||||
|
||||
public async Task<RegisterResult> Handle(RegisterCommand command, CancellationToken cancellationToken)
|
||||
@@ -31,12 +27,13 @@ public class RegisterHandler : IRequestHandler<RegisterCommand, RegisterResult>
|
||||
PasswordSalt = hash.Salt,
|
||||
};
|
||||
await _databaseContext.Accounts.AddAsync(account, cancellationToken);
|
||||
|
||||
// Change to outbox pattern
|
||||
//RegisterEvent eventData = account.ToEvent();
|
||||
//await _eventQueuePublisher.PublishAsync(eventData);
|
||||
|
||||
await _databaseContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
RegisterEvent eventData = account.ToEvent();
|
||||
await _eventQueuePublisher.PublishAsync(eventData);
|
||||
|
||||
RegisterResult result = account.ToResult();
|
||||
return result;
|
||||
return new RegisterResult(account.Id, account.Email);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Core.Commands.Register;
|
||||
|
||||
public record RegisterResult
|
||||
(
|
||||
long Id,
|
||||
string Email
|
||||
);
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Application.Helpers;
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Core.Helpers;
|
||||
|
||||
public interface IPasswordHasher
|
||||
{
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Core.Helpers;
|
||||
|
||||
public record PasswordHashData(
|
||||
byte[] Hash,
|
||||
string Salt
|
||||
);
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Text;
|
||||
using Konscious.Security.Cryptography;
|
||||
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Application.Helpers;
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Core.Helpers;
|
||||
|
||||
public class PasswordHasher : IPasswordHasher
|
||||
{
|
||||
@@ -0,0 +1,27 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Core.Helpers;
|
||||
|
||||
public class TokenGenerator
|
||||
{
|
||||
/*
|
||||
public TokenGenerator(IConfiguration configuration, DatabaseContext databaseContext)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string GenerateAccessToken(Account account)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public async Task<string> GenerateRefreshTokenAsync(Account account)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public async Task<string> ExtendRefreshTokenAsync()
|
||||
{
|
||||
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TimetableDesigner.Backend.Services.Authentication.Database\TimetableDesigner.Backend.Services.Authentication.Database.csproj" />
|
||||
<ProjectReference Include="..\TimetableDesigner.Backend.Services.Authentication.DTO.Events\TimetableDesigner.Backend.Services.Authentication.DTO.Events.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" />
|
||||
<PackageReference Include="MediatR" Version="14.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.DTO.WebAPI;
|
||||
|
||||
public record AuthPasswordRequest
|
||||
(
|
||||
string Email,
|
||||
string Password,
|
||||
bool RememberMe
|
||||
);
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.DTO.WebAPI;
|
||||
|
||||
public record AuthResponse
|
||||
(
|
||||
string AccessToken,
|
||||
string RefreshToken
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.DTO.WebAPI;
|
||||
|
||||
public class AuthenticateResponse
|
||||
public class AuthTokenRequest
|
||||
{
|
||||
public string AccessToken { get; set; } = null!;
|
||||
public string RefreshToken { get; set; } = null!;
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.DTO.WebAPI;
|
||||
|
||||
public class AuthenticatePasswordRequest
|
||||
{
|
||||
public string Email { get; set; } = null!;
|
||||
public string Password { get; set; } = null!;
|
||||
public bool RememberMe { get; set; }
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.DTO.WebAPI;
|
||||
|
||||
public class AuthenticateTokenRequest
|
||||
{
|
||||
public string AccessToken { get; set; } = null!;
|
||||
public string RefreshToken { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,4 +1,6 @@
|
||||
<Solution>
|
||||
<Project Path="TimetableDesigner.Backend.Services.Authentication.Core/TimetableDesigner.Backend.Services.Authentication.Core.csproj" />
|
||||
<Project Path="TimetableDesigner.Backend.Services.Authentication.Database/TimetableDesigner.Backend.Services.Authentication.Database.csproj" />
|
||||
<Project Path="TimetableDesigner.Backend.Services.Authentication.DTO.Events/TimetableDesigner.Backend.Services.Authentication.DTO.Events.csproj" />
|
||||
<Project Path="TimetableDesigner.Backend.Services.Authentication.DTO.WebAPI/TimetableDesigner.Backend.Services.Authentication.DTO.WebAPI.csproj" />
|
||||
<Project Path="TimetableDesigner.Backend.Services.Authentication/TimetableDesigner.Backend.Services.Authentication.csproj" />
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
using MediatR;
|
||||
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register;
|
||||
|
||||
public record RegisterCommand(
|
||||
string Email,
|
||||
string Password
|
||||
) : IRequest<RegisterResult>;
|
||||
@@ -1,20 +0,0 @@
|
||||
using TimetableDesigner.Backend.Services.Authentication.Database.Model;
|
||||
using TimetableDesigner.Backend.Services.Authentication.DTO.Events;
|
||||
using TimetableDesigner.Backend.Services.Authentication.DTO.WebAPI;
|
||||
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register;
|
||||
|
||||
public static class RegisterMappers
|
||||
{
|
||||
public static RegisterCommand ToCommand(this RegisterRequest request) =>
|
||||
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);
|
||||
|
||||
public static RegisterEvent ToEvent(this Account account) =>
|
||||
new RegisterEvent(account.Id, account.Email);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register;
|
||||
|
||||
public record RegisterResult(
|
||||
long Id,
|
||||
string Email
|
||||
);
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.Application.Helpers;
|
||||
|
||||
public record PasswordHashData(
|
||||
byte[] Hash,
|
||||
string Salt
|
||||
);
|
||||
@@ -6,7 +6,7 @@ using TimetableDesigner.Backend.Events.Extensions.AspNetCore.OpenApi;
|
||||
using TimetableDesigner.Backend.Events.Providers.RabbitMQ;
|
||||
using TimetableDesigner.Backend.Services.Authentication.API;
|
||||
using TimetableDesigner.Backend.Services.Authentication.API.Validators;
|
||||
using TimetableDesigner.Backend.Services.Authentication.Application.Helpers;
|
||||
using TimetableDesigner.Backend.Services.Authentication.Core.Helpers;
|
||||
using TimetableDesigner.Backend.Services.Authentication.Database;
|
||||
using TimetableDesigner.Backend.Services.Authentication.Events;
|
||||
using TimetableDesigner.Backend.Services.Authentication.WebAPI;
|
||||
|
||||
@@ -23,8 +23,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TimetableDesigner.Backend.Services.Authentication.Core\TimetableDesigner.Backend.Services.Authentication.Core.csproj" />
|
||||
<ProjectReference Include="..\TimetableDesigner.Backend.Services.Authentication.DTO.Events\TimetableDesigner.Backend.Services.Authentication.DTO.Events.csproj" />
|
||||
<ProjectReference Include="..\TimetableDesigner.Backend.Services.Authentication.DTO.WebAPI\TimetableDesigner.Backend.Services.Authentication.DTO.WebAPI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Workers\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,62 +2,64 @@
|
||||
using FluentValidation.Results;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using TimetableDesigner.Backend.Events;
|
||||
using TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register;
|
||||
using TimetableDesigner.Backend.Services.Authentication.Core.Commands.AuthPassword;
|
||||
using TimetableDesigner.Backend.Services.Authentication.Core.Commands.Register;
|
||||
using TimetableDesigner.Backend.Services.Authentication.DTO.WebAPI;
|
||||
using TimetableDesigner.Backend.Services.Authentication.WebAPI.Mappers;
|
||||
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.WebAPI;
|
||||
|
||||
public static class Endpoints
|
||||
{
|
||||
public static IEndpointRouteBuilder MapWebAPIEndpoints(this IEndpointRouteBuilder app)
|
||||
public static IEndpointRouteBuilder MapWebAPIEndpoints(this IEndpointRouteBuilder builder)
|
||||
{
|
||||
app.MapPost("/register", Register)
|
||||
builder.MapPost("/register", Register)
|
||||
.AllowAnonymous()
|
||||
.Produces<RegisterResponse>(201)
|
||||
.Produces<HttpValidationProblemDetails>(400)
|
||||
.Produces(500)
|
||||
.WithName("Register");
|
||||
app.MapPost("/authenticate_password", AuthenticatePassword)
|
||||
.WithName("AuthenticatePassword");
|
||||
app.MapPost("/authenticate_token", AuthenticateToken)
|
||||
.WithName("AuthenticateToken");
|
||||
app.MapPost("/test", Test)
|
||||
builder.MapPost("/auth/password", AuthPassword)
|
||||
.AllowAnonymous()
|
||||
.WithName("Test");
|
||||
.Produces<AuthResponse>()
|
||||
.Produces(401)
|
||||
.Produces(500)
|
||||
.WithName("AuthPassword");
|
||||
builder.MapPost("/auth/token", AuthToken)
|
||||
.WithName("AuthToken");
|
||||
|
||||
return app;
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static async Task<Results<Created<RegisterResponse>, ValidationProblem>> Register(IMediator mediator, IValidator<RegisterRequest> validator, RegisterRequest request, CancellationToken cancellationToken)
|
||||
private static async Task<Results<Created<RegisterResponse>, ValidationProblem, InternalServerError>> Register(IMediator mediator, IValidator<RegisterRequest> validator, RegisterRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
ValidationResult validationResult = await validator.ValidateAsync(request);
|
||||
ValidationResult validationResult = await validator.ValidateAsync(request, cancellationToken);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
return TypedResults.ValidationProblem(validationResult.ToDictionary());
|
||||
}
|
||||
|
||||
RegisterCommand registerCommand = request.ToCommand();
|
||||
RegisterResult result = await mediator.Send(registerCommand, cancellationToken);
|
||||
RegisterResponse response = result.ToResponse();
|
||||
RegisterResult result = await mediator.Send(request.ToCommand(), cancellationToken);
|
||||
|
||||
RegisterResponse response = result.ToResponse();
|
||||
return TypedResults.Created($"accounts/{response.Id}", response);
|
||||
}
|
||||
|
||||
public static async Task<Results<Ok<AuthenticateResponse>, ProblemHttpResult>> AuthenticatePassword(AuthenticatePasswordRequest request)
|
||||
private static async Task<Results<Ok<AuthResponse>, UnauthorizedHttpResult, InternalServerError>> AuthPassword(IMediator mediator, AuthPasswordRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return null;
|
||||
AuthPasswordResult result = await mediator.Send(request.ToCommand(), cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return TypedResults.Unauthorized();
|
||||
}
|
||||
|
||||
public static async Task<Results<Ok<AuthenticateResponse>, ProblemHttpResult>> AuthenticateToken(AuthenticateTokenRequest request)
|
||||
{
|
||||
return null;
|
||||
AuthResponse response = result.ToResponse();
|
||||
return TypedResults.Ok(response);
|
||||
}
|
||||
|
||||
public static async Task<Results<Ok, InternalServerError>> Test(IEventQueuePublisher publisher)
|
||||
public static async Task<Results<Ok<AuthResponse>, ProblemHttpResult>> AuthToken(AuthTokenRequest request)
|
||||
{
|
||||
await publisher.PublishAsync(new RegisterRequest("aaaa", "bbbb", "ccccc"));
|
||||
|
||||
return TypedResults.Ok();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using TimetableDesigner.Backend.Services.Authentication.Core.Commands.AuthPassword;
|
||||
using TimetableDesigner.Backend.Services.Authentication.DTO.WebAPI;
|
||||
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.WebAPI.Mappers;
|
||||
|
||||
public static class AuthPasswordMappers
|
||||
{
|
||||
public static AuthPasswordCommand ToCommand(this AuthPasswordRequest request) =>
|
||||
new AuthPasswordCommand(request.Email, request.Password, request.RememberMe);
|
||||
|
||||
public static AuthResponse ToResponse(this AuthPasswordResult result) =>
|
||||
new AuthResponse(result.AccessToken!, result.RefreshToken!);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using TimetableDesigner.Backend.Services.Authentication.Core.Commands.Register;
|
||||
using TimetableDesigner.Backend.Services.Authentication.DTO.WebAPI;
|
||||
|
||||
namespace TimetableDesigner.Backend.Services.Authentication.WebAPI.Mappers;
|
||||
|
||||
public static class RegisterMappers
|
||||
{
|
||||
public static RegisterCommand ToCommand(this RegisterRequest request) =>
|
||||
new RegisterCommand(request.Email, request.Password);
|
||||
|
||||
public static RegisterResponse ToResponse(this RegisterResult result) =>
|
||||
new RegisterResponse(result.Id, result.Email);
|
||||
}
|
||||
Reference in New Issue
Block a user