PasswordHasher added

This commit is contained in:
2026-01-13 02:47:34 +01:00
Unverified
parent 2b3650de1a
commit 2b9ddef055
11 changed files with 66 additions and 25 deletions

View File

@@ -19,18 +19,19 @@ public static class Endpoints
return app; return app;
} }
public static async Task<Results<Created<RegisterResponse>, ProblemHttpResult>> Register(RegisterCommand command, IMediator mediator) public static async Task<Results<Created<RegisterResponse>, InternalServerError>> Register(RegisterRequest request, CancellationToken cancellationToken, IMediator mediator)
{ {
await mediator.Send(command); RegisterCommand registerCommand = request.ToCommand();
return Results.Created($"accounts/0", null); RegisterData data = await mediator.Send(registerCommand, cancellationToken);
return Results.Created<RegisterResponse>($"accounts/0", null);
} }
public static async Task<Results<Ok<AuthenticateResponse>, ProblemHttpResult>> AuthenticatePassword(AuthenticatePasswordRequest request) public static async Task<Results<Ok<AuthenticateResponse>, ProblemHttpResult>> AuthenticatePassword(AuthenticatePasswordRequest request, CancellationToken cancellationToken)
{ {
return null; return null;
} }
public static async Task<Results<Ok<AuthenticateResponse>, ProblemHttpResult>> AuthenticateToken(AuthenticateTokenRequest request) public static async Task<Results<Ok<AuthenticateResponse>, ProblemHttpResult>> AuthenticateToken(AuthenticateTokenRequest request, CancellationToken cancellationToken)
{ {
return null; return null;
} }

View File

@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore;
using TimetableDesigner.Backend.Services.Authentication.Database; using TimetableDesigner.Backend.Services.Authentication.Database;
using TimetableDesigner.Backend.Services.Authentication.DTO.API; using TimetableDesigner.Backend.Services.Authentication.DTO.API;
namespace TimetableDesigner.Backend.Services.Authentication.DTO.Validators; namespace TimetableDesigner.Backend.Services.Authentication.API.Validators;
public class RegisterRequestValidator : AbstractValidator<RegisterRequest> public class RegisterRequestValidator : AbstractValidator<RegisterRequest>
{ {

View File

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

View File

@@ -1,6 +1,6 @@
namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register; namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register;
public class RegisterDto public class RegisterData
{ {
} }

View File

@@ -1,19 +1,23 @@
using MediatR; using MediatR;
using TimetableDesigner.Backend.Services.Authentication.Application.Helpers;
using TimetableDesigner.Backend.Services.Authentication.Database; using TimetableDesigner.Backend.Services.Authentication.Database;
namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register; namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register;
public class RegisterHandler : IRequestHandler<RegisterCommand> public class RegisterHandler : IRequestHandler<RegisterCommand, RegisterData>
{ {
private readonly DatabaseContext _databaseContext; private readonly DatabaseContext _databaseContext;
private readonly IPasswordHasher _passwordHasher;
public RegisterHandler(DatabaseContext databaseContext) public RegisterHandler(DatabaseContext databaseContext, IPasswordHasher passwordHasher)
{ {
_databaseContext = databaseContext; _databaseContext = databaseContext;
_passwordHasher = passwordHasher;
} }
public async Task Handle(RegisterCommand command, CancellationToken cancellationToken) public async Task<RegisterData> Handle(RegisterCommand command, CancellationToken cancellationToken)
{ {
PasswordHashData hash = _passwordHasher.CreateHash(command.Password);
} }
} }

View File

@@ -1,7 +1,6 @@
using TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register; using TimetableDesigner.Backend.Services.Authentication.DTO.API;
using TimetableDesigner.Backend.Services.Authentication.DTO.API;
namespace TimetableDesigner.Backend.Services.Authentication.DTO.Mappers; namespace TimetableDesigner.Backend.Services.Authentication.Application.Commands.Register;
public static class RegisterMappers public static class RegisterMappers
{ {

View File

@@ -3,5 +3,5 @@
public interface IPasswordHasher public interface IPasswordHasher
{ {
PasswordHashData CreateHash(string password); PasswordHashData CreateHash(string password);
byte[] ComputeHash(string password, string salt); bool ValidatePassword(PasswordHashData hash, string password);
} }

View File

@@ -2,6 +2,5 @@
public record PasswordHashData( public record PasswordHashData(
byte[] Hash, byte[] Hash,
string LeftSalt, string Salt
string RightSalt
); );

View File

@@ -1,6 +1,39 @@
namespace TimetableDesigner.Backend.Services.Authentication.Application.Helpers; using System.Security.Cryptography;
using System.Text;
using Konscious.Security.Cryptography;
public class PasswordHasher namespace TimetableDesigner.Backend.Services.Authentication.Application.Helpers;
public class PasswordHasher : IPasswordHasher
{ {
private const string RandomPasswordCharacters = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890!@#$%^&*()-_=+[{]};:'\"\\|,<.>/?";
public PasswordHashData CreateHash(string password)
{
string salt = RandomNumberGenerator.GetString(RandomPasswordCharacters, 20);
byte[] hash = ComputeHash(password, salt);
PasswordHashData data = new PasswordHashData(hash, salt);
return data;
}
public bool ValidatePassword(PasswordHashData hash, string password)
{
byte[] actualHash = hash.Hash;
byte[] checkedHash = ComputeHash(password, hash.Salt);
bool checkResult = checkedHash.SequenceEqual(actualHash);
return checkResult;
}
protected byte[] ComputeHash(string password, string salt)
{
Argon2id hashFunction = new Argon2id(Encoding.UTF8.GetBytes(password))
{
Salt = Encoding.UTF8.GetBytes(salt),
DegreeOfParallelism = 8,
MemorySize = 65536,
Iterations = 4
};
byte[] hash = hashFunction.GetBytes(32);
return hash;
}
} }

View File

@@ -3,8 +3,9 @@ using FluentValidation;
using Microsoft.AspNetCore.Identity.Data; using Microsoft.AspNetCore.Identity.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using TimetableDesigner.Backend.Services.Authentication.API; 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.Database; using TimetableDesigner.Backend.Services.Authentication.Database;
using TimetableDesigner.Backend.Services.Authentication.DTO.Validators;
namespace TimetableDesigner.Backend.Services.Authentication; namespace TimetableDesigner.Backend.Services.Authentication;
@@ -16,6 +17,7 @@ public static class Program
.SetupOpenApi() .SetupOpenApi()
.SetupSecurity() .SetupSecurity()
.SetupDatabase() .SetupDatabase()
.SetupHelpers()
.SetupValidation() .SetupValidation()
.SetupMediatR() .SetupMediatR()
.Build(); .Build();
@@ -47,6 +49,12 @@ public static class Program
return builder; return builder;
} }
private static WebApplicationBuilder SetupHelpers(this WebApplicationBuilder builder)
{
builder.Services.AddSingleton<IPasswordHasher, PasswordHasher>();
return builder;
}
private static WebApplicationBuilder SetupValidation(this WebApplicationBuilder builder) private static WebApplicationBuilder SetupValidation(this WebApplicationBuilder builder)
{ {
builder.Services.AddScoped<IValidator<TimetableDesigner.Backend.Services.Authentication.DTO.API.RegisterRequest>, RegisterRequestValidator>(); builder.Services.AddScoped<IValidator<TimetableDesigner.Backend.Services.Authentication.DTO.API.RegisterRequest>, RegisterRequestValidator>();

View File

@@ -10,6 +10,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentValidation" Version="12.1.1" /> <PackageReference Include="FluentValidation" Version="12.1.1" />
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" />
<PackageReference Include="MediatR" Version="14.0.0" /> <PackageReference Include="MediatR" Version="14.0.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.9" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.9"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.9">
@@ -19,10 +20,6 @@
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="DTO\Mappers\" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\TimetableDesigner.Backend.Services.Authentication.DTO\TimetableDesigner.Backend.Services.Authentication.DTO.API\TimetableDesigner.Backend.Services.Authentication.DTO.API.csproj" /> <ProjectReference Include="..\TimetableDesigner.Backend.Services.Authentication.DTO\TimetableDesigner.Backend.Services.Authentication.DTO.API\TimetableDesigner.Backend.Services.Authentication.DTO.API.csproj" />
</ItemGroup> </ItemGroup>