From 7920b6a20f0fac893260062f8e3799c136b90942 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Tue, 18 Aug 2026 12:10:17 +0200 Subject: [PATCH] Token-Rechte vollstaendig dokumentiert Die Angaben stammten aus der Zeit, als Nebula nur gelesen hat. Seit v1.7.0 legt es Issues an und schliesst sie - wer sich an README oder den Hinweis im Verbindungsdialog hielt, bekam dabei einen 403. Erhoben wurde es nicht aus dem Gedaechtnis, sondern aus dem Code: jeder ausgehende Aufruf in lib/git/gitea.ts, github.ts und gitlab.ts, abgebildet auf das noetige Recht. Aufgeschluesselt nach Funktion statt nach System, damit erkennbar ist, was sich weglassen laesst: der Abgleich allein kommt rein lesend aus, nur die Issue-Anbindung schreibt. **GitLab ist der unangenehme Fall.** Es kennt keinen schmalen Schreib-Scope fuer Issues - dort geht nur "api", also alles, was das Konto darf. Das steht jetzt ausdruecklich da, samt der Alternative: bei read_api bleiben und auf die Issue-Anbindung verzichten. Der Abgleich laeuft dann vollstaendig, und geschlossene Issues werden weiterhin erkannt, nur nicht zurueckgeschrieben. Ebenfalls festgehalten, was Nebula *nicht* braucht: Schreibrechte auf Code, Adminrechte, Rechte zum Anlegen von Webhooks. Letzteres ist der Grund, warum der Haken von Hand eingetragen wird - so bleibt der Token davon frei. Der Hinweis in lib/git/meta.ts steht direkt am Eingabefeld und ist die Stelle, die tatsaechlich gelesen wird; er ist deshalb mitgezogen. Dazu ein Waechter in meta.test.ts: jeder Provider muss in GitClient createIssue und setIssueState koennen, folglich muss auch jeder Hinweis sagen, was das an Rechten kostet. Genau diese Kopplung war hier gerissen. Geprueft: 267 Unit-Tests (6 neue), 36 E2E-Tests, Hinweistext je System gegen die laufende Anwendung gelesen, axe-core auf 375px und 1280px ohne Beanstandung. Refs #10 --- FEATURES.md | 24 ++++++++++++++++ README.md | 51 ++++++++++++++++++++++++++++------ src/lib/git/meta.test.ts | 60 ++++++++++++++++++++++++++++++++++++++++ src/lib/git/meta.ts | 11 ++++++-- 4 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 src/lib/git/meta.test.ts diff --git a/FEATURES.md b/FEATURES.md index b957c34..fcf90e7 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -485,6 +485,30 @@ Firmen-Gitea nebeneinander. Kurzbeschreibung. - **Löschen** trennt nur die Verknüpfung; die Projekte selbst bleiben erhalten. +#### Nötige Token-Rechte + +Nebula liest fast alles nur. Geschrieben wird ausschließlich beim Anlegen und Schließen +von Issues (4.6) — wer darauf verzichtet, kommt mit einem rein lesenden Token aus. + +| System | Nur Abgleich | Zusätzlich für die Issue-Anbindung | +| --- | --- | --- | +| GitHub | *Metadata*, *Contents*, *Issues*, *Pull requests*, *Actions* — je lesend | *Issues: Read and write* | +| GitLab | `read_api` | `api` | +| Gitea / Forgejo | `read:repository`, `read:issue`, `read:user` | `write:issue` | + +**GitLab kennt keinen schmalen Schreib-Scope.** `api` erlaubt alles, was das Konto darf, +Code eingeschlossen. Wem das zu weit geht, bleibt bei `read_api`: der Abgleich läuft +vollständig, geschlossene Issues werden weiterhin erkannt — nur schreibt Nebula dann +nicht zurück. + +**Nicht verlangt** werden Schreibrechte auf Code, Adminrechte und Rechte zum Anlegen von +Webhooks. Der Haken wird von Hand im Git-System eingetragen (4.7); das erspart dem Token +genau die Administrationsrechte, die er sonst bräuchte. + +Ein fehlendes Recht endet in einem `403` mit „Token hat nicht die noetigen Scopes", +sichtbar am Repository auf der Projektseite. Klein anfangen und gezielt nachlegen ist +deshalb ohne Risiko. + ### 4.3 Was beim Sync geholt wird | Bereich | Details | diff --git a/README.md b/README.md index 4e7b4ab..a8bec2e 100644 --- a/README.md +++ b/README.md @@ -173,14 +173,8 @@ Nebula auffindbare Credentials nutzt. ## Git-Verbindung einrichten -1. Im Git-System einen **Personal Access Token** mit Leserechten erzeugen: - - | System | Scopes | - | --- | --- | - | GitHub | Fine-grained Token: *Contents: Read*, *Metadata: Read*, *Issues: Read*, *Pull requests: Read* | - | GitLab | `read_api` (alternativ `read_repository`) | - | Gitea / Forgejo | `repository:read`, `issue:read`, `user:read` | - +1. Im Git-System einen **Personal Access Token** erzeugen — mit den Rechten aus dem + Abschnitt [Welche Rechte der Token braucht](#welche-rechte-der-token-braucht). 2. In Nebula unter **Git-Verbindungen → Verbindung hinzufügen** eintragen: System wählen, Basis-URL (z. B. `https://git.meine-firma.de`, ohne `/api`) und Token. 3. Mit **Testen** prüfen — Nebula meldet den erkannten Benutzer zurück. @@ -192,6 +186,47 @@ Nebula auffindbare Credentials nutzt. Der Snapshot bleibt in der Datenbank, das Dashboard funktioniert also auch offline. Ohne Token gehen nur öffentliche Repositories, mit strengem Rate-Limit. +### Welche Rechte der Token braucht + +Nebula liest fast alles nur — geschrieben wird ausschließlich, wenn du eine Aufgabe als +Issue anlegst oder abhakst. Deshalb lohnt es, zwei Fälle zu unterscheiden: + +**Fall A — nur Abgleich.** Branches, Tags, Commits, Releases, CI-Zustand, README, +Heatmap, offene Issues und PRs anzeigen. Rein lesend. + +**Fall B — zusätzlich die Issue-Anbindung.** Eine Aufgabe als Issue anlegen; wird das +Issue geschlossen, hakt Nebula die Aufgabe ab und schließt sie umgekehrt mit. + +| System | Fall A (nur lesen) | Fall B zusätzlich | +| --- | --- | --- | +| **GitHub** | Fine-grained Token: *Metadata: Read*, *Contents: Read*, *Issues: Read*, *Pull requests: Read*, *Actions: Read* | *Issues: **Read and write*** | +| **GitLab** | `read_api` | `api` — **es gibt nichts dazwischen** | +| **Gitea / Forgejo** | `read:repository`, `read:issue`, `read:user` | `write:issue` | + +Wozu die einzelnen Rechte gut sind: + +| Recht | Wofür | +| --- | --- | +| Repository lesen (`Contents`, `read:repository`) | Branches, Tags, Commits, Releases, Sprachen, README, Commit-Heatmap | +| Issues lesen | Offene Issues auf der Projektseite, „Issues als Aufgaben übernehmen" | +| Pull Requests lesen | Offene PRs auf der Projektseite | +| Actions lesen (nur GitHub) | CI-Zustand des Default-Branch. Bei GitLab kommt er aus den Pipelines (`read_api`), bei Gitea aus dem Commit-Status (`read:repository`) | +| Benutzer lesen | Nur für den Verbindungstest — Nebula meldet zurück, als wer der Token gilt | +| Issues schreiben | Aufgabe als Issue anlegen, schließen, wieder öffnen | + +> **GitLab ist der unangenehme Fall.** Es kennt keinen schmalen Schreib-Scope für Issues: +> `api` erlaubt alles, was dein Konto darf — auch Code ändern. Wenn dir das zu weit geht, +> nimm `read_api` und verzichte auf die Issue-Anbindung. Der Abgleich funktioniert +> vollständig, und geschlossene Issues werden trotzdem erkannt — nur eben nur gelesen. + +**Was Nebula *nicht* braucht:** Schreibrechte auf Code, Adminrechte, Rechte zum Anlegen +von Webhooks. Den Haken für Webhooks trägst du selbst im Git-System ein — genau deshalb +verlangt Nebula dafür kein zusätzliches Recht. + +**Im Zweifel klein anfangen.** Ein fehlendes Recht führt zu einem `403` mit dem Hinweis +„Token hat nicht die noetigen Scopes"; die Meldung steht am Repository auf der +Projektseite. Dann gezielt nachlegen, statt vorsorglich alles zu vergeben. + ### Automatisch statt auf Knopfdruck Unter **Admin-Einstellungen → Automatischer Abgleich** lässt sich der Sync in den diff --git a/src/lib/git/meta.test.ts b/src/lib/git/meta.test.ts new file mode 100644 index 0000000..045cc15 --- /dev/null +++ b/src/lib/git/meta.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from "vitest"; +import { PROVIDER_META, guessProvider } from "./meta"; + +const PROVIDER = ["GITHUB", "GITLAB", "GITEA"] as const; + +describe("PROVIDER_META", () => { + it("kennt jedes unterstuetzte System", () => { + for (const provider of PROVIDER) { + expect(PROVIDER_META[provider], provider).toBeDefined(); + } + // Und nichts darueber hinaus – ein Eintrag ohne Gegenstueck im Schema + // waere ein Ueberbleibsel. + expect(Object.keys(PROVIDER_META).sort()).toEqual([...PROVIDER].sort()); + }); + + it("hat zu jedem System Beschriftung, Basis-URL und Dokuverweis", () => { + for (const provider of PROVIDER) { + const meta = PROVIDER_META[provider]; + expect(meta.label.length, provider).toBeGreaterThan(0); + expect(meta.defaultBaseUrl, provider).toMatch(/^https:\/\//); + expect(meta.docsUrl, provider).toMatch(/^https:\/\//); + } + }); + + /** + * Der eigentliche Waechter. + * + * Der Hinweis am Eingabefeld war eine Zeit lang rein lesend formuliert, + * obwohl Nebula seit v1.7.0 Issues anlegt und schliesst – wer sich daran + * hielt, bekam einen 403. Jeder Provider in GitClient muss createIssue und + * setIssueState koennen; folglich muss auch jeder Hinweis sagen, was das + * an Rechten kostet. + */ + it("sagt bei jedem System etwas ueber die Rechte fuer Issues", () => { + for (const provider of PROVIDER) { + expect(PROVIDER_META[provider].tokenHelp, provider).toMatch(/issue/i); + } + }); + + it("nennt einen konkreten Scope statt nur 'Leserechte'", () => { + // GitLab nennt Scopes in Anfuehrungszeichen, GitHub und Gitea + // Berechtigungsnamen – gemeinsam ist, dass etwas Nachschlagbares dasteht. + expect(PROVIDER_META.GITLAB.tokenHelp).toMatch(/read_api/); + expect(PROVIDER_META.GITEA.tokenHelp).toMatch(/read:repository/); + expect(PROVIDER_META.GITHUB.tokenHelp).toMatch(/Metadata/); + }); +}); + +describe("guessProvider", () => { + it("erkennt die oeffentlichen Instanzen am Hostnamen", () => { + expect(guessProvider("github.com")).toBe("GITHUB"); + expect(guessProvider("gitlab.com")).toBe("GITLAB"); + }); + + it("faellt bei allem anderen auf Gitea zurueck", () => { + // Selbst gehostet ist hier der haeufigste Fall, und Gitea die + // wahrscheinlichste Wahl dafuer. + expect(guessProvider("git.wilde-server.de")).toBe("GITEA"); + }); +}); diff --git a/src/lib/git/meta.ts b/src/lib/git/meta.ts index 0fa0bed..26cd4b7 100644 --- a/src/lib/git/meta.ts +++ b/src/lib/git/meta.ts @@ -12,19 +12,24 @@ export const PROVIDER_META: Record< label: "GitHub", defaultBaseUrl: "https://github.com", tokenHelp: - "Fine-grained Token mit Lesezugriff auf Contents, Metadata, Issues und Pull Requests.", + "Fine-grained Token: Metadata, Contents und Pull requests lesen, Issues lesen. " + + "Nur wenn Aufgaben als Issue angelegt werden sollen, Issues zusaetzlich schreiben.", docsUrl: "https://github.com/settings/tokens", }, GITLAB: { label: "GitLab", defaultBaseUrl: "https://gitlab.com", - tokenHelp: "Personal Access Token mit Scope 'read_api' (bzw. 'read_repository').", + tokenHelp: + "Scope 'read_api' genuegt fuer den Abgleich. Aufgaben als Issue anzulegen verlangt " + + "'api' – GitLab kennt keinen schmaleren Schreibzugriff.", docsUrl: "https://gitlab.com/-/user_settings/personal_access_tokens", }, GITEA: { label: "Gitea / Forgejo", defaultBaseUrl: "https://git.example.com", - tokenHelp: "Access Token mit Leserechten auf repository, issue und user.", + tokenHelp: + "read:repository, read:issue und read:user. Nur wenn Aufgaben als Issue angelegt " + + "werden sollen, zusaetzlich write:issue.", docsUrl: "https://docs.gitea.com/development/api-usage", }, };