Portal-Login-URL als App-Setting (statt nur PUBLIC_URL-Env)
Bugfix: in der "Zugangsdaten versenden"-Mail stand bisher http://localhost:5173/portal/login als Login-Link, wenn die PUBLIC_URL-Env nicht gesetzt war – Kunden klickten auf einen toten Link. Neue Einstellung "portalLoginUrl" unter Einstellungen → Kundenportal. Wenn gepflegt, wird sie als Basis-URL für: - Portal-Zugangsdaten-Mail (Login-Link) - Passwort-Reset-Link verwendet. Reihenfolge: AppSetting → PUBLIC_URL-Env → localhost-Default. Backend: getPublicUrl() jetzt async, liest erst aus AppSetting, fällt auf Env zurück. Trailing-Slash-Bereinigung im Backend (damit Links nicht doppelt-Slash bekommen) und im Frontend (damit der gespeicherte Wert sauber ist). Frontend: neue Card "Portal-Login-URL" oberhalb der Support- Anfragen-Card in PortalSettings.tsx. Input + Save-Button + http(s)://-Schema-Validierung + Erfolgs-Toast. Live-verifiziert: PUT setzt 'https://crm.beispiel.de', Backend- getPublicUrl liefert 'https://crm.beispiel.de/portal/login' statt localhost. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,10 @@ export const ALLOWED_SETTING_KEYS: ReadonlySet<string> = new Set([
|
||||
'monitoringLastDigestAt',
|
||||
'companyName',
|
||||
'defaultEmailDomain',
|
||||
// Basis-URL für an Kunden verschickte Portal-Links (Login + Passwort-Reset).
|
||||
// Vorher kam aus `PUBLIC_URL`-Env, default localhost – Mails enthielten
|
||||
// dann unklickbare Links. Wird in Settings → Kundenportal gepflegt.
|
||||
'portalLoginUrl',
|
||||
]);
|
||||
|
||||
export function isAllowedSettingKey(key: string): boolean {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { encrypt, decrypt } from '../utils/encryption.js';
|
||||
import { sendEmail, SmtpCredentials } from './smtpService.js';
|
||||
import { getSystemEmailCredentials } from './emailProvider/emailProviderService.js';
|
||||
import { getAuthorizedCustomerIds } from './authorization.service.js';
|
||||
import * as appSettingService from './appSetting.service.js';
|
||||
|
||||
// Token-Lifetimes
|
||||
// - Access-Token: kurzlebig, nur im Browser-Memory → XSS klaut max. 15 min
|
||||
@@ -581,8 +582,23 @@ function generateResetToken(): string {
|
||||
return crypto.randomBytes(32).toString('hex');
|
||||
}
|
||||
|
||||
function getPublicUrl(): string {
|
||||
return process.env.PUBLIC_URL || 'http://localhost:5173';
|
||||
/**
|
||||
* Liefert die Basis-URL für an Kunden verschickte Links (Portal-Login,
|
||||
* Passwort-Reset). Reihenfolge:
|
||||
* 1. `portalLoginUrl` aus AppSettings (vom Admin in Settings → Kundenportal
|
||||
* gepflegt). Wenn HTTPS-Domain hier eingetragen, wird die in Mails
|
||||
* verwendet, nicht der Localhost-Default.
|
||||
* 2. `PUBLIC_URL`-Env (für Setups ohne Admin-UI-Konfiguration).
|
||||
* 3. Fallback `http://localhost:5173` (Dev-Default).
|
||||
* Hat Trailing-Slash-Bereinigung, sonst kommen Links wie
|
||||
* `https://crm.de//portal/login` zustande.
|
||||
*/
|
||||
async function getPublicUrl(): Promise<string> {
|
||||
const fromSettings = await appSettingService.getSetting('portalLoginUrl');
|
||||
const raw = (fromSettings && fromSettings.trim())
|
||||
|| process.env.PUBLIC_URL
|
||||
|| 'http://localhost:5173';
|
||||
return raw.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -610,7 +626,7 @@ export async function sendPortalCredentialsEmail(params: {
|
||||
allowSelfSignedCerts: systemEmail.allowSelfSignedCerts,
|
||||
};
|
||||
|
||||
const loginUrl = `${getPublicUrl()}/portal/login`;
|
||||
const loginUrl = `${await getPublicUrl()}/portal/login`;
|
||||
const name = params.customer.companyName?.trim()
|
||||
|| `${params.customer.firstName || ''} ${params.customer.lastName || ''}`.trim()
|
||||
|| 'Kunde';
|
||||
@@ -709,7 +725,7 @@ export async function requestPasswordReset(email: string, userType: 'admin' | 'p
|
||||
if (!recipient) return;
|
||||
|
||||
// Reset-Link + Email senden
|
||||
const resetUrl = `${getPublicUrl()}/password-reset?token=${token}&type=${userType}`;
|
||||
const resetUrl = `${await getPublicUrl()}/password-reset?token=${token}&type=${userType}`;
|
||||
const systemEmail = await getSystemEmailCredentials();
|
||||
|
||||
if (!systemEmail) {
|
||||
|
||||
Reference in New Issue
Block a user