156 lines
5.1 KiB
Zig
156 lines
5.1 KiB
Zig
const std = @import("std");
|
|
const util = @import("util");
|
|
|
|
const Uuid = util.Uuid;
|
|
const DateTime = util.DateTime;
|
|
|
|
pub const passwords = struct {
|
|
const PwHash = std.crypto.pwhash.scrypt;
|
|
const pw_hash_params = PwHash.Params.interactive;
|
|
const pw_hash_encoding = .phc;
|
|
const pw_hash_buf_size = 128;
|
|
|
|
const PwHashBuf = [pw_hash_buf_size]u8;
|
|
|
|
// Returned slice points into buf
|
|
fn hashPassword(password: []const u8, alloc: std.mem.Allocator, buf: *PwHashBuf) []const u8 {
|
|
return PwHash.strHash(password, .{ .allocator = alloc, .params = pw_hash_params, .encoding = pw_hash_encoding }, buf) catch unreachable;
|
|
}
|
|
|
|
pub const VerifyError = error{
|
|
InvalidLogin,
|
|
DbError,
|
|
};
|
|
pub fn verify(db: anytype, user_id: Uuid, password: []const u8, alloc: std.mem.Allocator) VerifyError!void {
|
|
// TODO: This could be done w/o the dynamically allocated hash buf
|
|
const hash = (db.queryRow(
|
|
&.{[]const u8},
|
|
"SELECT hashed_password FROM account_password WHERE user_id = $1 LIMIT 1",
|
|
.{user_id},
|
|
alloc,
|
|
) catch return error.DbError) orelse return error.InvalidLogin;
|
|
errdefer alloc.free(hash[0]);
|
|
|
|
PwHash.strVerify(hash[0], password, .{ .allocator = alloc }) catch unreachable;
|
|
}
|
|
|
|
pub const CreateError = error{DbError};
|
|
pub fn create(db: anytype, user_id: Uuid, password: []const u8, alloc: std.mem.Allocator) CreateError!void {
|
|
var buf: PwHashBuf = undefined;
|
|
const hash = PwHash.strHash(password, .{ .allocator = alloc, .params = pw_hash_params, .encoding = pw_hash_encoding }, &buf) catch unreachable;
|
|
|
|
db.insert("account_password", .{
|
|
.user_id = user_id,
|
|
.hashed_password = hash,
|
|
}) catch return error.DbError;
|
|
}
|
|
};
|
|
|
|
pub const tokens = struct {
|
|
const token_len = 20;
|
|
const token_str_len = std.base64.standard.Encoder.calcSize(token_len);
|
|
pub const Token = struct {
|
|
pub const Value = [token_str_len]u8;
|
|
pub const Info = struct {
|
|
user_id: Uuid,
|
|
issued_at: DateTime,
|
|
};
|
|
|
|
value: Value,
|
|
info: Info,
|
|
};
|
|
|
|
const TokenHash = std.crypto.hash.sha2.Sha256;
|
|
const TokenDigestBuf = [TokenHash.digest_length]u8;
|
|
|
|
const DbToken = struct {
|
|
hash: []const u8,
|
|
user_id: Uuid,
|
|
issued_at: DateTime,
|
|
};
|
|
|
|
pub const CreateError = error{DbError};
|
|
pub fn create(db: anytype, user_id: Uuid) CreateError!Token {
|
|
var token: [token_len]u8 = undefined;
|
|
std.crypto.random.bytes(&token);
|
|
|
|
var hash: TokenDigestBuf = undefined;
|
|
TokenHash.hash(&token, &hash, .{});
|
|
|
|
const issued_at = DateTime.now();
|
|
|
|
db.insert("token", DbToken{
|
|
.hash = &hash,
|
|
.user_id = user_id,
|
|
.issued_at = issued_at,
|
|
}) catch return error.DbError;
|
|
|
|
var token_enc: [token_str_len]u8 = undefined;
|
|
_ = std.base64.standard.Encoder.encode(&token_enc, &token);
|
|
|
|
return Token{ .value = token_enc, .info = .{
|
|
.user_id = user_id,
|
|
.issued_at = issued_at,
|
|
} };
|
|
}
|
|
|
|
fn lookupUserTokenFromHash(db: anytype, hash: []const u8, community_id: Uuid) !?Token.Info {
|
|
return if (try db.queryRow(
|
|
&.{ Uuid, DateTime },
|
|
\\SELECT user.id, token.issued_at
|
|
\\FROM token JOIN user ON token.user_id = user.id
|
|
\\WHERE user.community_id = $1 AND token.hash = $2
|
|
\\LIMIT 1
|
|
,
|
|
.{ community_id, hash },
|
|
null,
|
|
)) |result|
|
|
Token.Info{
|
|
.user_id = result[0],
|
|
.issued_at = result[1],
|
|
}
|
|
else
|
|
null;
|
|
}
|
|
|
|
fn lookupSystemTokenFromHash(db: anytype, hash: []const u8) !?Token.Info {
|
|
return if (try db.queryRow(
|
|
&.{ Uuid, DateTime },
|
|
\\SELECT user.id, token.issued_at
|
|
\\FROM token JOIN user ON token.user_id = user.id
|
|
\\WHERE user.community_id IS NULL AND token.hash = $1
|
|
\\LIMIT 1
|
|
,
|
|
.{hash},
|
|
null,
|
|
)) |result|
|
|
Token.Info{
|
|
.user_id = result[0],
|
|
.issued_at = result[1],
|
|
}
|
|
else
|
|
null;
|
|
}
|
|
|
|
pub const VerifyError = error{ InvalidToken, DbError };
|
|
pub fn verify(db: anytype, token: []const u8, community_id: ?Uuid) VerifyError!Token.Info {
|
|
const decoded_len = std.base64.standard.Decoder.calcSizeForSlice(token) catch return error.InvalidToken;
|
|
if (decoded_len != token_len) return error.InvalidToken;
|
|
|
|
var decoded: [token_len]u8 = undefined;
|
|
std.base64.standard.Decoder.decode(&decoded, token) catch return error.InvalidToken;
|
|
|
|
var hash: TokenDigestBuf = undefined;
|
|
TokenHash.hash(&decoded, &hash, .{});
|
|
|
|
const token_info = if (community_id) |id|
|
|
lookupUserTokenFromHash(db, &hash, id) catch return error.DbError
|
|
else
|
|
lookupSystemTokenFromHash(db, &hash) catch return error.DbError;
|
|
|
|
if (token_info) |info| return info;
|
|
|
|
return error.InvalidToken;
|
|
}
|
|
};
|