105 lines
3.4 KiB
D
105 lines
3.4 KiB
D
/// API endpoints for authentication-related functions, like registration and login.
|
|
module auth.api;
|
|
|
|
import handy_httpd;
|
|
import handy_httpd.components.optional;
|
|
import slf4d;
|
|
|
|
import auth.model;
|
|
import auth.data;
|
|
import auth.service;
|
|
import auth.data_impl_fs;
|
|
import util.json;
|
|
|
|
/// The credentials provided by a user to login.
|
|
struct LoginCredentials {
|
|
string username;
|
|
string password;
|
|
}
|
|
|
|
/// A token response sent to the user if they've been authenticated.
|
|
struct TokenResponse {
|
|
const string token;
|
|
}
|
|
|
|
void postLogin(ref HttpRequestContext ctx) {
|
|
LoginCredentials loginCredentials = readJsonPayload!LoginCredentials(ctx);
|
|
if (!validateUsername(loginCredentials.username)) {
|
|
ctx.response.status = HttpStatus.UNAUTHORIZED;
|
|
ctx.response.writeBodyString("Username is not valid.");
|
|
return;
|
|
}
|
|
UserRepository userRepo = new FileSystemUserRepository();
|
|
Optional!User optionalUser = userRepo.findByUsername(loginCredentials.username);
|
|
if (optionalUser.isNull) {
|
|
ctx.response.status = HttpStatus.UNAUTHORIZED;
|
|
return;
|
|
}
|
|
import botan.passhash.bcrypt : checkBcrypt;
|
|
|
|
if (!checkBcrypt(loginCredentials.password, optionalUser.value.passwordHash)) {
|
|
ctx.response.status = HttpStatus.UNAUTHORIZED;
|
|
return;
|
|
}
|
|
string token = generateAccessToken(optionalUser.value);
|
|
writeJsonBody(ctx, TokenResponse(token));
|
|
}
|
|
|
|
struct UsernameAvailabilityResponse {
|
|
const bool available;
|
|
}
|
|
|
|
void getUsernameAvailability(ref HttpRequestContext ctx) {
|
|
Optional!string username = ctx.request.queryParams.getFirst("username");
|
|
if (username.isNull) {
|
|
ctx.response.status = HttpStatus.BAD_REQUEST;
|
|
ctx.response.writeBodyString("Missing username parameter.");
|
|
return;
|
|
}
|
|
UserRepository userRepo = new FileSystemUserRepository();
|
|
bool available = userRepo.findByUsername(username.value).isNull;
|
|
writeJsonBody(ctx, UsernameAvailabilityResponse(available));
|
|
}
|
|
|
|
struct RegistrationData {
|
|
string username;
|
|
string password;
|
|
}
|
|
|
|
void postRegister(ref HttpRequestContext ctx) {
|
|
RegistrationData registrationData = readJsonPayload!RegistrationData(ctx);
|
|
if (!validateUsername(registrationData.username)) {
|
|
ctx.response.status = HttpStatus.BAD_REQUEST;
|
|
ctx.response.writeBodyString("Invalid username.");
|
|
return;
|
|
}
|
|
if (!validatePassword(registrationData.password)) {
|
|
ctx.response.status = HttpStatus.BAD_REQUEST;
|
|
ctx.response.writeBodyString("Invalid password.");
|
|
return;
|
|
}
|
|
UserRepository userRepo = new FileSystemUserRepository();
|
|
if (!userRepo.findByUsername(registrationData.username).isNull) {
|
|
ctx.response.status = HttpStatus.BAD_REQUEST;
|
|
ctx.response.writeBodyString("Username is taken.");
|
|
return;
|
|
}
|
|
|
|
User user = createNewUser(userRepo, registrationData.username, registrationData.password);
|
|
infoF!"Created user: %s"(registrationData.username);
|
|
string token = generateAccessToken(user);
|
|
writeJsonBody(ctx, TokenResponse(token));
|
|
}
|
|
|
|
void getMyUser(ref HttpRequestContext ctx) {
|
|
AuthContext auth = getAuthContext(ctx);
|
|
ctx.response.writeBodyString(auth.user.username);
|
|
}
|
|
|
|
void deleteMyUser(ref HttpRequestContext ctx) {
|
|
AuthContext auth = getAuthContext(ctx);
|
|
UserRepository userRepo = new FileSystemUserRepository();
|
|
deleteUser(auth.user, userRepo);
|
|
infoF!"Deleted user: %s"(auth.user.username);
|
|
}
|