module api_modules.auth; import handy_httpd; import handy_httpd.components.optional; import slf4d; import d2sqlite3; import db; import data_utils; struct User { const ulong id; const string username; const string passwordHash; const ulong createdAt; const bool isLocked; const bool isAdmin; } private struct UserResponse { ulong id; string username; ulong createdAt; bool isLocked; bool isAdmin; } Optional!User getUser(ref HttpRequestContext ctx) { import std.base64; import std.string : startsWith; import std.digest.sha; import std.algorithm : countUntil; string headerStr = ctx.request.headers.getFirst("Authorization").orElse(""); if (headerStr.length == 0 || !startsWith(headerStr, "Basic ")) { return Optional!User.empty; } string encodedCredentials = headerStr[6..$]; string decoded = cast(string) Base64.decode(encodedCredentials); size_t idx = countUntil(decoded, ':'); string username = decoded[0..idx]; auto passwordHash = toHexString(sha256Of(decoded[idx+1 .. $])); Database db = getDb(); Optional!User optUser = findOne!(User)(db, "SELECT * FROM user WHERE username = ?", username); if (!optUser.isNull && optUser.value.passwordHash != passwordHash) { return Optional!User.empty; } return optUser; } User getUserOrThrow(ref HttpRequestContext ctx) { Optional!User optUser = getUser(ctx); if (optUser.isNull) { throw new HttpStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials."); } return optUser.value; } void loginEndpoint(ref HttpRequestContext ctx) { Optional!User optUser = getUser(ctx); if (optUser.isNull) { ctx.response.status = HttpStatus.UNAUTHORIZED; ctx.response.writeBodyString("Invalid credentials."); return; } infoF!"Login successful for user \"%s\"."(optUser.value.username); writeJsonBody(ctx, UserResponse( optUser.value.id, optUser.value.username, optUser.value.createdAt, optUser.value.isLocked, optUser.value.isAdmin )); }