Use default admin user
This commit is contained in:
parent
e2381f1eaf
commit
1d4b0a6e77
7 changed files with 108 additions and 41 deletions
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
41
src/main/controllers/users.zig
Normal file
41
src/main/controllers/users.zig
Normal 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);
|
||||
}
|
|
@ -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)),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue