fediglam/src/main/api/auth.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;
}
};