feat: add Steam OpenID login
All checks were successful
Build & Deploy / build (push) Successful in 47s
Build & Deploy / deploy (push) Successful in 4s
Build & Deploy / bump-version (push) Successful in 2s

- Add Steam OpenID 2.0 authentication routes (login + callback)
- Enable Steam button in LoginModal (was placeholder)
- Unified user ID system: getUserId() supports Discord, Steam, Admin
- Update soundboard user-sound endpoints for Steam users
- UserSettings now works for both Discord and Steam providers
- Steam hover uses brand color #66c0f4

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel 2026-03-10 22:18:37 +01:00
parent aa998c9b44
commit d135aab6dc
6 changed files with 162 additions and 38 deletions

View file

@ -17,7 +17,7 @@ import nacl from 'tweetnacl';
import { ChannelType, Events, type VoiceBasedChannel, type VoiceState, type Message } from 'discord.js';
import type { Plugin, PluginContext } from '../../core/plugin.js';
import { sseBroadcast } from '../../core/sse.js';
import { getSession } from '../../core/discord-auth.js';
import { getSession, getUserId } from '../../core/discord-auth.js';
// ── Config (env) ──
const SOUNDS_DIR = process.env.SOUNDS_DIR ?? '/data/sounds';
@ -1245,15 +1245,15 @@ const soundboardPlugin: Plugin = {
});
});
// ── User Sound Preferences (Discord-authenticated) ──
// ── User Sound Preferences (Discord / Steam authenticated) ──
// Get current user's entrance/exit sounds
app.get('/api/soundboard/user/sounds', (req, res) => {
const session = getSession(req);
if (!session?.discordId) {
const userId = session ? getUserId(session) : null;
if (!userId) {
res.status(401).json({ error: 'Nicht eingeloggt' });
return;
}
const userId = session.discordId;
const entrance = persistedState.entranceSounds?.[userId] ?? null;
const exit = persistedState.exitSounds?.[userId] ?? null;
res.json({ entrance, exit });
@ -1262,7 +1262,8 @@ const soundboardPlugin: Plugin = {
// Set entrance sound
app.post('/api/soundboard/user/entrance', (req, res) => {
const session = getSession(req);
if (!session?.discordId) { res.status(401).json({ error: 'Nicht eingeloggt' }); return; }
const userId = session ? getUserId(session) : null;
if (!userId) { res.status(401).json({ error: 'Nicht eingeloggt' }); return; }
const { fileName } = req.body ?? {};
if (!fileName || typeof fileName !== 'string') { res.status(400).json({ error: 'fileName erforderlich' }); return; }
if (!/\.(mp3|wav)$/i.test(fileName)) { res.status(400).json({ error: 'Nur .mp3 oder .wav' }); return; }
@ -1279,16 +1280,17 @@ const soundboardPlugin: Plugin = {
})();
if (!resolve) { res.status(404).json({ error: 'Datei nicht gefunden' }); return; }
persistedState.entranceSounds = persistedState.entranceSounds ?? {};
persistedState.entranceSounds[session.discordId] = resolve;
persistedState.entranceSounds[userId] = resolve;
writeState();
console.log(`[Soundboard] User ${session.username} (${session.discordId}) set entrance: ${resolve}`);
console.log(`[Soundboard] User ${session!.username} (${userId}) set entrance: ${resolve}`);
res.json({ ok: true, entrance: resolve });
});
// Set exit sound
app.post('/api/soundboard/user/exit', (req, res) => {
const session = getSession(req);
if (!session?.discordId) { res.status(401).json({ error: 'Nicht eingeloggt' }); return; }
const userId = session ? getUserId(session) : null;
if (!userId) { res.status(401).json({ error: 'Nicht eingeloggt' }); return; }
const { fileName } = req.body ?? {};
if (!fileName || typeof fileName !== 'string') { res.status(400).json({ error: 'fileName erforderlich' }); return; }
if (!/\.(mp3|wav)$/i.test(fileName)) { res.status(400).json({ error: 'Nur .mp3 oder .wav' }); return; }
@ -1304,33 +1306,35 @@ const soundboardPlugin: Plugin = {
})();
if (!resolve) { res.status(404).json({ error: 'Datei nicht gefunden' }); return; }
persistedState.exitSounds = persistedState.exitSounds ?? {};
persistedState.exitSounds[session.discordId] = resolve;
persistedState.exitSounds[userId] = resolve;
writeState();
console.log(`[Soundboard] User ${session.username} (${session.discordId}) set exit: ${resolve}`);
console.log(`[Soundboard] User ${session!.username} (${userId}) set exit: ${resolve}`);
res.json({ ok: true, exit: resolve });
});
// Remove entrance sound
app.delete('/api/soundboard/user/entrance', (req, res) => {
const session = getSession(req);
if (!session?.discordId) { res.status(401).json({ error: 'Nicht eingeloggt' }); return; }
const userId = session ? getUserId(session) : null;
if (!userId) { res.status(401).json({ error: 'Nicht eingeloggt' }); return; }
if (persistedState.entranceSounds) {
delete persistedState.entranceSounds[session.discordId];
delete persistedState.entranceSounds[userId];
writeState();
}
console.log(`[Soundboard] User ${session.username} (${session.discordId}) removed entrance sound`);
console.log(`[Soundboard] User ${session!.username} (${userId}) removed entrance sound`);
res.json({ ok: true });
});
// Remove exit sound
app.delete('/api/soundboard/user/exit', (req, res) => {
const session = getSession(req);
if (!session?.discordId) { res.status(401).json({ error: 'Nicht eingeloggt' }); return; }
const userId = session ? getUserId(session) : null;
if (!userId) { res.status(401).json({ error: 'Nicht eingeloggt' }); return; }
if (persistedState.exitSounds) {
delete persistedState.exitSounds[session.discordId];
delete persistedState.exitSounds[userId];
writeState();
}
console.log(`[Soundboard] User ${session.username} (${session.discordId}) removed exit sound`);
console.log(`[Soundboard] User ${session!.username} (${userId}) removed exit sound`);
res.json({ ok: true });
});