Upgrade dependencies, hide verbose logging.
This commit is contained in:
parent
a142a847da
commit
669fecf441
|
|
@ -1,17 +1,14 @@
|
||||||
{
|
{
|
||||||
"fileVersion": 1,
|
"fileVersion": 1,
|
||||||
"versions": {
|
"versions": {
|
||||||
"asdf": {
|
"asdf": "0.8.0",
|
||||||
"repository": "git+https://github.com/libmir/asdf.git",
|
|
||||||
"version": "8b3352146256f3d850caa41b25351893fb1b773e"
|
|
||||||
},
|
|
||||||
"d2sqlite3": "1.0.0",
|
"d2sqlite3": "1.0.0",
|
||||||
"dxml": "0.4.5",
|
"dxml": "0.4.5",
|
||||||
"handy-http-data": "1.3.0",
|
"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-primitives": "1.8.1",
|
||||||
"handy-http-starter": "1.6.0",
|
"handy-http-starter": "1.6.0",
|
||||||
"handy-http-transport": "1.10.0",
|
"handy-http-transport": "1.10.1",
|
||||||
"handy-http-websockets": "1.2.0",
|
"handy-http-websockets": "1.2.0",
|
||||||
"jwt4d": "0.0.2",
|
"jwt4d": "0.0.2",
|
||||||
"mir-algorithm": "3.22.4",
|
"mir-algorithm": "3.22.4",
|
||||||
|
|
|
||||||
|
|
@ -162,18 +162,24 @@ private class ExceptionHandlingFilter : HttpRequestFilter {
|
||||||
* over time.
|
* over time.
|
||||||
*/
|
*/
|
||||||
private class TokenBucketRateLimitingFilter : HttpRequestFilter {
|
private class TokenBucketRateLimitingFilter : HttpRequestFilter {
|
||||||
import std.datetime;
|
import std.datetime : Duration, Clock, SysTime, seconds;
|
||||||
import std.math : floor;
|
import std.math : floor;
|
||||||
import std.algorithm : min;
|
import std.algorithm : min;
|
||||||
|
import std.traits : Unqual;
|
||||||
|
|
||||||
private static struct TokenBucket {
|
private static struct TokenBucket {
|
||||||
|
/// The number of tokens in this bucket.
|
||||||
uint tokens;
|
uint tokens;
|
||||||
|
/// The timestamp at which a token was last removed from this bucket.
|
||||||
SysTime lastRequest;
|
SysTime lastRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenBucket[string] tokenBuckets;
|
/// The internal set of token buckets, mapped to client addresses.
|
||||||
const uint tokensPerSecond;
|
private shared TokenBucket[string] tokenBuckets;
|
||||||
const uint maxTokens;
|
/// 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(uint tokensPerSecond, uint maxTokens) {
|
||||||
this.tokensPerSecond = tokensPerSecond;
|
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) {
|
private string getClientId(in ServerHttpRequest req) {
|
||||||
import handy_http_transport.helpers : indexOf;
|
import handy_http_transport.helpers : indexOf;
|
||||||
string clientAddr = req.clientAddress.toString();
|
string clientAddr = req.clientAddress.toString();
|
||||||
|
|
@ -215,28 +227,51 @@ private class TokenBucketRateLimitingFilter : HttpRequestFilter {
|
||||||
return clientAddr;
|
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) {
|
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) {
|
if (bucket is null) {
|
||||||
tokenBuckets[clientAddr] = TokenBucket(maxTokens, now);
|
unsharedBuckets[clientAddr] = TokenBucket(maxTokens, now);
|
||||||
bucket = clientAddr in tokenBuckets;
|
bucket = clientAddr in unsharedBuckets;
|
||||||
}
|
}
|
||||||
return bucket;
|
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) {
|
private void incrementTokensForElapsedTime(TokenBucket* bucket, SysTime now) {
|
||||||
Duration timeSinceLastRequest = now - bucket.lastRequest;
|
Duration timeSinceLastRequest = now - bucket.lastRequest;
|
||||||
const tokensAddedSinceLastRequest = floor((timeSinceLastRequest.total!"msecs") * (tokensPerSecond / 1000.0));
|
const tokensAddedSinceLastRequest = floor((timeSinceLastRequest.total!"msecs") * (tokensPerSecond / 1000.0));
|
||||||
bucket.tokens = cast(uint) min(bucket.tokens + tokensAddedSinceLastRequest, maxTokens);
|
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() {
|
private void clearOldBuckets() {
|
||||||
const Duration fillTime = seconds(maxTokens * tokensPerSecond);
|
const Duration fillTime = seconds(maxTokens * tokensPerSecond);
|
||||||
foreach (id; tokenBuckets.byKey()) {
|
TokenBucket[string] unsharedBuckets = cast(TokenBucket[string]) tokenBuckets;
|
||||||
TokenBucket bucket = tokenBuckets[id];
|
foreach (id; unsharedBuckets.byKey()) {
|
||||||
|
TokenBucket bucket = unsharedBuckets[id];
|
||||||
const Duration timeSinceLastRequest = Clock.currTime() - bucket.lastRequest;
|
const Duration timeSinceLastRequest = Clock.currTime() - bucket.lastRequest;
|
||||||
if (timeSinceLastRequest > fillTime) {
|
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);
|
LoginData data = readJsonBodyAs!LoginData(request);
|
||||||
string token = generateTokenForLogin(data.username, data.password);
|
string token = generateTokenForLogin(data.username, data.password);
|
||||||
response.writeBodyString(token);
|
response.writeBodyString(token);
|
||||||
infoF!"Generated token for user: %s"(data.username);
|
debugF!"Generated token for user: %s"(data.username);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UsernameAvailabilityResponse {
|
struct UsernameAvailabilityResponse {
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,9 @@ string generateTokenForLogin(string username, string password) {
|
||||||
throw new HttpStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials.");
|
throw new HttpStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials.");
|
||||||
}
|
}
|
||||||
User user = optionalUser.value;
|
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);
|
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) {
|
if (verificationResult == VerifyPasswordResult.Failure) {
|
||||||
throw new HttpStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials.");
|
throw new HttpStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue