╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1069 │ │1070 var author_arena = std.heap.ArenaAllocator.init(allocator); │ │1071 defer author_arena.deinit(); │ │1047 - const author = ((try eventAuthor(io, allocator, &author_arena, request, host)) orelse return respondLoginRequired(request)).author; │ │1072 + const actor = (try authorizeWrite(io, allocator, &author_arena, request, host, parts.repo_base, .read)) orelse return; │ │1073 + const author = actor.author; │ │1074 + // an attachment changes the event it hangs off │ │1075 + const request_repo = (try requestRepoSource(io, allocator, host, parts.repo_base)) orelse return respondAttachmentParentNotFound(request);│ │1076 + defer request_repo.deinit(allocator); │ │1077 + const source = request_repo.source; │ │1078 + if (!try source.canModify(io, allocator, actor, parts.kind, &parts.id_bytes)) return respondForbidden(request); │ │1079 │ │1080 const name = (try attachmentName(allocator, request)) orelse return respondUploadError(request, .bad_request, "attachment needs a name"); │ │1081 defer allocator.free(name); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1090 var body_buf: [4096]u8 = undefined; │ │1091 const blob: evt.Blob = .{ .name = name, .size = size, .reader = try request.readerExpectContinue(&body_buf) }; │ │1092 │ │1062 - const request_repo = (try requestRepoSource(io, allocator, host, parts.repo_base)) orelse return respondAttachmentParentNotFound(request);│ │1063 - defer request_repo.deinit(allocator); │ │1064 - const source = request_repo.source; │ │1093 switch (source.repo_kind) { │ │1094 inline else => |repo_kind| { │ │1095 var any_repo = try rp.AnyRepo(repo_kind, .{}).open(io, allocator, source.localInitOpts()); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1178 ) !void { │ │1179 var author_arena = std.heap.ArenaAllocator.init(allocator); │ │1180 defer author_arena.deinit(); │ │1153 - const author = ((try eventAuthor(io, allocator, &author_arena, request, host)) orelse return respondLoginRequired(request)).author; │ │1181 + const actor = (try authorizeWrite(io, allocator, &author_arena, request, host, parts.repo_base, .read)) orelse return; │ │1182 + const author = actor.author; │ │1183 + // edits only reach here for a comment │ │1184 + const comment_id = parts.comment_id orelse unreachable; │ │1185 + { │ │1186 + const request_repo = (try requestRepoSource(io, allocator, host, parts.repo_base)) orelse return respondRemoveNotFound(request);│ │1187 + defer request_repo.deinit(allocator); │ │1188 + if (!try request_repo.source.canModify(io, allocator, actor, .comment, &comment_id)) return respondForbidden(request); │ │1189 + } │ │1190 │ │1191 const form_body = try readFormBody(request, allocator); │ │1192 defer allocator.free(form_body); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1283 │ │1284 var author_arena = std.heap.ArenaAllocator.init(allocator); │ │1285 defer author_arena.deinit(); │ │1250 - const request_author = (try eventAuthor(io, allocator, &author_arena, request, host)) orelse return respondLoginRequired(request);│ │1251 - const author = request_author.author; │ │1286 + const actor = (try authorizeWrite(io, allocator, &author_arena, request, host, parts.repo_base, .read)) orelse return; │ │1287 + const author = actor.author; │ │1288 │ │1289 const request_repo = (try requestRepoSource(io, allocator, host, parts.repo_base)) orelse return respondRemoveNotFound(request); │ │1290 defer request_repo.deinit(allocator); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1292 │ │1293 if (parts.kind == .patch) switch (host) { │ │1294 .server => |server| { │ │1259 - const user_id = request_author.user_id orelse return respondLoginRequired(request); │ │1295 + const user_id = actor.user_id orelse return respondLoginRequired(request); │ │1296 │ │1297 var admin_repo = try rp.Repo(.xit, evt.admin_repo_opts).open(io, allocator, .{ .path = server.admin_repo_path });│ │1298 defer admin_repo.deinit(io, allocator); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1320 .local => {}, │ │1321 }; │ │1322 │ │1323 + // a draft is its author's alone, which its removal above checks │ │1324 + if (!try source.canModify(io, allocator, actor, parts.kind, &parts.id)) return respondForbidden(request);│ │1325 + │ │1326 switch (source.repo_kind) { │ │1327 inline else => |repo_kind| { │ │1328 var any_repo = try rp.AnyRepo(repo_kind, .{}).open(io, allocator, source.localInitOpts()); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1400 │ │1401 var author_arena = std.heap.ArenaAllocator.init(allocator); │ │1402 defer author_arena.deinit(); │ │1364 - const author = ((try eventAuthor(io, allocator, &author_arena, request, host)) orelse return respondLoginRequired(request)).author; │ │1403 + const actor = (try authorizeWrite(io, allocator, &author_arena, request, host, parts.repo_base, .read)) orelse return; │ │1404 + const author = actor.author; │ │1405 + { │ │1406 + const request_repo = (try requestRepoSource(io, allocator, host, parts.repo_base)) orelse return respondRemoveNotFound(request); │ │1407 + defer request_repo.deinit(allocator); │ │1408 + if (!try request_repo.source.canModify(io, allocator, actor, parts.thread_kind, &parts.thread_id)) return respondForbidden(request); │ │1409 + } │ │1410 │ │1411 (switch (parts.thread_kind) { │ │1412 .issue => updateThread(evt.Issue, io, allocator, host, parts.repo_base, &parts.thread_id, .{ .status = if (open) .open else .closed }, author),│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1443 ) !void { │ │1444 var author_arena = std.heap.ArenaAllocator.init(allocator); │ │1445 defer author_arena.deinit(); │ │1401 - const request_author = (try eventAuthor(io, allocator, &author_arena, request, host)) orelse return respondLoginRequired(request);│ │1402 - const author = request_author.author; │ │1446 + const actor = (try authorizeWrite(io, allocator, &author_arena, request, host, parts.repo_base, .read)) orelse return; │ │1447 + const author = actor.author; │ │1448 │ │1449 const body = try readFormBody(request, allocator); │ │1450 defer allocator.free(body); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1482 return; │ │1483 } │ │1484 │ │1440 - const draft_user_id = if (parts.thread_kind == .patch) request_author.user_id else null; │ │1485 + const request_repo = (try requestRepoSource(io, allocator, host, parts.repo_base)) orelse return respondRemoveNotFound(request);│ │1486 + defer request_repo.deinit(allocator); │ │1487 + │ │1488 + const draft_user_id = if (parts.thread_kind == .patch) actor.user_id else null; │ │1489 if (draft_user_id) |user_id| { │ │1490 const server = switch (host) { │ │1491 .server => |server| server, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1492 .local => unreachable, │ │1493 }; │ │1446 - const request_repo = (try requestRepoSource(io, allocator, host, parts.repo_base)) orelse return respondRemoveNotFound(request);│ │1447 - defer request_repo.deinit(allocator); │ │1494 if (!evt.Patch.branchValid(target_branch) or !try request_repo.source.hasBranch(io, allocator, target_branch)) { │ │1495 const form_location = try std.fmt.allocPrint(allocator, "{s}/edit", .{base}); │ │1496 defer allocator.free(form_location); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯