╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │155 const time_cursor = try times.getCursor(hash.hashInt(user_repo_opts.hash, repo_id)) orelse return null; │ │156 return try time_cursor.readUint(); │ │157 } │ │158 + │ │159 +// the account each ssh key last logged into with a password, which the key │ │160 +// logs into from then on. this instance's own state, so it lives outside the │ │161 +// events, in the admin repo. │ │162 +const login_key_to_user_id_key = "haxy/login-key->user-id"; │ │163 + │ │164 +pub const LoginKeyChange = enum { trust, forget }; │ │165 + │ │166 +// let the key with `fingerprint` log `user_id` in, or stop it doing so │ │167 +pub fn setLoginKey( │ │168 + io: std.Io, │ │169 + admin_repo: *rp.Repo(.xit, admin_repo_opts), │ │170 + fingerprint: *const [User.fingerprint_len]u8, │ │171 + user_id: *const [event_id_size]u8, │ │172 + change: LoginKeyChange, │ │173 +) !void { │ │174 + const hash_kind = admin_repo_opts.hash; │ │175 + const Ctx = struct { │ │176 + fingerprint: *const [User.fingerprint_len]u8, │ │177 + user_id: *const [event_id_size]u8, │ │178 + change: LoginKeyChange, │ │179 │ │180 + pub fn run(ctx: @This(), cursor: *AdminDB.Cursor(.read_write)) !void { │ │181 + const moment = try AdminDB.HashMap(.read_write).init(cursor.*); │ │182 + const keys = try AdminDB.HashMap(.read_write).init(try moment.putCursor(hash.hashInt(hash_kind, login_key_to_user_id_key))); │ │183 + const key = hash.hashInt(hash_kind, ctx.fingerprint); │ │184 + switch (ctx.change) { │ │185 + .trust => try keys.put(key, .{ .bytes = ctx.user_id }), │ │186 + // a later login to another account keeps its trust │ │187 + .forget => { │ │188 + const trusted_cursor = try keys.getCursor(key) orelse return error.CancelTransaction; │ │189 + var trusted_id: [event_id_size]u8 = undefined; │ │190 + _ = try trusted_cursor.readBytes(&trusted_id); │ │191 + if (!std.mem.eql(u8, &trusted_id, ctx.user_id)) return error.CancelTransaction; │ │192 + _ = try keys.remove(key); │ │193 + }, │ │194 + } │ │195 + } │ │196 + }; │ │197 + │ │198 + try admin_repo.core.db_file.lock(io, .exclusive); │ │199 + defer admin_repo.core.db_file.unlock(io); │ │200 + │ │201 + const history = try AdminDB.ArrayList(.read_write).init(admin_repo.core.db.rootCursor()); │ │202 + history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ .fingerprint = fingerprint, .user_id = user_id, .change = change }) catch |err| switch (err) {│ │203 + error.CancelTransaction => {}, │ │204 + else => |e| return e, │ │205 + }; │ │206 +} │ │207 + │ │208 +// the account the key with `fingerprint` logs into, from the admin repo's moment │ │209 +pub fn readLoginKeyUser(moment: AdminDB.HashMap(.read_only), fingerprint: *const [User.fingerprint_len]u8) !?[event_id_size]u8 { │ │210 + const keys_cursor = try moment.getCursor(hash.hashInt(admin_repo_opts.hash, login_key_to_user_id_key)) orelse return null; │ │211 + const keys = try AdminDB.HashMap(.read_only).init(keys_cursor); │ │212 + const user_id_cursor = try keys.getCursor(hash.hashInt(admin_repo_opts.hash, fingerprint)) orelse return null; │ │213 + var user_id: [event_id_size]u8 = undefined; │ │214 + _ = try user_id_cursor.readBytes(&user_id); │ │215 + return user_id; │ │216 +} │ │217 + │ │218 // open a user's repo, or null when it's missing │ │219 pub fn openUserRepo(io: std.Io, allocator: std.mem.Allocator, users_dir: []const u8, user_id: *const [event_id_size]u8) !?rp.Repo(.xit, user_repo_opts) { │ │220 const path = try userRepoPath(allocator, users_dir, user_id); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯