Abmelden beendet auch die Authentik-Sitzung und springt zurück ins Tool

- Kopfleiste meldet immer über das Portal ab, auch ohne Portal-Sitzung,
  und gibt die aktuelle Seite als returnTo mit.
- Portal merkt sich den Rücksprung über den Umweg zu Authentik.
- Setup-Skript setzt den Abmelde-Ablauf des Providers 'Portal' auf
  default-invalidation-flow (der Provider-Ablauf meldet nur bei der App ab).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kerim AtilganandClaude Opus 5.5 committed 2026-10-26 09:55:42 +01:00
1 parent c674fce196
commit edc64f159a
5 files changed
+59 -8

No files matched your search

+15 -1
View File
@@ -7,7 +7,7 @@ import helmet from 'helmet';
import { assertConfig, config } from './config.js';
import { initOidc } from './oidc.js';
import { SqliteStore } from './sessionStore.js';
import { authRouter } from './routes/auth.js';
import { AFTER_LOGOUT_COOKIE, authRouter, safeAfterLogout } from './routes/auth.js';
import { apiRouter } from './routes/api.js';
assertConfig();
@@ -68,8 +68,22 @@ app.get('/topbar.js', (_req, res) => {
});
app.use(express.static(publicDir, { index: false, maxAge: '1h' }));
function readCookie(req: Request, name: string): string | undefined {
for (const part of (req.headers.cookie ?? '').split(';')) {
const [key, ...value] = part.trim().split('=');
if (key === name) return decodeURIComponent(value.join('='));
}
return undefined;
}
// Portal-Seite: nur für angemeldete Benutzer, sonst direkt zu Authentik.
app.get('*', (req, res) => {
// Authentik schickt nach dem Abmelden auf "/" zurück; von dort zurück ins Tool, in dem abgemeldet wurde.
const afterLogout = safeAfterLogout(readCookie(req, AFTER_LOGOUT_COOKIE));
if (afterLogout && req.path === '/' && !req.session.user) {
res.clearCookie(AFTER_LOGOUT_COOKIE);
return res.redirect(afterLogout);
}
if (!req.session.user) return res.redirect(`/auth/login?returnTo=${encodeURIComponent(req.originalUrl)}`);
res.setHeader('Cache-Control', 'no-store');
res.sendFile(path.join(publicDir, 'index.html'));
+18
View File
@@ -53,11 +53,29 @@ authRouter.get('/callback', async (req, res) => {
}
});
/** Merkt sich über den Umweg zu Authentik, wohin es nach dem Abmelden zurückgeht (siehe index.ts). */
export const AFTER_LOGOUT_COOKIE = 'hi.portal.nach_abmelden';
export function safeAfterLogout(raw: unknown): string | null {
const target = safeReturnTo(raw);
return target === '/' ? null : target;
}
// Beendet Portal- und Authentik-Sitzung, auch wenn die Portal-Sitzung schon abgelaufen ist.
authRouter.get('/logout', async (req, res, next) => {
try {
const url = await buildLogoutUrl(req.session.idToken);
const returnTo = safeAfterLogout(req.query.returnTo);
req.session.destroy(() => {
res.clearCookie('hi.portal.sid');
if (returnTo) {
res.cookie(AFTER_LOGOUT_COOKIE, returnTo, {
httpOnly: true,
sameSite: 'lax',
secure: config.isProduction,
maxAge: 5 * 60 * 1000,
});
}
res.redirect(url);
});
} catch (err) {