feat: harden server pull deployments and git hygiene

This commit is contained in:
NuklearRabbit
2026-07-28 08:27:09 +02:00
parent d4d77c827a
commit 56efd1a00c
33 changed files with 2390 additions and 633 deletions
+38
View File
@@ -226,6 +226,44 @@ class GiteaService {
).data;
}
async listDeployKeys(owner, repo) {
const result = await this.request(
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/keys?limit=100`,
);
return Array.isArray(result.data) ? result.data : [];
}
async ensureReadOnlyDeployKey({ owner, repo, title, publicKey }) {
const key = String(publicKey || "").trim();
if (!/^ssh-(ed25519|rsa)\s+[A-Za-z0-9+/=]+(?:\s+.*)?$/.test(key))
throw new Error("The server did not return a valid SSH public key.");
const keys = await this.listDeployKeys(owner, repo);
const keyMaterial = key.split(/\s+/).slice(0, 2).join(" ");
const existing = keys.find((item) =>
String(item?.key || "").trim().split(/\s+/).slice(0, 2).join(" ") === keyMaterial,
);
if (existing) {
if (existing.read_only !== true) {
const error = new Error("The matching Gitea deploy key has write access. Revoke it before ForgeFlow configures a read-only server key.");
error.code = "DEPLOY_KEY_NOT_READ_ONLY";
throw error;
}
return { ...existing, created: false };
}
const result = await this.request(
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/keys`,
{
method: "POST",
body: {
title: String(title || "ForgeFlow server deploy key").trim().slice(0, 255),
key,
read_only: true,
},
},
);
return { ...result.data, created: true };
}
async listPullRequests({ owner, repo, state = "open", limit = 30 } = {}) {
const query = new URLSearchParams({
state,