teacher-tools/api/source/api_modules/auth.d

75 lines
2.1 KiB
D

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, ref Database db) {
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 .. $]));
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, ref Database db) {
Optional!User optUser = getUser(ctx, db);
if (optUser.isNull) {
throw new HttpStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials.");
}
return optUser.value;
}
void loginEndpoint(ref HttpRequestContext ctx) {
Database db = getDb();
Optional!User optUser = getUser(ctx, db);
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
));
}