Upgrade dependencies, hide verbose logging.

This commit is contained in:
andrewlalis 2026-01-15 07:54:47 -05:00
parent a142a847da
commit 669fecf441
4 changed files with 51 additions and 19 deletions

View File

@ -1,17 +1,14 @@
{
"fileVersion": 1,
"versions": {
"asdf": {
"repository": "git+https://github.com/libmir/asdf.git",
"version": "8b3352146256f3d850caa41b25351893fb1b773e"
},
"asdf": "0.8.0",
"d2sqlite3": "1.0.0",
"dxml": "0.4.5",
"handy-http-data": "1.3.0",
"handy-http-handlers": "1.2.0",
"handy-http-handlers": "1.3.0",
"handy-http-primitives": "1.8.1",
"handy-http-starter": "1.6.0",
"handy-http-transport": "1.10.0",
"handy-http-transport": "1.10.1",
"handy-http-websockets": "1.2.0",
"jwt4d": "0.0.2",
"mir-algorithm": "3.22.4",

View File

@ -162,18 +162,24 @@ private class ExceptionHandlingFilter : HttpRequestFilter {
* over time.
*/
private class TokenBucketRateLimitingFilter : HttpRequestFilter {
import std.datetime;
import std.datetime : Duration, Clock, SysTime, seconds;
import std.math : floor;
import std.algorithm : min;
import std.traits : Unqual;
private static struct TokenBucket {
/// The number of tokens in this bucket.
uint tokens;
/// The timestamp at which a token was last removed from this bucket.
SysTime lastRequest;
}
TokenBucket[string] tokenBuckets;
const uint tokensPerSecond;
const uint maxTokens;
/// The internal set of token buckets, mapped to client addresses.
private shared TokenBucket[string] tokenBuckets;
/// The number of tokens that are added to each bucket, per second.
private const uint tokensPerSecond;
/// The maximum number of tokens that each bucket can hold.
private const uint maxTokens;
this(uint tokensPerSecond, uint maxTokens) {
this.tokensPerSecond = tokensPerSecond;
@ -205,6 +211,12 @@ private class TokenBucketRateLimitingFilter : HttpRequestFilter {
}
}
/**
* Gets a string identifying the client who made the request.
* Params:
* req = The request.
* Returns: A string uniquely identifying the client.
*/
private string getClientId(in ServerHttpRequest req) {
import handy_http_transport.helpers : indexOf;
string clientAddr = req.clientAddress.toString();
@ -215,28 +227,51 @@ private class TokenBucketRateLimitingFilter : HttpRequestFilter {
return clientAddr;
}
/**
* Gets a pointer to a token bucket for a given client, creating it first
* if it doesn't exist yet.
* Params:
* clientAddr = The client's address.
* now = The current timestamp, used to initialize new buckets.
* Returns: A pointer to the token bucket in this filter's internal mapping.
*/
private TokenBucket* getOrCreateBucket(string clientAddr, SysTime now) {
TokenBucket* bucket = clientAddr in tokenBuckets;
TokenBucket[string] unsharedBuckets = cast(TokenBucket[string]) tokenBuckets;
TokenBucket* bucket = clientAddr in unsharedBuckets;
if (bucket is null) {
tokenBuckets[clientAddr] = TokenBucket(maxTokens, now);
bucket = clientAddr in tokenBuckets;
unsharedBuckets[clientAddr] = TokenBucket(maxTokens, now);
bucket = clientAddr in unsharedBuckets;
}
return bucket;
}
/**
* Increments the number of tokens in a client's bucket based on how much
* time has elapsed since the last time they made a request. This filter
* has a defined `tokensPerSecond`, as well as a `maxTokens`, so we know
* how long it takes for a bucket to fill up.
* Params:
* bucket = The bucket to fill with tokens.
* now = The current timestamp.
*/
private void incrementTokensForElapsedTime(TokenBucket* bucket, SysTime now) {
Duration timeSinceLastRequest = now - bucket.lastRequest;
const tokensAddedSinceLastRequest = floor((timeSinceLastRequest.total!"msecs") * (tokensPerSecond / 1000.0));
bucket.tokens = cast(uint) min(bucket.tokens + tokensAddedSinceLastRequest, maxTokens);
}
/**
* Removes any token buckets that haven't had a request in a while, and
* thus are full. This keeps our memory footprint smaller.
*/
private void clearOldBuckets() {
const Duration fillTime = seconds(maxTokens * tokensPerSecond);
foreach (id; tokenBuckets.byKey()) {
TokenBucket bucket = tokenBuckets[id];
TokenBucket[string] unsharedBuckets = cast(TokenBucket[string]) tokenBuckets;
foreach (id; unsharedBuckets.byKey()) {
TokenBucket bucket = unsharedBuckets[id];
const Duration timeSinceLastRequest = Clock.currTime() - bucket.lastRequest;
if (timeSinceLastRequest > fillTime) {
tokenBuckets.remove(id);
unsharedBuckets.remove(id);
}
}
}

View File

@ -19,7 +19,7 @@ void postLogin(ref ServerHttpRequest request, ref ServerHttpResponse response) {
LoginData data = readJsonBodyAs!LoginData(request);
string token = generateTokenForLogin(data.username, data.password);
response.writeBodyString(token);
infoF!"Generated token for user: %s"(data.username);
debugF!"Generated token for user: %s"(data.username);
}
struct UsernameAvailabilityResponse {

View File

@ -49,9 +49,9 @@ string generateTokenForLogin(string username, string password) {
throw new HttpStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials.");
}
User user = optionalUser.value;
infoF!"Verifying password for login attempt for user %s."(user.username);
debugF!"Verifying password for login attempt for user %s."(user.username);
auto verificationResult = verifyPassword(password, HashedPassword(user.passwordHash), PASSWORD_HASH_PEPPER);
infoF!"Verification result for login: %s"(verificationResult);
debugF!"Verification result for login: %s"(verificationResult);
if (verificationResult == VerifyPasswordResult.Failure) {
throw new HttpStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials.");
}