Token-Rechte vollstaendig dokumentiert
CI / pruefen (pull_request) Canceled after 0s

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
This commit is contained in:
2026-08-18 12:10:17 +02:00
parent 6c3f2d5bb6
commit 7920b6a20f
4 changed files with 135 additions and 11 deletions
+24
View File
@@ -485,6 +485,30 @@ Firmen-Gitea nebeneinander.
Kurzbeschreibung. Kurzbeschreibung.
- **Löschen** trennt nur die Verknüpfung; die Projekte selbst bleiben erhalten. - **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 ### 4.3 Was beim Sync geholt wird
| Bereich | Details | | Bereich | Details |
+43 -8
View File
@@ -173,14 +173,8 @@ Nebula auffindbare Credentials nutzt.
## Git-Verbindung einrichten ## Git-Verbindung einrichten
1. Im Git-System einen **Personal Access Token** mit Leserechten erzeugen: 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).
| 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` |
2. In Nebula unter **Git-Verbindungen → Verbindung hinzufügen** eintragen: 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. 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. 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. Der Snapshot bleibt in der Datenbank, das Dashboard funktioniert also auch offline.
Ohne Token gehen nur öffentliche Repositories, mit strengem Rate-Limit. 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 ### Automatisch statt auf Knopfdruck
Unter **Admin-Einstellungen → Automatischer Abgleich** lässt sich der Sync in den Unter **Admin-Einstellungen → Automatischer Abgleich** lässt sich der Sync in den
+60
View File
@@ -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");
});
});
+8 -3
View File
@@ -12,19 +12,24 @@ export const PROVIDER_META: Record<
label: "GitHub", label: "GitHub",
defaultBaseUrl: "https://github.com", defaultBaseUrl: "https://github.com",
tokenHelp: 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", docsUrl: "https://github.com/settings/tokens",
}, },
GITLAB: { GITLAB: {
label: "GitLab", label: "GitLab",
defaultBaseUrl: "https://gitlab.com", 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", docsUrl: "https://gitlab.com/-/user_settings/personal_access_tokens",
}, },
GITEA: { GITEA: {
label: "Gitea / Forgejo", label: "Gitea / Forgejo",
defaultBaseUrl: "https://git.example.com", 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", docsUrl: "https://docs.gitea.com/development/api-usage",
}, },
}; };