feat(auth): Client-Secret in den Einstellungen pflegen, Warnung vor Ablauf
Das Secret der App-Registrierung laeuft in Entra ab. Bisher hiess das: jemand
mit SSH-Zugang aendert die .env und startet den Dienst neu - und niemand sieht
vorher, wann es soweit ist. Laeuft es aus, stehen Anmeldung UND
Postfachabgleich still, denn beide haengen an derselben Registrierung.
Neu unter Einstellungen > Anmeldung: Secret eintragen, Ablaufdatum hinterlegen,
Restlaufzeit ablesen. Ab 30 Tagen vor dem Ablauf bekommen Administratoren ueber
jeder Ansicht ein Hinweisband, das direkt dorthin fuehrt; nach dem Ablauf wird
es rot. Der Hinweis reist mit /api/badges mit, das die App ohnehin bei jedem
Laden abruft.
Bewusst so entschieden:
- Das Secret muss dafuer in die Datenbank, entgegen der bisherigen Regel
"keine Geheimnisse in crm_settings". Es liegt deshalb AES-256-GCM-
verschluesselt dort; der Schluessel kommt aus CRM_ENCRYPTION_KEY, ersatz-
weise JWT_SECRET, und steht damit nie im selben Dump.
- Vor dem Speichern holt das CRM mit dem eingegebenen Wert ein Token bei
Microsoft. Ein Tippfehler wird gar nicht erst uebernommen.
- Gelesen wird Datenbank vor .env; der .env-Wert bleibt Rueckfallebene, und
"Hinterlegtes Secret verwerfen" schaltet dorthin zurueck.
- Passt der Schluessel nicht mehr, meldet die Oberflaeche das ausdruecklich,
statt still auf die .env zurueckzufallen - sonst sucht man den Fehler an
der falschen Stelle.
- Das Ablaufdatum wird von Hand eingetragen. Aus Entra lesen braeuchte
Application.Read.All, die die App bewusst nicht hat.
MAIL_CONFIGURED und SSO_CONFIGURED sind dadurch von Konstanten zu den
Funktionen mailConfigured() und ssoConfigured() geworden - die Pruefung muss
jetzt in die Datenbank sehen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e271f5c1e8
commit
c8eaaa1947
@@ -12,9 +12,10 @@
|
||||
* Client-ID zu sehen; er kennt nur /api/auth/sso/start.
|
||||
*/
|
||||
|
||||
import { hasSecret, requireSecret } from '../msSecret.js';
|
||||
|
||||
export const TENANT_ID = (process.env.MS_TENANT_ID || '').trim();
|
||||
export const CLIENT_ID = (process.env.MS_CLIENT_ID || '').trim();
|
||||
const CLIENT_SECRET = (process.env.MS_CLIENT_SECRET || '').trim();
|
||||
|
||||
/** Hauptschalter. Ohne ausdrueckliches "true" bleibt die Anmeldung per Microsoft aus. */
|
||||
const ENABLED = process.env.MS_SSO_ENABLED === 'true';
|
||||
@@ -40,24 +41,27 @@ export const GRAPH_ROOT = 'https://graph.microsoft.com/v1.0';
|
||||
/** Angefragte delegierte Rechte. Alle vier sind in Entra bereits zugestimmt. */
|
||||
export const SCOPES = ['openid', 'profile', 'email', 'offline_access', 'User.Read'];
|
||||
|
||||
/** Ohne diese Werte ist die Anmeldung nicht betriebsbereit. */
|
||||
export const SSO_CONFIGURED = Boolean(ENABLED && TENANT_ID && CLIENT_ID && CLIENT_SECRET && REDIRECT_URI);
|
||||
|
||||
export function clientSecret() {
|
||||
if (!CLIENT_SECRET) throw new Error('MS_CLIENT_SECRET ist nicht gesetzt.');
|
||||
return CLIENT_SECRET;
|
||||
/*
|
||||
* Ohne diese Werte ist die Anmeldung nicht betriebsbereit. Asynchron, weil das
|
||||
* Secret auch aus der Datenbank kommen darf (msSecret.js).
|
||||
*/
|
||||
export async function ssoConfigured() {
|
||||
return Boolean(ENABLED && TENANT_ID && CLIENT_ID && REDIRECT_URI && (await hasSecret()));
|
||||
}
|
||||
|
||||
/** Das gueltige Client-Secret: Datenbank vor .env. */
|
||||
export const clientSecret = requireSecret;
|
||||
|
||||
/**
|
||||
* Was zum Betrieb fehlt - fuer eine brauchbare Meldung in den Einstellungen,
|
||||
* ohne Werte zu verraten.
|
||||
*/
|
||||
export function missingSsoConfig() {
|
||||
export async function missingSsoConfig() {
|
||||
const missing = [];
|
||||
if (!ENABLED) missing.push('MS_SSO_ENABLED');
|
||||
if (!TENANT_ID) missing.push('MS_TENANT_ID');
|
||||
if (!CLIENT_ID) missing.push('MS_CLIENT_ID');
|
||||
if (!CLIENT_SECRET) missing.push('MS_CLIENT_SECRET');
|
||||
if (!(await hasSecret())) missing.push('MS_CLIENT_SECRET');
|
||||
if (!REDIRECT_URI) missing.push('MS_SSO_REDIRECT_URI');
|
||||
return missing;
|
||||
}
|
||||
|
||||
@@ -123,10 +123,10 @@ async function postToken(body) {
|
||||
}
|
||||
|
||||
/** Tauscht den Code gegen Tokens. Der Verifier weist uns als Starter des Vorgangs aus. */
|
||||
export function exchangeCode(code, verifier) {
|
||||
export async function exchangeCode(code, verifier) {
|
||||
return postToken({
|
||||
client_id: CLIENT_ID,
|
||||
client_secret: clientSecret(),
|
||||
client_secret: await clientSecret(),
|
||||
grant_type: 'authorization_code',
|
||||
code,
|
||||
redirect_uri: REDIRECT_URI,
|
||||
|
||||
Reference in New Issue
Block a user