Upgrade dependencies, hide verbose logging.
This commit is contained in:
parent
a142a847da
commit
669fecf441
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue