╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2255 .id = std.fmt.bytesToHex(user_id.*, .lower), │ │2256 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), │ │2257 .author = .{ .name = event.name, .email = email }, │ │2258 + .event = .{ .user = event }, │ │2259 + }}); │ │2260 +} │ │2261 + │ │2262 +pub const KeyAction = enum { add, remove }; │ │2263 + │ │2264 +// add an authorized_keys line to the user's ssh keys, or remove the lines │ │2265 +// holding the same key. removing a key they don't hold writes nothing. │ │2266 +pub fn changeUserKey( │ │2267 + io: std.Io, │ │2268 + allocator: std.mem.Allocator, │ │2269 + users_dir: []const u8, │ │2270 + admin_repo: *rp.Repo(.xit, admin_repo_opts), │ │2271 + user_id: *const [event_id_size]u8, │ │2272 + action: KeyAction, │ │2273 + key_text: []const u8, │ │2274 +) !void { │ │2275 + const key = std.mem.trim(u8, key_text, " \t\r"); │ │2276 + // the keys are stored one per line │ │2277 + if (std.mem.indexOfScalar(u8, key, '\n') != null) return error.InvalidKey; │ │2278 + const fingerprint = User.fingerprintOfKey(key) orelse return error.InvalidKey; │ │2279 + // the key types the ssh server verifies │ │2280 + const key_type = std.mem.sliceTo(key, ' '); │ │2281 + if (action == .add and !std.mem.eql(u8, key_type, "ssh-ed25519") and !std.mem.eql(u8, key_type, "ssh-rsa")) return error.UnsupportedKey; │ │2282 + │ │2283 + var arena = std.heap.ArenaAllocator.init(allocator); │ │2284 + defer arena.deinit(); │ │2285 + const moment = try currentMoment(admin_repo_opts, admin_repo); │ │2286 + const user = (try User.readById(AdminDB, admin_repo_opts.hash, moment, &arena, user_id)) orelse return error.NotFound; │ │2287 + if (user.removed) return error.NotFound; │ │2288 + │ │2289 + var keys: std.ArrayList(u8) = .empty; │ │2290 + var held = false; │ │2291 + var lines = std.mem.splitScalar(u8, user.event.ssh_keys, '\n'); │ │2292 + while (lines.next()) |line| { │ │2293 + const line_fingerprint = User.fingerprintOfKey(line) orelse continue; │ │2294 + if (std.mem.eql(u8, &line_fingerprint, &fingerprint)) { │ │2295 + held = true; │ │2296 + if (action == .remove) continue; │ │2297 + } │ │2298 + if (keys.items.len > 0) try keys.append(arena.allocator(), '\n'); │ │2299 + try keys.appendSlice(arena.allocator(), line); │ │2300 + } │ │2301 + switch (action) { │ │2302 + .add => { │ │2303 + if (held) return error.KeyAdded; │ │2304 + if (keys.items.len > 0) try keys.append(arena.allocator(), '\n'); │ │2305 + try keys.appendSlice(arena.allocator(), key); │ │2306 + }, │ │2307 + .remove => if (!held) return, │ │2308 + } │ │2309 + │ │2310 + var event = user.event; │ │2311 + event.ssh_keys = keys.items; │ │2312 + try consume(.{ .server = .{ .users_dir = users_dir } }, .admin, .xit, admin_repo_opts, io, allocator, admin_repo, events_ref, &[_]EventWithId{.{│ │2313 + .id = std.fmt.bytesToHex(user_id.*, .lower), │ │2314 + .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), │ │2315 + .author = .{ .name = event.name, .email = event.email }, │ │2316 .event = .{ .user = event }, │ │2317 }}); │ │2318 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯