Files
GameList/Endpoints/AuthEndpoints.cs

96 lines
3.6 KiB
C#

using GameList.Contracts;
using GameList.Data;
using GameList.Domain;
using GameList.Infrastructure;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace GameList.Endpoints;
public static class AuthEndpoints
{
public static void MapAuthEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api/auth");
group.MapPost("/register", async ([FromBody] RegisterRequest request, HttpContext ctx, AppDbContext db, IConfiguration config) =>
{
if (!AuthValidator.TryValidateRegistration(request, out var validated, out var registrationError))
return EndpointHelpers.BadRequestError(registrationError);
var exists = await db.Players.AnyAsync(p => p.NormalizedUsername == validated.NormalizedUsername);
if (exists)
return EndpointHelpers.ConflictError("Username already taken.");
var (hash, salt) = PasswordHasher.HashPassword(request.Password);
var expectedAdminKey = config["ADMIN_PASSWORD"];
var wantsAdmin = !string.IsNullOrWhiteSpace(validated.AdminKey);
if (wantsAdmin)
{
if (string.IsNullOrWhiteSpace(expectedAdminKey) || validated.AdminKey != expectedAdminKey)
return EndpointHelpers.BadRequestError("Invalid admin key.");
}
var isAdmin = wantsAdmin;
var player = new Player
{
Id = Guid.NewGuid(),
Username = validated.Username,
NormalizedUsername = validated.NormalizedUsername,
PasswordHash = hash,
PasswordSalt = salt,
DisplayName = validated.DisplayName,
IsAdmin = isAdmin,
CreatedAt = DateTimeOffset.UtcNow,
LastLoginAt = DateTimeOffset.UtcNow
};
db.Players.Add(player);
await db.SaveChangesAsync();
await PlayerIdentityExtensions.SignInPlayerAsync(ctx, player);
return Results.Ok(new AuthSessionResponse(
player.Id,
player.Username,
player.DisplayName,
player.IsAdmin
));
});
group.MapPost("/login", async ([FromBody] LoginRequest request, HttpContext ctx, AppDbContext db) =>
{
if (!AuthValidator.TryValidateLogin(request, out _, out var normalizedUsername, out var loginError))
return EndpointHelpers.BadRequestError(loginError);
var player = await db.Players.FirstOrDefaultAsync(p => p.NormalizedUsername == normalizedUsername);
if (player == null || !PasswordHasher.Verify(request.Password, player.PasswordHash, player.PasswordSalt))
return EndpointHelpers.UnauthorizedError("Invalid username or password.");
if (string.IsNullOrWhiteSpace(player.DisplayName))
{
player.DisplayName = EndpointHelpers.TrimTo(player.Username, AuthValidator.MaxDisplayNameLength);
}
player.LastLoginAt = DateTimeOffset.UtcNow;
await db.SaveChangesAsync();
await PlayerIdentityExtensions.SignInPlayerAsync(ctx, player);
return Results.Ok(new AuthSessionResponse(
player.Id,
player.Username,
player.DisplayName,
player.IsAdmin
));
});
group.MapPost("/logout", async (HttpContext ctx) =>
{
await PlayerIdentityExtensions.SignOutPlayerAsync(ctx);
return Results.NoContent();
});
}
}