Login über beide Domains reparieren, Hub-Link auf der Bot-Seite korrigieren
Zwei Fehler aus dem ersten Praxistest: 1. "Ungültiger OAuth-State" beim Anmelden auf dem Hub. Der Login startete auf hub.d4rkst3r.de, aber die Rücksprung-Adresse für Discord war fest auf die öffentliche URL gesetzt — Discord schickte also zur Bot-Domain zurück. Das Schutz-Cookie gegen Sitzungsübernahme gilt aber nur für die Domain, auf der es gesetzt wurde, und war dort nicht lesbar. Jetzt bleibt der ganze Ablauf auf der Domain, von der er gestartet ist. Damit entfällt auch der Umweg über ein zusätzliches Cookie, das sich die Ursprungsseite merken sollte. 2. Der Link "zum Community-Hub" auf der Bot-Seite zeigte auf die Bot-Seite selbst — er benutzte die alte öffentliche URL statt der Hub-Adresse. /api/legal liefert jetzt beide Adressen getrennt; die Verweise zwischen den Seiten erscheinen nur, wenn die Domains wirklich verschieden sind. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,7 @@ export default function BotApp() {
|
||||
apiGet('/api/me')
|
||||
.then((data) => setMe({ scopes: [], ...data, loading: false }))
|
||||
.catch(() => setMe({ user: null, admin: false, scopes: [], loading: false }));
|
||||
apiGet('/api/legal').then((d) => setHub(d.publicUrl)).catch(() => {});
|
||||
apiGet('/api/legal').then((d) => setHub(d.split ? d.hubUrl : null)).catch(() => {});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -25,7 +25,8 @@ export default function BotLanding() {
|
||||
const [hub, setHub] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
apiGet('/api/legal').then((d) => setHub(d)).catch(() => {});
|
||||
// Nur verlinken, wenn der Hub wirklich woanders liegt
|
||||
apiGet('/api/legal').then((d) => setHub(d.split ? d.hubUrl : null)).catch(() => {});
|
||||
apiGet('/api/serverstats').then(setStatus).catch(() => {});
|
||||
window.scrollTo(0, 0);
|
||||
}, []);
|
||||
@@ -106,11 +107,11 @@ export default function BotLanding() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hub?.publicUrl && (
|
||||
{hub && (
|
||||
<p className="notice" style={{ marginTop: '2rem' }}>
|
||||
Der Bot läuft für die D4RKST3R-Community
|
||||
{status?.members ? ` mit ${status.members} Mitgliedern` : ''} —
|
||||
{' '}<a href={hub.publicUrl} style={{ textDecoration: 'underline' }}>zum Community-Hub</a>.
|
||||
{' '}<a href={hub} style={{ textDecoration: 'underline' }}>zum Community-Hub</a>.
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
+5
-1
@@ -27,7 +27,7 @@ import { publishRoleMenu, unpublishRoleMenu, MAX_ENTRIES } from '../bot/role-men
|
||||
import { computeNextRun } from './scheduled-posts.js';
|
||||
import { config } from '../config.js';
|
||||
import { removeDevlog } from '../bot/devlog-archive.js';
|
||||
import { commitChannelId, devlogChannelId, releaseChannelId, devlogPingRoleId, publicUrl, roadmapRepo, brandColor, brandFooter, brandName, giteaApiToken, votingChannelId, discordGuildId, discordInviteUrl, legalInfo } from '../runtime-settings.js';
|
||||
import { commitChannelId, devlogChannelId, releaseChannelId, devlogPingRoleId, publicUrl, roadmapRepo, brandColor, brandFooter, brandName, giteaApiToken, votingChannelId, discordGuildId, discordInviteUrl, legalInfo, hubUrl, botUrl } from '../runtime-settings.js';
|
||||
import { xpForLevel } from '../bot/levels.js';
|
||||
import { getMilestones } from '../gitea-api.js';
|
||||
import { getSessionUser, isAdmin, isGuildMember } from './auth.js';
|
||||
@@ -1261,6 +1261,10 @@ ${rssItems}
|
||||
legal: legalInfo(),
|
||||
brand: brandName(),
|
||||
publicUrl: publicUrl(),
|
||||
// Für Querverweise zwischen den beiden Seiten
|
||||
hubUrl: hubUrl(),
|
||||
botUrl: botUrl(),
|
||||
split: hubUrl() !== botUrl(),
|
||||
guildConfigured: Boolean(discordGuildId()),
|
||||
}));
|
||||
|
||||
|
||||
+22
-27
@@ -6,10 +6,23 @@ import { publicUrl, discordGuildId, memberGateEnabled, cookieDomain, hubUrl, bot
|
||||
const DISCORD_API = 'https://discord.com/api/v10';
|
||||
const SESSION_COOKIE = 'd4rkbot_session';
|
||||
const STATE_COOKIE = 'd4rkbot_oauth_state';
|
||||
const ORIGIN_COOKIE = 'd4rkbot_origin';
|
||||
|
||||
// Dynamisch, damit die Setup-Seite die URL ändern kann (Redirect auch im Dev-Portal eintragen!)
|
||||
const redirectUri = () => `${publicUrl()}/auth/callback`;
|
||||
/**
|
||||
* Rücksprung-Adresse für Discord — bleibt auf der Domain, von der der Login
|
||||
* gestartet wurde. Das ist wichtig, weil das Schutz-Cookie gegen
|
||||
* Sitzungsübernahme nur für diese eine Domain gilt: Würde Discord auf eine
|
||||
* andere Adresse zurückschicken, wäre es dort nicht lesbar und der Login
|
||||
* schlüge mit „Ungültiger OAuth-State" fehl.
|
||||
*
|
||||
* Jede hier mögliche Adresse muss im Discord-Portal als Redirect eingetragen sein.
|
||||
*/
|
||||
function redirectUri(request) {
|
||||
const host = String(request?.headers?.host ?? '').toLowerCase().split(':')[0];
|
||||
const known = [hubUrl(), botUrl(), publicUrl()].find((u) => {
|
||||
try { return new URL(u).hostname === host; } catch { return false; }
|
||||
});
|
||||
return `${known ?? publicUrl()}/auth/callback`;
|
||||
}
|
||||
|
||||
/** Eingeloggten User aus dem signierten Session-Cookie lesen (null wenn nicht eingeloggt) */
|
||||
export function getSessionUser(request) {
|
||||
@@ -46,25 +59,16 @@ export function registerAuthRoutes(app, client) {
|
||||
const state = crypto.randomBytes(16).toString('hex');
|
||||
const params = new URLSearchParams({
|
||||
client_id: config.discordClientId,
|
||||
redirect_uri: redirectUri(),
|
||||
redirect_uri: redirectUri(request),
|
||||
response_type: 'code',
|
||||
scope: 'identify',
|
||||
state,
|
||||
});
|
||||
// Bei getrennten Domains merken, von welcher Seite der Login kam —
|
||||
// Discord schickt immer zur selben Rücksprung-Adresse zurück.
|
||||
const host = String(request.headers.host ?? '').toLowerCase().split(':')[0];
|
||||
const origin = [hubUrl(), botUrl()].find((u) => {
|
||||
try { return new URL(u).hostname === host; } catch { return false; }
|
||||
});
|
||||
|
||||
return reply
|
||||
.setCookie(STATE_COOKIE, state, {
|
||||
path: '/auth', httpOnly: true, sameSite: 'lax', maxAge: 600, signed: true,
|
||||
})
|
||||
.setCookie(ORIGIN_COOKIE, origin ?? '', {
|
||||
path: '/', httpOnly: true, sameSite: 'lax', maxAge: 600, signed: true,
|
||||
})
|
||||
.redirect(`https://discord.com/oauth2/authorize?${params}`);
|
||||
});
|
||||
|
||||
@@ -86,7 +90,8 @@ export function registerAuthRoutes(app, client) {
|
||||
client_secret: config.discordClientSecret,
|
||||
grant_type: 'authorization_code',
|
||||
code,
|
||||
redirect_uri: redirectUri(),
|
||||
// Muss exakt dieselbe Adresse sein wie beim Weiterleiten
|
||||
redirect_uri: redirectUri(request),
|
||||
}),
|
||||
});
|
||||
if (!tokenRes.ok) {
|
||||
@@ -120,28 +125,18 @@ export function registerAuthRoutes(app, client) {
|
||||
const { takeReturnTo } = await import('./sso.js');
|
||||
const returnTo = takeReturnTo(request, reply);
|
||||
|
||||
// Sonst zurück zu der Seite, von der der Login gestartet wurde
|
||||
let origin = null;
|
||||
const rawOrigin = request.cookies[ORIGIN_COOKIE];
|
||||
if (rawOrigin) {
|
||||
const unsigned = request.unsignCookie(rawOrigin);
|
||||
// Nur die beiden bekannten Adressen zulassen
|
||||
if (unsigned.valid && [hubUrl(), botUrl()].includes(unsigned.value)) {
|
||||
origin = unsigned.value;
|
||||
}
|
||||
}
|
||||
|
||||
// Mit gesetzter Cookie-Domain (z. B. .d4rkst3r.de) gilt die Anmeldung
|
||||
// auf Hub und Bot-Seite gleichzeitig
|
||||
const domain = cookieDomain();
|
||||
return reply
|
||||
.clearCookie(STATE_COOKIE, { path: '/auth' })
|
||||
.clearCookie(ORIGIN_COOKIE, { path: '/' })
|
||||
.setCookie(SESSION_COOKIE, JSON.stringify(session), {
|
||||
path: '/', httpOnly: true, sameSite: 'lax', maxAge: 7 * 24 * 3600, signed: true,
|
||||
...(domain ? { domain } : {}),
|
||||
})
|
||||
.redirect(returnTo ?? (origin ? `${origin}/` : '/'));
|
||||
// Der Callback läuft auf derselben Domain wie der Login — ein
|
||||
// relativer Pfad führt also automatisch zurück zur richtigen Seite
|
||||
.redirect(returnTo ?? '/');
|
||||
});
|
||||
|
||||
app.get('/auth/logout', async (request, reply) => {
|
||||
|
||||
Reference in New Issue
Block a user