╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ User.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
94 wrong_password, 95 }; 96 97 // look up a user by name (via the name->user-id index) and verify the 98 // supplied password against the stored bcrypt hash. used by both the TTY 99 // login submit and the server's /login route. 100 pub fn verifyCredentials( 101 comptime DB: type, 102 comptime hash_kind: hash.HashKind, 103 haxy_moment: DB.HashMap(.read_only), 104 arena: *std.heap.ArenaAllocator, 105 name: []const u8, 106 password: []const u8, 107 ) !VerifyResult { 108 const name_index_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, "name->user-id")) orelse return .unknown_user; 109 const name_index = try DB.HashMap(.read_only).init(name_index_cursor); 110 111 const user_id_cursor = try name_index.getCursor(hash.hashInt(hash_kind, name)) orelse return .unknown_user; 112 var user_id: [evt.event_id_size]u8 = undefined; 113 _ = try user_id_cursor.readBytes(&user_id); 114 115 const user_key = hash.hashInt(hash_kind, &user_id); 116 const event_id_to_user_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, "event-id->user")) orelse return .unknown_user; 117 const event_id_to_user = try DB.HashMap(.read_only).init(event_id_to_user_cursor); 118 119 const user_cursor = try event_id_to_user.getCursor(user_key) orelse return .unknown_user; 120 const user_map = try DB.HashMap(.read_only).init(user_cursor); 121 const user_event = try evt.read(@This(), DB, hash_kind, arena, user_map); 122 123 bcrypt.strVerify(user_event.password_hash, password, .{ .silently_truncate_password = false }) catch { 124 return .wrong_password; 125 }; 126 127 return .{ .success = user_id }; 128 } 129 130 // read a user by event id via the event-id->user index, or null if the id 131 // isn't a known user. field byte slices are allocated in `arena`. 132 pub fn readById( 133 comptime DB: type, 134 comptime hash_kind: hash.HashKind, 135 haxy_moment: DB.HashMap(.read_only), 136 arena: *std.heap.ArenaAllocator, 137 user_id: []const u8, 138 ) !?@This() { 139 const event_id_to_user_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, "event-id->user")) orelse return null; 140 const event_id_to_user = try DB.HashMap(.read_only).init(event_id_to_user_cursor); 141 const user_cursor = try event_id_to_user.getCursor(hash.hashInt(hash_kind, user_id)) orelse return null; 142 const user_map = try DB.HashMap(.read_only).init(user_cursor); 143 return try evt.read(@This(), DB, hash_kind, arena, user_map); 144 } 145 146 // flip a user's ANSI-art preference by re-emitting their User event with 147 // enable_ansi negated. a no-op for an unknown user. `repo` must be writable. 148 pub fn toggleAnsi( 149 comptime repo_opts: rp.RepoOpts(.xit), 150 io: std.Io, 151 allocator: std.mem.Allocator, 152 repo: *rp.Repo(.xit, repo_opts), 153 user_id: []const u8, 154 ) !void { 155 const DB = rp.Repo(.xit, repo_opts).DB; 156 var arena = std.heap.ArenaAllocator.init(allocator); 157 defer arena.deinit(); 158 159 const moment = try evt.currentMoment(repo_opts, repo); 160 const user = (try readById(DB, repo_opts.hash, moment, &arena, user_id)) orelse return; 161 162 var updated = user; 163 updated.enable_ansi = !user.enable_ansi; 164 try evt.commitAndConsume(repo_opts, io, allocator, repo, evt.events_ref, &[_]evt.EventWithId{.{ 165 .id = std.fmt.bytesToHex(user_id[0..evt.event_id_size].*, .lower), 166 .event = .{ .user = updated }, 167 }}); 168 } 169