CI / pruefen (push) Canceled after 0s
Ein Push aktualisiert den Stand sofort, statt bis zum naechsten Durchlauf des Zeitgebers zu warten; ein geschlossenes Issue hakt seine Aufgabe ab. Der Zeitplan bleibt als Rueckfalllosung. Der Endpunkt ist der einzige ohne Anmeldung, der schreibt – entsprechend eng gefasst: - Ausgewiesen wird sich mit einer Signatur ueber den Rumpf. GitHub und Gitea rechnen HMAC-SHA256, GitLab schickt nur den Token im Klartext; dessen Verfahren ist schwaecher, mehr gibt die Plattform nicht her. Verglichen wird durchweg zeitkonstant. - Geprueft wird gegen den **rohen** Rumpf. Durch JSON.parse und JSON.stringify gedreht kaeme eine andere Zeichenfolge heraus und die Signatur passte nie. - Der Rumpf ist nie Quelle fuer Inhalte. Wer das Geheimnis kennt, kann eine Aufgabe auf- oder zumachen und einen Abgleich ausloesen – keine beliebigen Daten einschleusen. - Ratenbegrenzung je Repository, Deckel auf die Rumpfgroesse, und ein unbekanntes Repository antwortet wie ein nicht eingerichtetes: 404, damit die ID nicht bestaetigt wird. - Unbekannte Ereignisse werden angenommen und liegen gelassen. Ein Fehler brachte das Git-System zum Wiederholen und irgendwann zum Abschalten. Das Geheimnis liegt verschluesselt am Repository, wie die Zugangstoken, und ist genau einmal zu sehen. Damit es nicht doch nach draussen wandert, laedt repositoryInclude die Repositories nicht mehr per include, sondern ueber eine ausdrueckliche Feldliste: ein include haette das neue Feld still in jede Projektantwort und jede Seite mitgenommen. Ob ein Haken eingerichtet ist, holt die Projektseite getrennt. Die Migration legt zwei nullbare Spalten an, mehr nicht; der Gegenlauf von migrate diff kommt leer heraus. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
64 lines
2.2 KiB
TypeScript
64 lines
2.2 KiB
TypeScript
import { randomBytes } from "node:crypto";
|
||
import { NextResponse } from "next/server";
|
||
import { requireUserApi } from "@/lib/auth/guard";
|
||
import { APP_URL } from "@/lib/config";
|
||
import { encryptSecret } from "@/lib/crypto";
|
||
import { prisma } from "@/lib/db";
|
||
import { ownedRepository } from "@/lib/repositories";
|
||
import { jsonError } from "@/lib/security/request";
|
||
|
||
export const dynamic = "force-dynamic";
|
||
|
||
type Params = { params: Promise<{ id: string }> };
|
||
|
||
/**
|
||
* Richtet den Webhook eines Repositories ein oder nimmt ihn zurueck.
|
||
*
|
||
* Das Geheimnis wird hier erzeugt und **genau einmal** im Klartext
|
||
* zurueckgegeben – danach liegt es nur noch verschluesselt in der Datenbank,
|
||
* wie die Zugangstoken. Wer es verliert, erzeugt ein neues; das alte gilt
|
||
* dann nicht mehr.
|
||
*
|
||
* Eingetragen wird der Haken im Git-System von Hand. Ihn ueber die API
|
||
* anzulegen waere machbar, verlangte aber Administrationsrechte am
|
||
* Repository – deutlich mehr, als Nebula sonst braucht.
|
||
*/
|
||
export async function POST(_request: Request, { params }: Params) {
|
||
const auth = await requireUserApi();
|
||
if (!auth.ok) return auth.response;
|
||
|
||
const { id } = await params;
|
||
const repository = await ownedRepository(id, auth.user.id);
|
||
if (!repository) return jsonError("Repository nicht gefunden", 404);
|
||
|
||
// 32 Byte, hexadezimal. Lang genug, dass Raten aussichtslos ist, und
|
||
// ohne Sonderzeichen, die in Formularfeldern Aerger machen.
|
||
const secret = randomBytes(32).toString("hex");
|
||
|
||
await prisma.repository.update({
|
||
where: { id: repository.id },
|
||
data: { webhookSecret: encryptSecret(secret), webhookLastAt: null },
|
||
});
|
||
|
||
return NextResponse.json({
|
||
secret,
|
||
url: `${APP_URL}/api/webhooks/${repository.id}`,
|
||
});
|
||
}
|
||
|
||
export async function DELETE(_request: Request, { params }: Params) {
|
||
const auth = await requireUserApi();
|
||
if (!auth.ok) return auth.response;
|
||
|
||
const { id } = await params;
|
||
const repository = await ownedRepository(id, auth.user.id);
|
||
if (!repository) return jsonError("Repository nicht gefunden", 404);
|
||
|
||
await prisma.repository.update({
|
||
where: { id: repository.id },
|
||
data: { webhookSecret: null, webhookLastAt: null },
|
||
});
|
||
|
||
return NextResponse.json({ ok: true });
|
||
}
|