Issue tokens on login
This commit is contained in:
parent
3e91d66565
commit
7fc83b6e56
4 changed files with 72 additions and 8 deletions
|
@ -12,6 +12,9 @@ const pw_hash_params = PwHash.Params.interactive;
|
||||||
const pw_hash_encoding = .phc;
|
const pw_hash_encoding = .phc;
|
||||||
const pw_hash_buf_size = 128;
|
const pw_hash_buf_size = 128;
|
||||||
|
|
||||||
|
const token_len = 20;
|
||||||
|
const token_str_len = std.base64.standard.count(token_len);
|
||||||
|
|
||||||
// Frees an api struct and its fields allocated from alloc
|
// Frees an api struct and its fields allocated from alloc
|
||||||
pub fn free(alloc: std.mem.Allocator, val: anytype) void {
|
pub fn free(alloc: std.mem.Allocator, val: anytype) void {
|
||||||
switch (@typeInfo(@TypeOf(val))) {
|
switch (@typeInfo(@TypeOf(val))) {
|
||||||
|
@ -165,7 +168,12 @@ pub const ApiServer = struct {
|
||||||
return actor;
|
return actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn login(self: *ApiServer, username: []const u8, password: []const u8, alloc: std.mem.Allocator) !?models.Actor {
|
const LoginResult = struct {
|
||||||
|
user_id: Uuid,
|
||||||
|
token: [token_len]u8,
|
||||||
|
issued_at: DateTime,
|
||||||
|
};
|
||||||
|
pub fn login(self: *ApiServer, username: []const u8, password: []const u8, alloc: std.mem.Allocator) !LoginResult {
|
||||||
// TODO: This gives away the existence of a user through a timing side channel. is that acceptable?
|
// TODO: This gives away the existence of a user through a timing side channel. is that acceptable?
|
||||||
const user_info = (try self.db.getBy(models.LocalUser, .username, username, alloc)) orelse return error.InvalidLogin;
|
const user_info = (try self.db.getBy(models.LocalUser, .username, username, alloc)) orelse return error.InvalidLogin;
|
||||||
defer free(alloc, user_info);
|
defer free(alloc, user_info);
|
||||||
|
@ -176,8 +184,39 @@ pub const ApiServer = struct {
|
||||||
else => return err,
|
else => return err,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: return token instead
|
const token = try self.createToken(user_info);
|
||||||
return (try self.db.getBy(models.Actor, .id, user_info.actor_id.?, alloc)) orelse unreachable;
|
|
||||||
|
return LoginResult{
|
||||||
|
.user_id = user_info.id,
|
||||||
|
.token = token.value,
|
||||||
|
.issued_at = token.info.issued_at,
|
||||||
|
};
|
||||||
|
//return (try self.db.getBy(models.Actor, .id, user_info.actor_id.?, alloc)) orelse unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TokenResult = struct {
|
||||||
|
info: models.Token,
|
||||||
|
value: [token_len]u8,
|
||||||
|
};
|
||||||
|
fn createToken(self: *ApiServer, user: models.LocalUser) !TokenResult {
|
||||||
|
var token: [token_len]u8 = undefined;
|
||||||
|
std.crypto.random.bytes(&token);
|
||||||
|
|
||||||
|
var hash: [models.Token.hash_len]u8 = undefined;
|
||||||
|
models.Token.HashFn.hash(&token, &hash, .{});
|
||||||
|
|
||||||
|
const db_token = models.Token{
|
||||||
|
.id = Uuid.randV4(self.prng.random()),
|
||||||
|
.hash = .{ .data = hash },
|
||||||
|
.user_id = user.id,
|
||||||
|
.issued_at = DateTime.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
try self.db.insert(models.Token, db_token);
|
||||||
|
return TokenResult{
|
||||||
|
.info = db_token,
|
||||||
|
.value = token,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getNote(self: *ApiServer, id: Uuid, alloc: std.mem.Allocator) !?models.Note {
|
pub fn getNote(self: *ApiServer, id: Uuid, alloc: std.mem.Allocator) !?models.Note {
|
||||||
|
|
|
@ -34,11 +34,10 @@ pub fn login(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void
|
||||||
const credentials = try utils.parseRequestBody(struct { username: []const u8, password: []const u8 }, ctx);
|
const credentials = try utils.parseRequestBody(struct { username: []const u8, password: []const u8 }, ctx);
|
||||||
defer utils.freeRequestBody(credentials, ctx.alloc);
|
defer utils.freeRequestBody(credentials, ctx.alloc);
|
||||||
|
|
||||||
const actor = srv.api.login(credentials.username, credentials.password, ctx.alloc) catch |err| switch (err) {
|
const token = srv.api.login(credentials.username, credentials.password, ctx.alloc) catch |err| switch (err) {
|
||||||
error.PasswordVerificationFailed => return utils.respondError(ctx, .bad_request, "Invalid Login"),
|
error.PasswordVerificationFailed => return utils.respondError(ctx, .bad_request, "Invalid Login"),
|
||||||
else => return err,
|
else => return err,
|
||||||
};
|
};
|
||||||
defer api.free(ctx.alloc, actor);
|
|
||||||
|
|
||||||
try utils.respondJson(ctx, .ok, actor);
|
try utils.respondJson(ctx, .ok, token);
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ fn tableName(comptime T: type) String {
|
||||||
models.Actor => "actor",
|
models.Actor => "actor",
|
||||||
models.Reaction => "reaction",
|
models.Reaction => "reaction",
|
||||||
models.LocalUser => "local_user",
|
models.LocalUser => "local_user",
|
||||||
|
models.Token => "token",
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -175,7 +176,7 @@ pub const Database = struct {
|
||||||
\\ id TEXT NOT NULL,
|
\\ id TEXT NOT NULL,
|
||||||
\\
|
\\
|
||||||
\\ handle TEXT NOT NULL,
|
\\ handle TEXT NOT NULL,
|
||||||
\\ created_at INTEGER NOT NULL,
|
\\ created_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
\\
|
\\
|
||||||
\\ PRIMARY KEY(id)
|
\\ PRIMARY KEY(id)
|
||||||
\\) STRICT;
|
\\) STRICT;
|
||||||
|
@ -189,7 +190,7 @@ pub const Database = struct {
|
||||||
\\ email TEXT,
|
\\ email TEXT,
|
||||||
\\ hashed_password TEXT NOT NULL,
|
\\ hashed_password TEXT NOT NULL,
|
||||||
\\
|
\\
|
||||||
\\ created_at INTEGER NOT NULL,
|
\\ created_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
\\ password_changed_at INTEGER NOT NULL,
|
\\ password_changed_at INTEGER NOT NULL,
|
||||||
\\
|
\\
|
||||||
\\ UNIQUE(actor_id),
|
\\ UNIQUE(actor_id),
|
||||||
|
@ -218,11 +219,24 @@ pub const Database = struct {
|
||||||
\\ reactor_id TEXT NOT NULL,
|
\\ reactor_id TEXT NOT NULL,
|
||||||
\\ note_id TEXT NOT NULL,
|
\\ note_id TEXT NOT NULL,
|
||||||
\\
|
\\
|
||||||
|
\\ created_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
\\
|
||||||
\\ FOREIGN KEY(reactor_id) REFERENCES actor(id),
|
\\ FOREIGN KEY(reactor_id) REFERENCES actor(id),
|
||||||
\\ FOREIGN KEY(note_id) REFERENCES note(id),
|
\\ FOREIGN KEY(note_id) REFERENCES note(id),
|
||||||
\\
|
\\
|
||||||
\\ PRIMARY KEY(id)
|
\\ PRIMARY KEY(id)
|
||||||
\\) STRICT;
|
\\) STRICT;
|
||||||
|
,
|
||||||
|
\\CREATE TABLE IF NOT EXISTS
|
||||||
|
\\token(
|
||||||
|
\\ id TEXT NOT NULL,
|
||||||
|
\\
|
||||||
|
\\ hash BLOB UNIQUE NOT NULL,
|
||||||
|
\\ user_id TEXT NOT NULL REFERENCES local_user(id),
|
||||||
|
\\ issued_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
\\
|
||||||
|
\\ PRIMARY KEY(id)
|
||||||
|
\\) STRICT;
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init() !Database {
|
pub fn init() !Database {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const util = @import("util");
|
const util = @import("util");
|
||||||
|
|
||||||
|
const ByteArray = @import("./db.zig").ByteArray;
|
||||||
const Uuid = util.Uuid;
|
const Uuid = util.Uuid;
|
||||||
const DateTime = util.DateTime;
|
const DateTime = util.DateTime;
|
||||||
|
|
||||||
|
@ -44,3 +45,14 @@ pub const Reaction = struct {
|
||||||
|
|
||||||
created_at: DateTime,
|
created_at: DateTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Token = struct {
|
||||||
|
pub const HashFn = std.crypto.hash.sha2.Sha256;
|
||||||
|
pub const hash_len = HashFn.digest_length;
|
||||||
|
|
||||||
|
id: Uuid,
|
||||||
|
|
||||||
|
hash: ByteArray(hash_len),
|
||||||
|
user_id: Ref(LocalUser),
|
||||||
|
issued_at: DateTime,
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue