- Portal mit Kacheln aus Authentik (pro Benutzer gefiltert) und "Aktuelles" - Web-Component <hi-topbar> als zentraler App-Umschalter für alle Tools - OIDC-Login über Authentik, zentrales Abmelden - Setup-Skript für Authentik (Gruppen-Bindungen, Portal-App, Service-Account) - Deployment auf den Portal-CT per systemd Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
67 lines
2.2 KiB
TypeScript
67 lines
2.2 KiB
TypeScript
import { Router } from 'express';
|
|
import { config, isAllowedUrl } from '../config.js';
|
|
import { buildLoginUrl, buildLogoutUrl, handleCallback } from '../oidc.js';
|
|
|
|
export const authRouter = Router();
|
|
|
|
/** Rücksprung nur auf das Portal selbst oder erlaubte Tool-Domains. */
|
|
function safeReturnTo(raw: unknown): string {
|
|
if (typeof raw !== 'string' || !raw) return '/';
|
|
if (raw.startsWith('/') && !raw.startsWith('//')) return raw;
|
|
return isAllowedUrl(raw) ? raw : '/';
|
|
}
|
|
|
|
authRouter.get('/login', async (req, res, next) => {
|
|
try {
|
|
const returnTo = safeReturnTo(req.query.returnTo);
|
|
if (config.devUser) {
|
|
req.session.user = {
|
|
sub: `dev-${config.devUser}`,
|
|
username: config.devUser,
|
|
name: config.devUser.charAt(0).toUpperCase() + config.devUser.slice(1),
|
|
email: `${config.devUser}@example.invalid`,
|
|
groups: ['HI-Mitarbeiter', 'HI-Admin'],
|
|
};
|
|
return res.redirect(returnTo);
|
|
}
|
|
const { url, state, codeVerifier } = await buildLoginUrl();
|
|
req.session.login = { state, codeVerifier, returnTo };
|
|
req.session.save((err) => (err ? next(err) : res.redirect(url)));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
});
|
|
|
|
authRouter.get('/callback', async (req, res) => {
|
|
const pending = req.session.login;
|
|
if (!pending) return res.redirect('/auth/login');
|
|
try {
|
|
const query = req.originalUrl.slice(req.originalUrl.indexOf('?'));
|
|
const { user, idToken } = await handleCallback(query, pending);
|
|
const returnTo = pending.returnTo;
|
|
// Neue Session-ID nach dem Login (Schutz vor Session Fixation).
|
|
req.session.regenerate((err) => {
|
|
if (err) return res.status(500).send('Anmeldung fehlgeschlagen.');
|
|
req.session.user = user;
|
|
req.session.idToken = idToken;
|
|
req.session.save(() => res.redirect(returnTo));
|
|
});
|
|
} catch (err) {
|
|
console.error('[auth] Callback fehlgeschlagen:', err);
|
|
delete req.session.login;
|
|
res.status(401).send('Anmeldung fehlgeschlagen. <a href="/auth/login">Erneut versuchen</a>');
|
|
}
|
|
});
|
|
|
|
authRouter.get('/logout', async (req, res, next) => {
|
|
try {
|
|
const url = await buildLogoutUrl(req.session.idToken);
|
|
req.session.destroy(() => {
|
|
res.clearCookie('hi.portal.sid');
|
|
res.redirect(url);
|
|
});
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
});
|