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

@@ -1,6 +1,29 @@
// Discord OAuth2 Service
const DISCORD_API = 'https://discord.com/api/v10';
// Lazy initialization - wird erst bei Verwendung geladen (nach dotenv)
let _guildConfigs = null;
function getGuildConfigs() {
if (_guildConfigs === null) {
_guildConfigs = [
{
name: 'Bacanaks',
guildId: process.env.DISCORD_GUILD_ID_1,
adminRoleId: process.env.DISCORD_ADMIN_ROLE_ID_1,
modRoleId: process.env.DISCORD_MOD_ROLE_ID_1
},
{
name: 'Piccadilly',
guildId: process.env.DISCORD_GUILD_ID_2,
adminRoleId: process.env.DISCORD_ADMIN_ROLE_ID_2,
modRoleId: process.env.DISCORD_MOD_ROLE_ID_2
}
].filter(config => config.guildId);
}
return _guildConfigs;
}
export function getDiscordAuthUrl() {
const params = new URLSearchParams({
client_id: process.env.DISCORD_CLIENT_ID,
@@ -48,9 +71,10 @@ export async function getDiscordUser(accessToken) {
return response.json();
}
export async function getGuildMember(userId) {
// Prüft einen einzelnen Server
async function fetchGuildMember(guildId, userId) {
const response = await fetch(
`${DISCORD_API}/guilds/${process.env.DISCORD_GUILD_ID}/members/${userId}`,
`${DISCORD_API}/guilds/${guildId}/members/${userId}`,
{
headers: {
Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN}`
@@ -60,17 +84,78 @@ export async function getGuildMember(userId) {
if (!response.ok) {
if (response.status === 404) {
return null; // User not in guild
return null;
}
throw new Error('Failed to get guild member');
throw new Error(`Failed to get guild member from ${guildId}`);
}
return response.json();
}
// Prüft alle konfigurierten Server und gibt Memberships zurück
export async function getGuildMemberships(userId) {
const configs = getGuildConfigs();
const memberships = [];
for (const config of configs) {
try {
const member = await fetchGuildMember(config.guildId, userId);
if (member) {
memberships.push({
config,
member,
roles: member.roles || []
});
}
} catch (err) {
console.error(`[Discord] Failed to check membership for guild ${config.name}:`, err.message);
}
}
return memberships.length > 0 ? memberships : null;
}
// Legacy-Funktion für Kompatibilität
export async function getGuildMember(userId) {
const memberships = await getGuildMemberships(userId);
if (!memberships || memberships.length === 0) {
return null;
}
return memberships[0].member;
}
// Rollen-Priorität: superadmin > moderator > user
const ROLE_PRIORITY = { superadmin: 3, moderator: 2, user: 1 };
// Bestimmt die höchste Rolle aus allen Server-Memberships
export function getUserRoleFromMemberships(memberships) {
if (!memberships || memberships.length === 0) {
return 'user';
}
let highestRole = 'user';
for (const { config, roles } of memberships) {
let role = 'user';
if (roles.includes(config.adminRoleId)) {
role = 'superadmin';
} else if (roles.includes(config.modRoleId)) {
role = 'moderator';
}
if (ROLE_PRIORITY[role] > ROLE_PRIORITY[highestRole]) {
highestRole = role;
}
}
return highestRole;
}
// Legacy-Funktion für Kompatibilität
export function getUserRole(memberRoles) {
const adminRoleId = process.env.DISCORD_ADMIN_ROLE_ID;
const modRoleId = process.env.DISCORD_MOD_ROLE_ID;
const adminRoleId = process.env.DISCORD_ADMIN_ROLE_ID || process.env.DISCORD_ADMIN_ROLE_ID_1;
const modRoleId = process.env.DISCORD_MOD_ROLE_ID || process.env.DISCORD_MOD_ROLE_ID_1;
if (memberRoles.includes(adminRoleId)) {
return 'superadmin';