feat(git): Webhooks fuer Push und Issues, v1.7.0
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>
This commit is contained in:
2026-08-15 00:35:29 +02:00
co-authored by Claude Opus 5
parent e1d435f10a
commit c270038edd
15 changed files with 956 additions and 52 deletions
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "Repository" ADD COLUMN "webhookLastAt" TIMESTAMP(3),
ADD COLUMN "webhookSecret" TEXT;
+10
View File
@@ -407,6 +407,16 @@ model Repository {
notifiedCiState String?
notifiedRelease String?
/// Gemeinsames Geheimnis fuer eingehende Webhooks, verschluesselt wie die
/// Zugangstoken. Je Repository, weil der Haken im Git-System dort
/// eingetragen wird und sich einzeln zuruecknehmen laesst.
/// NULL heisst: fuer dieses Repository ist kein Webhook eingerichtet, und
/// eingehende Aufrufe werden abgewiesen.
webhookSecret String?
/// Wann zuletzt ein gueltiger Webhook ankam – die einzige Rueckmeldung
/// darueber, ob der Haken im Git-System wirklich funktioniert.
webhookLastAt DateTime?
snapshot RepoSnapshot?
createdAt DateTime @default(now())