Files
D4rkst3randClaude Fable 5 2ce274707a
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Config statt Setup: Bereiche gruppiert, Suche, Status als Einstieg
Die Seite hieß Setup und listete 15 Bereiche flach untereinander. Wer
nicht wusste, dass Geburtstage unter „Community" stecken und Willkommens-
Texte unter „Support", hat geklickt bis er es fand.

Die Seitenleiste gruppiert jetzt nach Überblick, Auftritt, Inhalte,
Community, Technik und Zugang. Darüber steht ein Suchfeld, das nicht nur
Beschriftungen durchsucht, sondern auch Stichwörter je Bereich — „geburtstag"
führt zu Community, „ticket" zu Support. Enter springt zum ersten Treffer.

Der aktive Bereich steht in der Adresse (/settings#texte). Damit überlebt er
das Neuladen, und man kann jemandem einen Link auf genau die Stelle schicken.

Der Status-Bereich war die leerste Seite im ganzen Panel: sechs Zahlen. Er ist
jetzt der Einstieg und beantwortet die Frage, die man beim Öffnen wirklich hat
— was läuft noch nicht? Eingeschaltete Module, denen ein Kanal oder eine Rolle
fehlt, stehen dort mit einem Knopf, der direkt an die richtige Stelle springt.
Ist alles eingerichtet, sagt die Seite genau das.

Umbenannt in „Config", weil „Setup" nach einmaliger Einrichtung klingt — die
Seite ist aber der Ort, an dem man dauerhaft alles einstellt.

Schmale Bildschirme: die Leiste wird zur umbrechenden Zeile ohne
Gruppen-Überschriften, das Suchfeld nimmt die volle Breite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 16:13:33 +02:00

138 lines
4.9 KiB
Markdown

# Single Sign-On mit d4rkbot
Der Bot ist Identity-Provider für die anderen D4RKST3R-Dienste (Kanban, Platform …).
Statt in jeder App einen eigenen Discord-OAuth2-Flow zu bauen, fragst du beim Bot nach —
und bekommst dabei gleich die **Discord-Rollen** des Users mitgeliefert.
## 1. App registrieren
Config → **API***Single Sign-On*:
| Feld | Beispiel | Bedeutung |
|---|---|---|
| Kürzel | `kanban` | interner Slug, taucht in der URL auf |
| Name | `Kanban-Board` | Anzeigename |
| Rücksprung-URL | `https://kanban.d4rkst3r.de/` | erlaubte Ziele (mehrere kommagetrennt) |
Beim Anlegen wird ein **Secret** angezeigt — einmalig, danach nie wieder.
In der App als Umgebungsvariable hinterlegen (`SSO_SECRET`), niemals ins Repo.
> Der Bot leitet ausschließlich auf Adressen zurück, die mit einer der hinterlegten
> Rücksprung-URLs beginnen. Das verhindert, dass jemand den Login-Flow auf eine
> fremde Seite umbiegt und den Token abgreift.
## 2. Ablauf
```
Nutzer klickt "Login" → https://bot.d4rkst3r.de/sso/authorize
?app=kanban
&redirect=https://kanban.d4rkst3r.de/auth/callback
↓ (Bot prüft Session, ggf. erst Discord-Login)
Zurück zu https://kanban.d4rkst3r.de/auth/callback?sso_token=<token>
↓ (App tauscht den Token serverseitig ein)
POST https://bot.d4rkst3r.de/sso/verify
Authorization: Bearer <SSO_SECRET>
{ "token": "<token>" }
```
Der Token ist **60 Sekunden** gültig und nur **einmal** einlösbar. Er selbst enthält
keine Nutzerdaten — die gibt es erst beim Verify-Aufruf, der serverseitig passieren muss.
## 3. Antwort von `/sso/verify`
```json
{
"user": {
"id": "123456789012345678",
"username": "d4rkst3r",
"displayName": "Alexander",
"avatar": "https://cdn.discordapp.com/avatars/…",
"joinedAt": "2026-02-01T12:00:00.000Z"
},
"roles": [
{ "id": "111…", "name": "Entwickler" },
{ "id": "222…", "name": "Playtester" }
],
"owner": false,
"scopes": ["content", "server"],
"app": "kanban"
}
```
* `owner` — ist der Server-Owner (`ADMIN_DISCORD_ID`)
* `scopes` — Team-Bereiche aus dem Bot (`['*']` beim Owner, sonst z. B. `['content']`)
* `roles` — alle Discord-Rollen; damit kannst du in der App Rechte vergeben,
ohne eine eigene Nutzerverwaltung zu pflegen
## 4. Beispiel (Node / Express)
```js
const BOT = 'https://bot.d4rkst3r.de';
app.get('/login', (req, res) => {
const redirect = `${process.env.PUBLIC_URL}/auth/callback`;
res.redirect(`${BOT}/sso/authorize?app=kanban&redirect=${encodeURIComponent(redirect)}`);
});
app.get('/auth/callback', async (req, res) => {
const result = await fetch(`${BOT}/sso/verify`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.SSO_SECRET}`,
},
body: JSON.stringify({ token: req.query.sso_token }),
});
if (!result.ok) return res.status(401).send('Login fehlgeschlagen');
const { user, roles, owner } = await result.json();
req.session.user = {
id: user.id,
name: user.displayName ?? user.username,
avatar: user.avatar,
// Rechte direkt aus den Discord-Rollen ableiten
canEdit: owner || roles.some((r) => r.name === 'Entwickler'),
};
res.redirect('/');
});
```
## 5. Aussehen mitnehmen (Design-System)
Damit die angebundene App nicht wie ein Fremdkörper wirkt, liefert der Bot die
Brand-Tokens und eine fertige Navigation aus:
```html
<link rel="stylesheet" href="https://bot.d4rkst3r.de/brand.css">
<script defer src="https://bot.d4rkst3r.de/brand-nav.js"></script>
```
* **`/brand.css`** — CSS-Variablen (`--neon`, `--bg`, `--display` …) plus ein paar
Basis-Klassen: `.d4rk-card`, `.d4rk-btn`, `.d4rk-title`, `.d4rk-tag`.
Die Farben kommen live aus dem Brand-Tab — änderst du sie dort, ändern sie sich
überall mit (Cache: 5 Minuten).
* **`/brand-nav.js`** — fügt oben die D4RKST3R-Leiste ein, mit Links zurück zum Hub
und zu allen in der Config gepflegten Diensten. Die Links stecken fertig im Skript,
es sind also keine weiteren Anfragen und kein CORS nötig.
Die Schriften installierst du in der App selbst, damit sie lokal ausgeliefert werden
(kein Google-Fonts-Aufruf):
```bash
npm i @fontsource/bebas-neue @fontsource/barlow-condensed @fontsource/share-tech-mono
```
## 6. Hinweise
* Der Verify-Aufruf muss **serverseitig** erfolgen — das Secret darf nie im Browser landen.
* Nur Mitglieder des konfigurierten Discord-Servers können sich anmelden; alle anderen
landen auf der Beitritts-Seite des Bots.
* Rollen werden bei jedem Login frisch von Discord geholt. Wer eine Rolle verliert,
verliert die Rechte beim nächsten Login — für sofortige Wirkung die Sitzung in der
App kurz halten oder periodisch neu prüfen.
* Wird eine App in der Config gelöscht, funktioniert ihr Login sofort nicht mehr.