Timer-App unter Versionsverwaltung stellen
Stand der laufenden Anwendung von 192.168.1.5:/opt/timer-app (systemd-Dienst timer-app, Port 3003). Zeiterfassung mit Kostenberechnung: Node/Express, SQLite, Anmeldung ausschliesslich ueber Authentik (OIDC, PKCE, RP-Logout) - der frueher vorhandene Passwort-Login ist entfernt. Nicht im Repo, bewusst: - backend/.env und die .env-Sicherung: enthalten Session-Secret und die Authentik-Zugangsdaten. Vorlage ist backend/.env.example. - data/: die Datenbank mit den echten Zeiterfassungen. - node_modules/. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+107
@@ -0,0 +1,107 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// Authentik-Anbindung (OpenID Connect) — gleiches Muster wie das VVL-Tool.
|
||||
//
|
||||
// openid-client v6 ist ESM-only, diese App ist CommonJS. Deshalb wird das
|
||||
// Modul einmalig per dynamischem import() geladen und zwischengespeichert.
|
||||
// Ist OIDC nicht konfiguriert (Variablen fehlen), bleibt der Passwort-Login
|
||||
// aktiv und diese Funktionen melden "nicht bereit".
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const ISSUER = process.env.OIDC_ISSUER || '';
|
||||
const CLIENT_ID = process.env.OIDC_CLIENT_ID || '';
|
||||
const CLIENT_SECRET = process.env.OIDC_CLIENT_SECRET || '';
|
||||
const REDIRECT_URI = process.env.OIDC_REDIRECT_URI || '';
|
||||
const ALLOW_INSECURE = String(process.env.OIDC_ALLOW_INSECURE || '') === 'true';
|
||||
const SCOPE = 'openid profile email';
|
||||
|
||||
let oidc = null; // das openid-client-Modul (via dynamic import)
|
||||
let oidcConfig = null; // Ergebnis der Discovery
|
||||
|
||||
function isConfigured() {
|
||||
return Boolean(ISSUER && CLIENT_ID && CLIENT_SECRET && REDIRECT_URI);
|
||||
}
|
||||
|
||||
function isReady() {
|
||||
return oidcConfig !== null;
|
||||
}
|
||||
|
||||
/** Discovery beim Serverstart. Wirft nicht — der Server startet auch, wenn
|
||||
* Authentik gerade nicht erreichbar ist (Login schlaegt dann fehl). */
|
||||
async function init() {
|
||||
if (!isConfigured()) {
|
||||
console.warn('[oidc] Authentik nicht konfiguriert (OIDC_ISSUER/CLIENT_ID/CLIENT_SECRET/REDIRECT_URI) — nur Passwort-Login aktiv.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
oidc = await import('openid-client');
|
||||
oidcConfig = await oidc.discovery(
|
||||
new URL(ISSUER),
|
||||
CLIENT_ID,
|
||||
CLIENT_SECRET,
|
||||
undefined,
|
||||
ALLOW_INSECURE ? { execute: [oidc.allowInsecureRequests] } : undefined
|
||||
);
|
||||
console.log('[oidc] Discovery erfolgreich:', ISSUER);
|
||||
} catch (err) {
|
||||
console.error('[oidc] Discovery fehlgeschlagen:', err.message || err);
|
||||
}
|
||||
}
|
||||
|
||||
/** Baut die Weiterleitungs-URL zu Authentik (PKCE). */
|
||||
async function buildLoginUrl() {
|
||||
if (!oidcConfig) throw new Error('OIDC nicht initialisiert');
|
||||
const codeVerifier = oidc.randomPKCECodeVerifier();
|
||||
const codeChallenge = await oidc.calculatePKCECodeChallenge(codeVerifier);
|
||||
const state = oidc.randomState();
|
||||
const url = oidc.buildAuthorizationUrl(oidcConfig, {
|
||||
redirect_uri: REDIRECT_URI,
|
||||
scope: SCOPE,
|
||||
code_challenge: codeChallenge,
|
||||
code_challenge_method: 'S256',
|
||||
state,
|
||||
});
|
||||
return { url: url.href, codeVerifier, state };
|
||||
}
|
||||
|
||||
/** Tauscht den Code gegen Tokens und liefert die Nutzer-Claims. */
|
||||
async function handleCallback(currentUrl, codeVerifier, state) {
|
||||
if (!oidcConfig) throw new Error('OIDC nicht initialisiert');
|
||||
const tokens = await oidc.authorizationCodeGrant(oidcConfig, currentUrl, {
|
||||
pkceCodeVerifier: codeVerifier,
|
||||
expectedState: state,
|
||||
});
|
||||
const claims = tokens.claims();
|
||||
if (!claims || !claims.sub) throw new Error('ID-Token ohne sub-Claim');
|
||||
return {
|
||||
user: {
|
||||
sub: claims.sub,
|
||||
username: claims.preferred_username || claims.sub,
|
||||
displayName: claims.name || claims.preferred_username || null,
|
||||
email: claims.email || null,
|
||||
},
|
||||
idToken: tokens.id_token || '',
|
||||
};
|
||||
}
|
||||
|
||||
/** RP-Initiated Logout: beendet auch die Authentik-Sitzung. Liefert null,
|
||||
* wenn kein end_session_endpoint vorhanden oder OIDC nicht bereit ist. */
|
||||
function buildLogoutUrl(idToken) {
|
||||
if (!oidcConfig) return null;
|
||||
try {
|
||||
const params = { post_logout_redirect_uri: new URL(REDIRECT_URI).origin + '/' };
|
||||
if (idToken) params.id_token_hint = idToken;
|
||||
return oidc.buildEndSessionUrl(oidcConfig, params).href;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init,
|
||||
isReady,
|
||||
isConfigured,
|
||||
buildLoginUrl,
|
||||
handleCallback,
|
||||
buildLogoutUrl,
|
||||
REDIRECT_URI,
|
||||
};
|
||||
Reference in New Issue
Block a user