Formatting and loca

This commit is contained in:
2026-02-08 18:51:01 +01:00
parent 42e60d2a5a
commit 97f1b30b75
3 changed files with 17 additions and 42 deletions

View File

@@ -6,26 +6,10 @@ public sealed class AuthAttemptMonitor(ILogger<AuthAttemptMonitor> logger)
{ {
private static readonly TimeSpan FailureWindow = TimeSpan.FromMinutes(10); private static readonly TimeSpan FailureWindow = TimeSpan.FromMinutes(10);
private const int AlertThreshold = 5; private const int AlertThreshold = 5;
private static readonly Action<ILogger, string, string, string, string, int, Exception?> LogAuthFailure = private static readonly Action<ILogger, string, string, string, string, int, Exception?> LogAuthFailure = LoggerMessage.Define<string, string, string, string, int>(LogLevel.Warning, new EventId(2001, nameof(LogAuthFailure)), "Auth failure scope={Scope} actor={Actor} ip={Ip} reason={Reason} failuresInWindow={Count}");
LoggerMessage.Define<string, string, string, string, int>( private static readonly Action<ILogger, string, string, string, int, double, Exception?> LogSecurityAlert = LoggerMessage.Define<string, string, string, int, double>(LogLevel.Error, new EventId(2002, nameof(LogSecurityAlert)), "Security alert: repeated auth failures scope={Scope} actor={Actor} ip={Ip} failuresInWindow={Count} windowMinutes={WindowMinutes}");
LogLevel.Warning, private static readonly Action<ILogger, string, string, string, Exception?> LogRateLimited = LoggerMessage.Define<string, string, string>(LogLevel.Warning, new EventId(2003, nameof(LogRateLimited)), "Rate limit rejection path={Path} ip={Ip} userId={UserId}");
new EventId(2001, nameof(LogAuthFailure)), private static readonly Action<ILogger, string, string, DateTimeOffset, Exception?> LogSessionExpired = LoggerMessage.Define<string, string, DateTimeOffset>(LogLevel.Warning, new EventId(2004, nameof(LogSessionExpired)), "Session expired by absolute lifetime path={Path} ip={Ip} startedAt={StartedAt:o}");
"Auth failure scope={Scope} actor={Actor} ip={Ip} reason={Reason} failuresInWindow={Count}");
private static readonly Action<ILogger, string, string, string, int, double, Exception?> LogSecurityAlert =
LoggerMessage.Define<string, string, string, int, double>(
LogLevel.Error,
new EventId(2002, nameof(LogSecurityAlert)),
"Security alert: repeated auth failures scope={Scope} actor={Actor} ip={Ip} failuresInWindow={Count} windowMinutes={WindowMinutes}");
private static readonly Action<ILogger, string, string, string, Exception?> LogRateLimited =
LoggerMessage.Define<string, string, string>(
LogLevel.Warning,
new EventId(2003, nameof(LogRateLimited)),
"Rate limit rejection path={Path} ip={Ip} userId={UserId}");
private static readonly Action<ILogger, string, string, DateTimeOffset, Exception?> LogSessionExpired =
LoggerMessage.Define<string, string, DateTimeOffset>(
LogLevel.Warning,
new EventId(2004, nameof(LogSessionExpired)),
"Session expired by absolute lifetime path={Path} ip={Ip} startedAt={StartedAt:o}");
private readonly ConcurrentDictionary<string, AttemptState> _failures = new(StringComparer.Ordinal); private readonly ConcurrentDictionary<string, AttemptState> _failures = new(StringComparer.Ordinal);
@@ -34,12 +18,13 @@ public sealed class AuthAttemptMonitor(ILogger<AuthAttemptMonitor> logger)
var now = DateTimeOffset.UtcNow; var now = DateTimeOffset.UtcNow;
var key = BuildKey(context, scope, actor); var key = BuildKey(context, scope, actor);
var state = _failures.AddOrUpdate( var state = _failures.AddOrUpdate(key, _ => new AttemptState(1, now, now), (_, previous) => previous.LastFailureAt + FailureWindow < now
key,
_ => new AttemptState(1, now, now),
(_, previous) => previous.LastFailureAt + FailureWindow < now
? new AttemptState(1, now, now) ? new AttemptState(1, now, now)
: previous with { Count = previous.Count + 1, LastFailureAt = now }); : previous with
{
Count = previous.Count + 1,
LastFailureAt = now
});
LogAuthFailure(logger, scope, actor, GetRemoteIp(context), reason, state.Count, null); LogAuthFailure(logger, scope, actor, GetRemoteIp(context), reason, state.Count, null);
@@ -56,22 +41,12 @@ public sealed class AuthAttemptMonitor(ILogger<AuthAttemptMonitor> logger)
public void RecordRateLimited(HttpContext context) public void RecordRateLimited(HttpContext context)
{ {
LogRateLimited( LogRateLimited(logger, context.Request.Path.Value ?? string.Empty, GetRemoteIp(context), context.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "anonymous", null);
logger,
context.Request.Path.Value ?? string.Empty,
GetRemoteIp(context),
context.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "anonymous",
null);
} }
public void RecordSessionExpired(HttpContext context, DateTimeOffset startedAt) public void RecordSessionExpired(HttpContext context, DateTimeOffset startedAt)
{ {
LogSessionExpired( LogSessionExpired(logger, context.Request.Path.Value ?? string.Empty, GetRemoteIp(context), startedAt, null);
logger,
context.Request.Path.Value ?? string.Empty,
GetRemoteIp(context),
startedAt,
null);
} }
private static string BuildKey(HttpContext context, string scope, string actor) private static string BuildKey(HttpContext context, string scope, string actor)

View File

@@ -196,7 +196,7 @@ Warte kurz und versuche es dann erneut.
## Daten & Datenschutz ## Daten & Datenschutz
- Vorschläge, Stimmen und Phasenstatus werden in einer gemeinsamen **SQLite-Datenbank** gespeichert. - Vorschläge, Stimmen und Phasenstatus werden in einer gemeinsamen Datenbank gespeichert.
- Passwörter werden als gesalzene PBKDF2-SHA256-Hashes gespeichert (nicht im Klartext). - Passwörter werden als gesalzene Hashes gespeichert (nicht im Klartext).
- Beim Abmelden wird dein Authentifizierungs-Cookie gelöscht und die Eingaben in Login/Registrierung werden zurückgesetzt. - Beim Abmelden wird dein Authentifizierungs-Cookie gelöscht und die Eingaben in Login/Registrierung werden zurückgesetzt.
- Wenn ein Admin dein Spielerkonto löscht, werden auch deine Vorschläge und Stimmen entfernt. - Wenn ein Admin dein Spielerkonto löscht, werden auch deine Vorschläge und Stimmen entfernt.

View File

@@ -200,7 +200,7 @@ Wait briefly, then retry.
## Data & Privacy ## Data & Privacy
- Suggestions, votes, and phase states are stored in a shared **SQLite database**. - Suggestions, votes, and phase states are stored in a shared database.
- Passwords are stored as salted PBKDF2-SHA256 hashes (not plaintext). - Passwords are stored as salted hashes (not plaintext).
- Logging out clears your authentication cookie and resets login/register form inputs. - Logging out clears your authentication cookie and resets login/register form inputs.
- If an admin deletes your player account, your suggestions and votes are removed as well. - If an admin deletes your player account, your suggestions and votes are removed as well.