Add multi-guild Discord OAuth support

- Users can now login via Bacanaks OR Piccadilly Discord server
- Highest role from all servers is used (superadmin > moderator > user)
- Lazy initialization fixes env loading timing issue
- Updated documentation with implementation details and troubleshooting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-08 00:30:15 +01:00
parent 4fcc111def
commit 20ba93b26f
3 changed files with 260 additions and 31 deletions

View File

@@ -49,13 +49,15 @@ Wenn der Bot einem Server beitritt, erstellt er automatisch folgende Struktur:
| Channel | @everyone | Bot |
|---------|-----------|-----|
| Kategorie | Lesen | Schreiben |
| info | Lesen | Schreiben |
| status | Lesen | Schreiben |
| alerts | Lesen | Schreiben |
| updates | Lesen | Schreiben |
| diskussion | Lesen + Schreiben | Schreiben |
| requests | Lesen + Threads erstellen | Schreiben |
| Kategorie | Lesen | ViewChannel + Schreiben |
| info | Lesen (kein Schreiben) | ViewChannel + Schreiben |
| status | Lesen (kein Schreiben) | ViewChannel + Schreiben |
| alerts | Lesen (kein Schreiben) | ViewChannel + Schreiben |
| updates | Lesen (kein Schreiben) | ViewChannel + Schreiben |
| diskussion | Lesen + Schreiben | ViewChannel + Schreiben |
| requests | Lesen + Threads erstellen | ViewChannel + Schreiben |
**Wichtig**: Der Bot braucht explizit `ViewChannel` Permission für jeden Channel, auch wenn @everyone den Channel sehen kann. Ohne `ViewChannel` kann der Bot nicht in den Channel schreiben ("Missing Access" Fehler).
## Datenbank
@@ -80,6 +82,8 @@ CREATE TABLE guild_settings (
);
```
Die Datenbank liegt in `backend/db/users.sqlite`.
### DB-Funktionen
In `backend/db/init.js`:
@@ -137,12 +141,43 @@ Jeder Server bekommt ein eigenes Embed mit:
DISCORD_CLIENT_ID=1458251194806833306
DISCORD_CLIENT_SECRET=xxx
DISCORD_BOT_TOKEN=xxx
DISCORD_GUILD_ID=729865854329815051 # Haupt-Server für Login
DISCORD_ADMIN_ROLE_ID=1024693717434650736
DISCORD_MOD_ROLE_ID=1024693170958766141
# Multi-Server OAuth Login
# User muss nur in EINEM der Server Mitglied sein
# Rollen werden pro Server geprüft, höchste Berechtigung zählt
# Server 1: Bacanaks
DISCORD_GUILD_ID_1=729865854329815051
DISCORD_ADMIN_ROLE_ID_1=1024693717434650736
DISCORD_MOD_ROLE_ID_1=1024693170958766141
# Server 2: Piccadilly
DISCORD_GUILD_ID_2=730907665802330224
DISCORD_ADMIN_ROLE_ID_2=1458595551514988584
DISCORD_MOD_ROLE_ID_2=1458591909210488914
```
**Hinweis**: `DISCORD_GUILD_ID` wird nur für den Discord OAuth Login verwendet, nicht für den Bot selbst.
**Hinweis**: Die Guild-IDs werden nur für den Discord OAuth Login verwendet, nicht für den Bot selbst.
### OAuth Login-Logik
```
1. User loggt sich via Discord OAuth ein
2. Für jeden konfigurierten Server:
- Ist User Mitglied? → Rollen prüfen
- Admin-Rolle → superadmin
- Mod-Rolle → moderator
- Nur Mitglied → user
3. Höchste Berechtigung aus allen Servern wird verwendet
4. Nicht in mindestens einem Server → Login verweigert
```
| User ist in... | Bacanaks Rolle | Piccadilly Rolle | GSM Rolle |
|----------------|----------------|------------------|-----------|
| Nur Bacanaks | Admin | - | superadmin |
| Nur Piccadilly | - | Mod | moderator |
| Beide | Mitglied | Admin | superadmin |
| Keinem | - | - | ❌ Kein Login |
### Developer Portal Einstellungen
@@ -158,12 +193,84 @@ DISCORD_MOD_ROLE_ID=1024693170958766141
| Datei | Beschreibung |
|-------|--------------|
| `backend/services/discord.js` | OAuth-Logik, Multi-Guild Membership-Prüfung |
| `backend/services/discordBot.js` | Bot-Logik und Event-Handler |
| `backend/routes/auth.js` | Auth-Endpoints (Login, Callback, Refresh) |
| `backend/db/init.js` | Guild-Settings DB-Funktionen |
| `frontend/src/pages/Dashboard.jsx` | Invite-Button im Dashboard |
## Implementierungsdetails
### Multi-Guild OAuth
Die OAuth-Implementierung in `discord.js` verwendet **lazy initialization** für die Guild-Konfigurationen:
```javascript
let _guildConfigs = null;
function getGuildConfigs() {
if (_guildConfigs === null) {
_guildConfigs = [
{ name: 'Bacanaks', guildId: process.env.DISCORD_GUILD_ID_1, ... },
{ name: 'Piccadilly', guildId: process.env.DISCORD_GUILD_ID_2, ... }
].filter(config => config.guildId);
}
return _guildConfigs;
}
```
**Wichtig**: Die Konfiguration darf NICHT beim Modul-Import initialisiert werden, da zu diesem Zeitpunkt dotenv die `.env` noch nicht geladen hat. Die lazy initialization stellt sicher, dass die Umgebungsvariablen verfügbar sind.
### Funktionen
| Funktion | Beschreibung |
|----------|--------------|
| `getGuildMemberships(userId)` | Prüft alle konfigurierten Server, gibt Array von Memberships zurück |
| `getUserRoleFromMemberships(memberships)` | Bestimmt höchste Rolle aus allen Memberships |
| `getGuildMember(userId)` | Legacy-Funktion, gibt ersten Match zurück |
| `getUserRole(memberRoles)` | Legacy-Funktion für einzelne Rollen-Liste |
### Rollen-Priorität
```javascript
const ROLE_PRIORITY = { superadmin: 3, moderator: 2, user: 1 };
```
Bei mehreren Memberships wird immer die höchste Rolle verwendet.
### Voraussetzungen
- Der Bot muss auf **allen** konfigurierten Discord-Servern sein
- Der Bot braucht Zugriff auf die Guild Members API
## Troubleshooting
### Login schlägt fehl mit "nicht Mitglied"
1. **Bot auf allen Servern?** Der Bot muss auf Bacanaks UND Piccadilly eingeladen sein
2. **Env-Variablen prüfen**:
```bash
grep GUILD /opt/gameserver-monitor/backend/.env
```
3. **Mit --update-env neustarten**:
```bash
pm2 restart gameserver-backend --update-env
```
4. **Logs prüfen**:
```bash
pm2 logs gameserver-backend --lines 30 | grep -i discord
```
### Rolle wird nicht erkannt
1. **Rollen-IDs prüfen** - Discord Developer Mode aktivieren, Rechtsklick auf Rolle → ID kopieren
2. **User-Rollen abfragen**:
```bash
curl -s -H "Authorization: Bot BOT_TOKEN" \
"https://discord.com/api/v10/guilds/GUILD_ID/members/USER_ID" | jq '.roles'
```
3. **Konfigurierte IDs vergleichen** mit den tatsächlichen Rollen des Users
### Bot erstellt keine Channels
- Prüfen ob Bot "Manage Channels" Permission hat
@@ -180,18 +287,52 @@ Suche nach `[DiscordBot]` Log-Einträgen.
### Bot aus Datenbank entfernen
```bash
sqlite3 /opt/gameserver-monitor/backend/users.sqlite
cd /opt/gameserver-monitor/backend
node -e "
import Database from 'better-sqlite3';
const db = new Database('./db/users.sqlite');
db.prepare('DELETE FROM guild_settings WHERE guild_id = ?').run('GUILD_ID_HIER');
console.log('Deleted');
"
```
Alternativ mit sqlite3 (falls installiert):
```bash
sqlite3 /opt/gameserver-monitor/backend/db/users.sqlite
DELETE FROM guild_settings WHERE guild_id = 'xxx';
```
### Missing Access Fehler
Wenn der Bot "Missing Access" meldet obwohl er eingeladen wurde:
1. **ViewChannel Permission prüfen**: Der Bot braucht explizit `ViewChannel` für jeden Channel
2. Im Discord: Rechtsklick auf Channel → Bearbeiten → Berechtigungen → Bot auswählen → "Kanal ansehen" aktivieren
3. Logs prüfen: `pm2 logs gameserver-backend --lines 50 | grep -i "missing\|access"`
### Status-Nachricht wurde gelöscht
Wenn die Status-Nachricht manuell gelöscht wurde, erscheint "Unknown Message" in den Logs. Fix:
```bash
cd /opt/gameserver-monitor/backend
node -e "
import Database from 'better-sqlite3';
const db = new Database('./db/users.sqlite');
// Status Message ID auf NULL setzen, Bot erstellt neue beim nächsten Update
db.prepare('UPDATE guild_settings SET status_message_id = NULL WHERE guild_id = ?').run('GUILD_ID_HIER');
console.log('Reset status_message_id');
"
```
## Login vs. Bot
| Feature | Login (OAuth) | Bot |
|---------|--------------|-----|
| Erfordert Mitgliedschaft | Haupt-Discord | Nein |
| Erfordert Mitgliedschaft | Bacanaks oder Piccadilly | Nein |
| Server-Steuerung | Ja (je nach Rolle) | Nein |
| Status sehen | Ja | Ja |
| Alerts erhalten | Nein | Ja |
| Verfügbar für | Haupt-Discord Mitglieder | Alle mit Bot |
| Verfügbar für | Mitglieder beider Discord-Server | Alle mit Bot |
Der Login zur Webapp erfordert Mitgliedschaft im Haupt-Discord-Server (DISCORD_GUILD_ID). Der Bot ist davon unabhängig und zeigt nur passive Status-Updates.
Der Login zur Webapp erfordert Mitgliedschaft in mindestens einem der konfigurierten Discord-Server (Bacanaks oder Piccadilly). Die höchste Rolle aus beiden Servern bestimmt die GSM-Berechtigung. Der Bot ist davon unabhängig und zeigt nur passive Status-Updates.