chore: update src/pages/api/internal/hooks/pre-receive.ts
swadhinbiswas committed 8 months ago
1333f3c
1 parent 25d2b94
commit 1333f3c
1 file changed
+24 -8
Showing 1 changed file+24-8
▼src/pages/api/internal/hooks/pre-receive.ts
+24-8
| @@ -4,6 +4,7 @@ import { logger } from "@/lib/logger"; | ||
4 | 4 | import type { APIRoute } from "astro"; |
5 | 5 | import { eq, and } from "drizzle-orm"; |
6 | 6 | import micromatch from "micromatch"; |
7 | +import { resolveRepoPath } from "@/lib/git-storage"; | |
7 | 8 | |
8 | 9 | export const POST: APIRoute = async ({ request, url }) => { |
9 | 10 | const hookSecret = process.env.INTERNAL_HOOK_SECRET; |
| @@ -29,14 +30,13 @@ export const POST: APIRoute = async ({ request, url }) => { | ||
29 | 30 | |
30 | 31 | const { refname, newrev, oldrev, pusher } = body; |
31 | 32 | const userId = pusher; // The SSH server sends REMOTE_USER as userId |
32 | - // const isForcePush = ... // Hard to detect strictly from here without more Git context, but typically oldrev mismatch? | |
33 | - // Actually, force push is when newrev is not a descendant of oldrev. We can check that if we have git access. | |
34 | - // For now, let's focus on branch existence and roles. | |
33 | + const isSystemPush = userId === "system" || userId === "internal-bot"; | |
35 | 34 | |
36 | - const branchName = refname.replace("refs/heads/", ""); | |
37 | - if (!branchName) { | |
38 | - return new Response("OK", { status: 200 }); // Not a branch push (e.g. tag), or ignore for now | |
35 | + // We enforce branch protection only for branch refs. | |
36 | + if (!refname.startsWith("refs/heads/")) { | |
37 | + return new Response("OK", { status: 200 }); | |
39 | 38 | } |
39 | + const branchName = refname.slice("refs/heads/".length); | |
40 | 40 | |
41 | 41 | const db = getDatabase() as NodePgDatabase<typeof schema>; |
42 | 42 | |
| @@ -68,6 +68,14 @@ export const POST: APIRoute = async ({ request, url }) => { | ||
68 | 68 | if (!repo) { |
69 | 69 | return new Response("Repo not found", { status: 404 }); |
70 | 70 | } |
71 | + const localRepoPath = await resolveRepoPath(repo.diskPath); | |
72 | + const pusherUser = userId | |
73 | + ? await db.query.users.findFirst({ | |
74 | + where: eq(schema.users.id, userId), | |
75 | + columns: { id: true, isAdmin: true }, | |
76 | + }) | |
77 | + : null; | |
78 | + const isPrivilegedPush = isSystemPush || !!pusherUser?.isAdmin; | |
71 | 79 | |
72 | 80 | // Fetch protection rules |
73 | 81 | const rules = await db.query.branchProtection.findMany({ |
| @@ -94,6 +102,10 @@ export const POST: APIRoute = async ({ request, url }) => { | ||
94 | 102 | |
95 | 103 | // 1. Require PR |
96 | 104 | if (matchingRule.requiresPr) { |
105 | + if (isPrivilegedPush) { | |
106 | + return new Response("OK", { status: 200 }); | |
107 | + } | |
108 | + | |
97 | 109 | // If newrev allows force push (0000 -> sha is Create, Sha -> 0000 is Delete) |
98 | 110 | const isDelete = newrev === "0000000000000000000000000000000000000000"; |
99 | 111 | |
| @@ -123,6 +135,10 @@ export const POST: APIRoute = async ({ request, url }) => { | ||
123 | 135 | |
124 | 136 | // 2. Prevent Force Push |
125 | 137 | if (!matchingRule.allowForcePushes) { |
138 | + if (isPrivilegedPush) { | |
139 | + return new Response("OK", { status: 200 }); | |
140 | + } | |
141 | + | |
126 | 142 | // We need to check if it's a force push. |
127 | 143 | // git rev-list oldrev ^newrev (if output non-empty, it's non-fast-forward?) |
128 | 144 | // Actually simpler: `git merge-base --is-ancestor oldrev newrev` |
| @@ -135,7 +151,7 @@ export const POST: APIRoute = async ({ request, url }) => { | ||
135 | 151 | // This is expensive for an API request? Maybe. But necessary. |
136 | 152 | // We can use simple-git. |
137 | 153 | const { simpleGit } = await import("simple-git"); |
138 | - const git = simpleGit(repoPath); | |
154 | + const git = simpleGit(localRepoPath); | |
139 | 155 | try { |
140 | 156 | // merge-base --is-ancestor oldrev newrev |
141 | 157 | await git.raw(["merge-base", "--is-ancestor", oldrev, newrev]); |
| @@ -164,7 +180,7 @@ export const POST: APIRoute = async ({ request, url }) => { | ||
164 | 180 | let changedFiles: string[] = []; |
165 | 181 | try { |
166 | 182 | const { simpleGit } = await import("simple-git"); |
167 | - const git = simpleGit(repoPath); | |
183 | + const git = simpleGit(localRepoPath); | |
168 | 184 | |
169 | 185 | // Determine base revision for diff |
170 | 186 | let baseRev = oldrev; |
171 | 187 | |