commentAuth.ts
Raw
1import { db } from "../db/index.ts";
2import type { SessionUser } from "../middleware/session.ts";
3
4/**
5 * Authorise an edit on an issue or patch comment.
6 *
7 * Returns a Response if the request must be denied, or null if it may proceed.
8 *
9 * Verifies, in one query, that:
10 * 1. the comment exists, and
11 * 2. its parent issue/patch belongs to the requested repo (prevents
12 * cross-repo bypasses where the URL repo and the comment's repo differ),
13 * 3. the user is the comment author or an admin,
14 * 4. the parent issue/patch is open (admins may edit on closed parents).
15 */
16export async function authorizeCommentEdit(
17 kind: "issue" | "patch",
18 commentId: number,
19 repoId: number,
20 user: SessionUser | null,
21): Promise<Response | null> {
22 if (!user) return new Response("Unauthorized", { status: 401 });
23
24 const row =
25 kind === "issue"
26 ? await db
27 .selectFrom("issue_comments")
28 .innerJoin("issues", "issues.id", "issue_comments.issue_id")
29 .select(["issue_comments.author_id", "issues.status"])
30 .where("issue_comments.id", "=", commentId)
31 .where("issues.repo_id", "=", repoId)
32 .executeTakeFirst()
33 : await db
34 .selectFrom("patch_comments")
35 .innerJoin("patches", "patches.id", "patch_comments.patch_id")
36 .select(["patch_comments.author_id", "patches.status"])
37 .where("patch_comments.id", "=", commentId)
38 .where("patches.repo_id", "=", repoId)
39 .executeTakeFirst();
40
41 if (!row) return new Response("Not found", { status: 404 });
42 if (row.author_id !== user.id && !user.isAdmin)
43 return new Response("Forbidden", { status: 403 });
44 if (row.status !== "open" && !user.isAdmin)
45 return new Response("Forbidden", { status: 403 });
46 return null;
47}
48