Use default admin user

This commit is contained in:
jaina heartles 2022-09-05 01:52:49 -07:00
parent e2381f1eaf
commit 1d4b0a6e77
7 changed files with 108 additions and 41 deletions

View file

@ -94,7 +94,6 @@ pub const RegistrationInfo = struct {
password: []const u8,
email: ?[]const u8,
invite_code: ?[]const u8,
community_host: ?[]const u8,
};
pub const LoginResult = struct {
@ -116,6 +115,11 @@ pub fn initThreadPrng(seed: u64) void {
prng = std.rand.DefaultPrng.init(seed +% std.Thread.getCurrentId());
}
// Returned slice points into buf
fn hashPassword(password: []const u8, alloc: std.mem.Allocator, buf: *[pw_hash_buf_size]u8) ![]const u8 {
return PwHash.strHash(password, .{ .allocator = alloc, .params = pw_hash_params, .encoding = pw_hash_encoding }, buf);
}
pub const ApiSource = struct {
db: db.Database,
internal_alloc: std.mem.Allocator,
@ -123,38 +127,59 @@ pub const ApiSource = struct {
pub const Conn = ApiConn(db.Database);
const root_username = "root";
const root_id = Uuid.nil;
const root_password_envvar = "CLUSTER_ROOT_PASSWORD";
pub fn init(alloc: std.mem.Allocator, cfg: Config) !ApiSource {
var my_db = try db.Database.init();
{
const row = try my_db.execRow2(
&.{Uuid},
"SELECT id FROM user WHERE username = ?",
.{"heartles"},
null,
);
std.log.debug("{s}", .{row.?[0]});
}
return ApiSource{
//.db = try db.Database.init(),
.db = my_db,
var self = ApiSource{
.db = try db.Database.init(),
.internal_alloc = alloc,
.config = cfg,
};
if ((try self.db.execRow2(&.{i64}, "SELECT 1 FROM user WHERE id = ? LIMIT 1;", .{root_id}, null)) == null) {
std.log.info("No cluster root user detected. Creating...", .{});
const root_password = std.os.getenv(root_password_envvar) orelse {
std.log.err(
"No root user created and no password specified. Please provide the password for the root user by the ${s} environment variable for initial startup.",
.{root_password_envvar},
);
@panic("No root password provided");
};
var buf: [pw_hash_buf_size]u8 = undefined;
const hash = try hashPassword(root_password, self.internal_alloc, &buf);
try self.db.insert2("user", .{
.id = root_id,
.username = root_username,
.community_id = null,
});
try self.db.insert2("local_user", .{
.user_id = root_id,
.hashed_password = hash,
.invite_id = null,
.email = null,
});
}
return self;
}
pub fn connectUnauthorized(self: *ApiSource, host: ?[]const u8, alloc: std.mem.Allocator) !Conn {
pub fn connectUnauthorized(self: *ApiSource, host: []const u8, alloc: std.mem.Allocator) !Conn {
const community_id = blk: {
if (host) |h| {
const result = try self.db.execRow2(&.{Uuid}, "SELECT id FROM community WHERE host = ?", .{h}, null);
if (result) |r| break :blk r[0];
}
const result = try self.db.execRow2(&.{Uuid}, "SELECT id FROM community WHERE host = ?", .{host}, null);
if (result) |r| break :blk r[0];
break :blk null;
};
if (community_id == null and !util.ciutf8.eql(self.config.cluster_host, host)) {
return error.NoCommunity;
}
return Conn{
.db = self.db,
.internal_alloc = self.internal_alloc,
@ -164,7 +189,7 @@ pub const ApiSource = struct {
};
}
pub fn connectToken(self: *ApiSource, host: ?[]const u8, token: []const u8, alloc: std.mem.Allocator) !Conn {
pub fn connectToken(self: *ApiSource, host: []const u8, token: []const u8, alloc: std.mem.Allocator) !Conn {
var conn = try self.connectUnauthorized(host, alloc);
errdefer conn.close();
@ -326,9 +351,10 @@ fn ApiConn(comptime DbConn: type) type {
return try self.db.getBy(models.Community, .host, host, self.arena.allocator());
}
pub fn register(self: *Self, info: RegistrationInfo) !models.Actor {
pub fn registerOld(self: *Self, info: RegistrationInfo) !models.Actor {
// Try to find invite id
const user_id = Uuid.randV4(prng.random());
// TODO: lock for transaction
// TODO: not community aware :(
if (try self.db.execRow2(&.{}, "SELECT 1 FROM user WHERE username = ?", .{info.username}, null) != null) {

View file

@ -64,20 +64,22 @@ pub const utils = struct {
}
pub fn getApiConn(srv: *RequestServer, ctx: *http.server.Context) !api.ApiSource.Conn {
return authorizeApiConn(srv, ctx) catch |err| switch (err) {
error.NoToken => srv.api.connectUnauthorized(ctx.request.headers.get("Host"), ctx.alloc),
const host = ctx.request.headers.get("Host") orelse return error.NoHost;
return authorizeApiConn(srv, ctx, host) catch |err| switch (err) {
error.NoToken => srv.api.connectUnauthorized(host, ctx.alloc),
error.InvalidToken => return error.InvalidToken,
else => @panic("TODO"), // doing this to resolve some sort of compiler analysis dependency issue
};
}
fn authorizeApiConn(srv: *RequestServer, ctx: *http.server.Context) !api.ApiSource.Conn {
fn authorizeApiConn(srv: *RequestServer, ctx: *http.server.Context, host: []const u8) !api.ApiSource.Conn {
const header = ctx.request.headers.get("authorization") orelse return error.NoToken;
if (header.len < ("bearer ").len) return error.InvalidToken;
const token = header[("bearer ").len..];
return try srv.api.connectToken(ctx.request.headers.get("Host"), token, ctx.alloc);
return try srv.api.connectToken(host, token, ctx.alloc);
}
};

View file

@ -0,0 +1,41 @@
const std = @import("std");
const root = @import("root");
const builtin = @import("builtin");
const http = @import("http");
const Uuid = @import("util").Uuid;
const RegistrationInfo = @import("../api.zig").RegistrationInfo;
const utils = @import("../controllers.zig").utils;
const RequestServer = root.RequestServer;
const RouteArgs = http.RouteArgs;
pub fn register(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void {
const info = try utils.parseRequestBody(RegistrationInfo, ctx);
defer utils.freeRequestBody(info, ctx.alloc);
var api = try utils.getApiConn(srv, ctx);
defer api.close();
const user = api.register(info) catch |err| switch (err) {
error.UsernameUnavailable => return utils.respondError(ctx, .bad_request, "Username Unavailable"),
else => return err,
};
try utils.respondJson(ctx, .created, user);
}
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);
var api = try utils.getApiConn(srv, ctx);
defer api.close();
const token = api.login(credentials.username, credentials.password) catch |err| switch (err) {
error.PasswordVerificationFailed => return utils.respondError(ctx, .bad_request, "Invalid Login"),
else => return err,
};
try utils.respondJson(ctx, .ok, token);
}

View file

@ -171,8 +171,7 @@ fn bind(stmt: sql.PreparedStmt, idx: u15, val: anytype) !void {
val.bindToSql(stmt, idx)
else
@compileError("unsupported type " ++ @typeName(T)),
else => unreachable,
//@compileError("unsupported type " ++ @typeName(T)),
else => @compileError("unsupported type " ++ @typeName(T)),
},
};
}

View file

@ -85,13 +85,13 @@ const migrations: []const Migration = &.{
\\ id TEXT NOT NULL PRIMARY KEY,
\\ username TEXT NOT NULL,
\\
\\ created_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP
\\) STRICT;
\\ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
\\);
\\
\\CREATE TABLE actor(
\\ user_id TEXT NOT NULL PRIMARY KEY REFERENCES user(id),
\\ public_id TEXT NOT NULL
\\) STRICT;
\\);
\\
\\CREATE TABLE local_user(
\\ user_id TEXT NOT NULL PRIMARY KEY REFERENCES user(id),
@ -99,8 +99,8 @@ const migrations: []const Migration = &.{
\\ email TEXT,
\\
\\ hashed_password TEXT NOT NULL,
\\ password_changed_at INTEGER NOT NULL
\\) STRICT;
\\ password_changed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
\\);
,
.down =
\\DROP TABLE local_user;

View file

@ -63,8 +63,6 @@ pub const User = struct {
id: Uuid,
username: []const u8,
community_id: ?Ref(Community),
created_at: DateTime,
};
pub const Actor = struct {
@ -79,7 +77,6 @@ pub const LocalUser = struct {
invite_id: ?Ref(Invite),
hashed_password: []const u8, // encoded in PHC format, with salt
password_changed_at: DateTime,
};
pub const Note = struct {

View file

@ -15,8 +15,10 @@ const router = Router{
.routes = &[_]Route{
Route.new(.GET, "/healthcheck", c.healthcheck),
//Route.new(.POST, "/users", c.users.create),
//Route.new(.POST, "/auth/register", &c.auth.register),
//Route.new(.POST, "/auth/login", &c.auth.login),
Route.new(.POST, "/login", &c.auth.login),
//Route.new(.POST, "/notes", &c.notes.create),
//Route.new(.GET, "/notes/:id", &c.notes.get),
@ -72,7 +74,7 @@ pub const RequestServer = struct {
};
pub const Config = struct {
host: []const u8,
cluster_host: []const u8,
};
fn loadConfig(alloc: std.mem.Allocator) !Config {