Basic password auth

This commit is contained in:
jaina heartles 2022-07-20 22:26:13 -07:00
parent 7b1e4030b0
commit e6217d543d
5 changed files with 40 additions and 8 deletions

View file

@ -135,6 +135,10 @@ pub const ApiServer = struct {
return self.db.getBy(models.User, .id, id, alloc);
}
pub fn getUserByHandle(self: *ApiServer, handle: []const u8, alloc: std.mem.Allocator) !?models.User {
return self.db.getBy(models.User, .handle, handle, alloc);
}
pub fn react(self: *ApiServer, note_id: Uuid, ctx: ApiContext) !void {
try self.db.insert(models.Reaction, .{ .note_id = note_id, .reactor_id = ctx.user_context.user.id });
}

View file

@ -77,10 +77,15 @@ pub fn createNote(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs)
}
pub fn createUser(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void {
const info = try utils.parseRequestBody(api.CreateInfo(models.User), ctx);
defer utils.freeRequestBody(info, ctx.alloc);
const credentials = try utils.parseRequestBody(struct { handle: []const u8, password: []const u8 }, ctx);
defer utils.freeRequestBody(credentials, ctx.alloc);
const user = srv.api.createUser(info) catch |err| switch (err) {
var hash_buf: [128]u8 = undefined;
const Hash = std.crypto.pwhash.scrypt;
const hashed_password = try Hash.strHash(credentials.password, .{ .allocator = ctx.alloc, .params = Hash.Params.interactive, .encoding = .phc }, &hash_buf);
const user = srv.api.createUser(.{ .handle = credentials.handle, .hashed_password = hashed_password }) catch |err| switch (err) {
error.HandleNotAvailable => return try utils.respondError(ctx, .bad_request, "handle not available"),
else => return err,
};
@ -135,6 +140,22 @@ pub fn authenticate(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs
try utils.respondJson(ctx, .ok, user_ctx.user_context);
}
pub fn login(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void {
const credentials = try utils.parseRequestBody(struct { username: []const u8, password: []const u8 }, ctx);
defer utils.freeRequestBody(credentials, ctx.alloc);
// TODO: This gives away the existence of a user through a timing side channel. is that acceptable?
const user = (try srv.api.getUserByHandle(credentials.username, ctx.alloc)) orelse return utils.respondError(ctx, .bad_request, "Invalid Login");
const Hash = std.crypto.pwhash.scrypt;
Hash.strVerify(user.hashed_password, credentials.password, .{ .allocator = ctx.alloc }) catch |err| switch (err) {
error.PasswordVerificationFailed => return utils.respondError(ctx, .bad_request, "Invalid Login"),
else => return err,
};
try utils.respondJson(ctx, .ok, .{ .id = user.id });
}
pub fn healthcheck(_: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void {
try utils.respondJson(ctx, .ok, .{ .status = "ok" });
}

View file

@ -104,7 +104,9 @@ pub const Database = struct {
\\CREATE TABLE IF NOT EXISTS
\\user(
\\ id TEXT PRIMARY KEY,
\\ handle TEXT NOT NULL
\\ handle TEXT NOT NULL,
\\
\\ hashed_password TEXT NOT NULL
\\) STRICT;
,
\\CREATE TABLE IF NOT EXISTS

View file

@ -17,13 +17,15 @@ const router = Router{
Route.new(.GET, "/healthcheck", c.healthcheck),
Route.new(.GET, "/auth/authenticate", c.authenticate),
Route.new(.POST, "/auth/login", c.login),
Route.new(.GET, "/notes/:id", c.getNote),
Route.new(.POST, "/notes", c.createNote),
Route.new(.GET, "/notes/:id", c.getNote),
Route.new(.GET, "/notes/:id/reacts", c.listReacts),
Route.new(.POST, "/notes/:id/reacts", c.react),
Route.new(.GET, "/users/:id", c.getUser),
Route.new(.POST, "/users", c.createUser),
Route.new(.GET, "/users/:id", c.getUser),
},
};
@ -43,8 +45,9 @@ pub const RequestServer = struct {
defer srv.shutdown();
while (true) {
var buf: [1 << 20]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf);
const buf = try self.alloc.alloc(u8, 1 << 28); // 4mb
defer self.alloc.free(buf);
var fba = std.heap.FixedBufferAllocator.init(buf);
const alloc = fba.allocator();
var ctx = try srv.accept(alloc);

View file

@ -15,6 +15,8 @@ pub const Note = struct {
pub const User = struct {
id: Uuid,
handle: []const u8,
hashed_password: []const u8, // encoded in PHC format, with salt
};
pub const Reaction = struct {