add nonce rate limit
All checks were successful
Server Build / publish (push) Successful in 37s
Voice Build / publish (push) Successful in 24s

This commit is contained in:
olcxja 2026-10-02 22:39:49 +02:00
commit 7b9e499ff3
2 changed files with 9 additions and 11 deletions

View file

@ -456,8 +456,9 @@ public class Requests
string id = Utils.GetValidIdOrZero(idQuery.ToString()); string id = Utils.GetValidIdOrZero(idQuery.ToString());
//rate limit pobran nonce (konto) //rate limit pobran nonce (IP+konto): jeden atakujący nie zjada budżetu konta innym IP
if (RateLimited(NonceRateLimits, id, NONCE_RATE_LIMIT)) string clientIp = context.Connection.RemoteIpAddress?.ToString() ?? "";
if (RateLimited(NonceRateLimits, $"{clientIp}_{id}", NONCE_RATE_LIMIT))
{ {
string pass = await Utils.GetPassword(id); string pass = await Utils.GetPassword(id);
await context.Response.WriteAsync(Encryption.Encryption.EncryptForClient("error:rate.limit", pass)); await context.Response.WriteAsync(Encryption.Encryption.EncryptForClient("error:rate.limit", pass));

View file

@ -37,7 +37,7 @@ public class Utils
public static ConcurrentDictionary<string, (int count, long resetAt)> AccountInitRateLimits = new(); public static ConcurrentDictionary<string, (int count, long resetAt)> AccountInitRateLimits = new();
public static int AUTH_RATE_LIMIT = 10; // failed password verifications per account+IP per minute public static int AUTH_RATE_LIMIT = 10; // failed password verifications per account+IP per minute
public static int NONCE_RATE_LIMIT = 240; // pobrania nonce per konto per minute public static int NONCE_RATE_LIMIT = 120; // pobrania nonce per IP+konto per minute
public static int KDFSALT_RATE_LIMIT = 60; // pobrania soli per IP per minute public static int KDFSALT_RATE_LIMIT = 60; // pobrania soli per IP per minute
public static int ACCOUNT_INIT_RATE_LIMIT = 4; // utworzone konta per IP per hour (check w init, zapis w finish) public static int ACCOUNT_INIT_RATE_LIMIT = 4; // utworzone konta per IP per hour (check w init, zapis w finish)
@ -45,11 +45,7 @@ public class Utils
public static bool RateLimited(ConcurrentDictionary<string, (int count, long resetAt)> store, string key, int limit, int windowSeconds = 60) public static bool RateLimited(ConcurrentDictionary<string, (int count, long resetAt)> store, string key, int limit, int windowSeconds = 60)
{ {
if (limit <= 0) return false; // <=0 wyłącza limit if (limit <= 0) return false; // <=0 wyłącza limit
long nowSec = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); return RecordRateLimitHit(store, key, windowSeconds) > limit;
var window = store.AddOrUpdate(key,
(1, nowSec + windowSeconds),
(_, old) => old.resetAt <= nowSec ? (1, nowSec + windowSeconds) : (old.count + 1, old.resetAt));
return window.count > limit;
} }
//Sprawdzenie bez inkrementacji: true jeśli klucz osiągnął już limit w bieżącym oknie //Sprawdzenie bez inkrementacji: true jeśli klucz osiągnął już limit w bieżącym oknie
@ -67,13 +63,14 @@ public class Utils
return entry.count >= limit; return entry.count >= limit;
} }
//Zapis jednego zdarzenia; okno startuje przy pierwszym zapisie //Zapis jednego zdarzenia; okno startuje przy pierwszym zapisie. Zwraca licznik okna
public static void RecordRateLimitHit(ConcurrentDictionary<string, (int count, long resetAt)> store, string key, int windowSeconds = 60) public static int RecordRateLimitHit(ConcurrentDictionary<string, (int count, long resetAt)> store, string key, int windowSeconds = 60)
{ {
long nowSec = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); long nowSec = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
store.AddOrUpdate(key, var window = store.AddOrUpdate(key,
(1, nowSec + windowSeconds), (1, nowSec + windowSeconds),
(_, old) => old.resetAt <= nowSec ? (1, nowSec + windowSeconds) : (old.count + 1, old.resetAt)); (_, old) => old.resetAt <= nowSec ? (1, nowSec + windowSeconds) : (old.count + 1, old.resetAt));
return window.count;
} }
// Failed password verifications only, keyed "{id}:{ip}". A success clears the // Failed password verifications only, keyed "{id}:{ip}". A success clears the