╭ changed ─────────╮ │src/event/User.zig│ ╰──────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────────╮ │15 haxy_moment: DB.HashMap(.read_write), │ │16 event_id: *const [evt.event_id_size]u8, │ │17 event_maybe: ?@This(), │ │18 + arena: *std.heap.ArenaAllocator, │ │19 ) !void { │ │20 const user_key = hash.hashInt(hash_kind, event_id);│ │21 │ ╰───────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │22 const event_id_to_user_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "event-id->user"));│ │23 const event_id_to_user = try DB.HashMap(.read_write).init(event_id_to_user_cursor); │ │24 + │ │25 + const name_to_user_id_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "name->user-id")); │ │26 + const name_to_user_id = try DB.HashMap(.read_write).init(name_to_user_id_cursor); │ │27 │ │28 if (event_maybe) |event| { │ │29 + // if this event_id already maps to a user with a different name, │ │30 + // drop the stale name->id entry first │ │31 + if (try event_id_to_user.getCursor(user_key)) |existing_cursor| { │ │32 + const existing_user = try DB.HashMap(.read_only).init(existing_cursor); │ │33 + const existing_event = try evt.read(@This(), DB, hash_kind, arena, existing_user); │ │34 + if (!std.mem.eql(u8, existing_event.name, event.name)) { │ │35 + _ = try name_to_user_id.remove(hash.hashInt(hash_kind, existing_event.name)); │ │36 + } │ │37 + } │ │38 + │ │39 const user_cursor = try event_id_to_user.putCursor(user_key); │ │40 const user = try DB.HashMap(.read_write).init(user_cursor); │ │41 try evt.upsert(@This(), DB, hash_kind, user, event); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │42 + │ │43 + try name_to_user_id.put(hash.hashInt(hash_kind, event.name), .{ .bytes = event_id }); │ │44 } else { │ │45 + // read the user's name so we can drop its name->id index entry │ │46 + if (try event_id_to_user.getCursor(user_key)) |existing_cursor| { │ │47 + const existing_user = try DB.HashMap(.read_only).init(existing_cursor); │ │48 + const existing_event = try evt.read(@This(), DB, hash_kind, arena, existing_user); │ │49 + _ = try name_to_user_id.remove(hash.hashInt(hash_kind, existing_event.name)); │ │50 + } │ │51 + │ │52 if (!try event_id_to_user.remove(user_key)) return error.EventNotFound; │ │53 │ │54 const user_id_to_repos_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "user-id->repos"));│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 68 .params = bcrypt.Params.owasp, │ │ 69 .encoding = .phc, │ │ 70 }, out, io); │ │ 71 +} │ │ 72 + │ │ 73 +pub const VerifyResult = union(enum) { │ │ 74 + success: [evt.event_id_size]u8, │ │ 75 + unknown_user, │ │ 76 + wrong_password, │ │ 77 +}; │ │ 78 + │ │ 79 +// look up a user by name (via the name->user-id index) and verify the │ │ 80 +// supplied password against the stored bcrypt hash. used by both the TTY │ │ 81 +// login submit and the server's /login route. │ │ 82 +pub fn verifyCredentials( │ │ 83 + comptime DB: type, │ │ 84 + comptime hash_kind: hash.HashKind, │ │ 85 + haxy_moment: DB.HashMap(.read_only), │ │ 86 + arena: *std.heap.ArenaAllocator, │ │ 87 + name: []const u8, │ │ 88 + password: []const u8, │ │ 89 +) !VerifyResult { │ │ 90 + const name_index_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, "name->user-id")) orelse return .unknown_user; │ │ 91 + const name_index = try DB.HashMap(.read_only).init(name_index_cursor); │ │ 92 + │ │ 93 + const user_id_cursor = try name_index.getCursor(hash.hashInt(hash_kind, name)) orelse return .unknown_user; │ │ 94 + var user_id: [evt.event_id_size]u8 = undefined; │ │ 95 + _ = try user_id_cursor.readBytes(&user_id); │ │ 96 + │ │ 97 + const user_key = hash.hashInt(hash_kind, &user_id); │ │ 98 + const event_id_to_user_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, "event-id->user")) orelse return .unknown_user;│ │ 99 + const event_id_to_user = try DB.HashMap(.read_only).init(event_id_to_user_cursor); │ │100 + │ │101 + const user_cursor = try event_id_to_user.getCursor(user_key) orelse return .unknown_user; │ │102 + const user_map = try DB.HashMap(.read_only).init(user_cursor); │ │103 + const user_event = try evt.read(@This(), DB, hash_kind, arena, user_map); │ │104 + │ │105 + bcrypt.strVerify(user_event.password_hash, password, .{ .silently_truncate_password = false }) catch { │ │106 + return .wrong_password; │ │107 + }; │ │108 + │ │109 + return .{ .success = user_id }; │ │110 } │ │111 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯