Harden auth validation against null request fields

This commit is contained in:
2026-02-08 21:48:07 +01:00
parent acffbc199d
commit d2ab8a676f
5 changed files with 36 additions and 10 deletions

View File

@@ -23,7 +23,7 @@ public static class AuthEndpoints
{
if (!AuthValidator.TryValidateRegistration(request, out var validated, out var registrationError))
{
authAttemptMonitor.RecordFailure(ctx, "auth-register", request.Username.Trim(), "validation-failed");
authAttemptMonitor.RecordFailure(ctx, "auth-register", NormalizeActor(request.Username), "validation-failed");
return EndpointHelpers.BadRequestError(registrationError);
}
@@ -31,7 +31,7 @@ public static class AuthEndpoints
if (exists)
return EndpointHelpers.ConflictError("Username already taken.");
var (hash, salt) = PasswordHasher.HashPassword(request.Password);
var (hash, salt) = PasswordHasher.HashPassword(validated.Password);
var expectedAdminKey = config["ADMIN_PASSWORD"];
var wantsAdmin = !string.IsNullOrWhiteSpace(validated.AdminKey);
if (wantsAdmin)
@@ -99,12 +99,12 @@ public static class AuthEndpoints
{
if (!AuthValidator.TryValidateLogin(request, out _, out var normalizedUsername, out var loginError))
{
authAttemptMonitor.RecordFailure(ctx, "auth-login", request.Username.Trim(), "validation-failed");
authAttemptMonitor.RecordFailure(ctx, "auth-login", NormalizeActor(request.Username), "validation-failed");
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))
if (player == null || !PasswordHasher.Verify(request.Password ?? string.Empty, player.PasswordHash, player.PasswordSalt))
{
authAttemptMonitor.RecordFailure(ctx, "auth-login", normalizedUsername, "invalid-credentials");
return EndpointHelpers.UnauthorizedError("Invalid username or password.");
@@ -135,4 +135,6 @@ public static class AuthEndpoints
return Results.NoContent();
});
}
private static string NormalizeActor(string? username) => string.IsNullOrWhiteSpace(username) ? "(missing)" : username.Trim();
}

View File

@@ -12,7 +12,7 @@ internal static class AuthValidator
public static bool TryValidateRegistration(RegisterRequest request, out ValidatedRegistration validated, out string error)
{
var username = (request.Username).Trim();
var username = (request.Username ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(username) || username.Length > MaxUsernameLength)
{
validated = default;
@@ -61,14 +61,14 @@ internal static class AuthValidator
}
var adminKey = EndpointHelpers.TrimTo(request.AdminKey, MaxAdminKeyLength);
validated = new ValidatedRegistration(username, username.ToLowerInvariant(), displayName, adminKey);
validated = new ValidatedRegistration(username, username.ToLowerInvariant(), password, displayName, adminKey);
error = string.Empty;
return true;
}
public static bool TryValidateLogin(LoginRequest request, out string username, out string normalizedUsername, out string error)
{
username = (request.Username).Trim();
username = (request.Username ?? string.Empty).Trim();
normalizedUsername = string.Empty;
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(request.Password))
@@ -94,5 +94,5 @@ internal static class AuthValidator
return true;
}
public readonly record struct ValidatedRegistration(string Username, string NormalizedUsername, string DisplayName, string? AdminKey);
public readonly record struct ValidatedRegistration(string Username, string NormalizedUsername, string Password, string DisplayName, string? AdminKey);
}