Passwort-Komplexität + Portal-Credentials-UX
validatePasswordComplexity (12 Zeichen, Groß/Klein/Zahl/Sonderzeichen) zentral in passwordGenerator.ts; jetzt erzwungen in setPortalPassword, confirmPasswordReset, register, createUser, updateUser. Neue Endpoints: - POST /customers/:id/portal/password/generate → 16-Zeichen Zufallspasswort - POST /customers/:id/portal/send-credentials → Versand per Mail (nur wenn portalEnabled aktiv) Frontend (CustomerDetail): Generate-Button vor Setzen, Send-Credentials nach gesetztem Passwort, Live-Komplexitäts-Hint (✓/○) während Eingabe, alert() durch Toast-Notifications ersetzt. Live-verifiziert: schwaches Passwort → 400 mit Detail-Fehler, komplexes Passwort → 200, Generator liefert 16-Zeichen-Passwort. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -513,6 +513,80 @@ function getPublicUrl(): string {
|
||||
return process.env.PUBLIC_URL || 'http://localhost:5173';
|
||||
}
|
||||
|
||||
/**
|
||||
* Portal-Zugangsdaten per E-Mail an den Kunden versenden. Nur durch Admin-
|
||||
* UI ausgelöst – nie automatisch –, weil das Klartext-Passwort im Mail-
|
||||
* Body steht. Login-URL zeigt auf das `/portal/login`-Frontend-Route.
|
||||
*/
|
||||
export async function sendPortalCredentialsEmail(params: {
|
||||
to: string;
|
||||
customer: { firstName: string | null; lastName: string | null; salutation: string | null; companyName: string | null };
|
||||
loginEmail: string;
|
||||
password: string;
|
||||
}): Promise<void> {
|
||||
const systemEmail = await getSystemEmailCredentials();
|
||||
if (!systemEmail) {
|
||||
throw new Error('Kein System-E-Mail-Konto konfiguriert (Einstellungen → E-Mail-Provider)');
|
||||
}
|
||||
|
||||
const credentials: SmtpCredentials = {
|
||||
host: systemEmail.smtpServer,
|
||||
port: systemEmail.smtpPort,
|
||||
user: systemEmail.emailAddress,
|
||||
password: systemEmail.password,
|
||||
encryption: systemEmail.smtpEncryption,
|
||||
allowSelfSignedCerts: systemEmail.allowSelfSignedCerts,
|
||||
};
|
||||
|
||||
const loginUrl = `${getPublicUrl()}/portal/login`;
|
||||
const name = params.customer.companyName?.trim()
|
||||
|| `${params.customer.firstName || ''} ${params.customer.lastName || ''}`.trim()
|
||||
|| 'Kunde';
|
||||
|
||||
// HTML-Escape – Customer-Namen können theoretisch Sonderzeichen enthalten,
|
||||
// die wir nicht ungefiltert in die Mail rendern wollen.
|
||||
const esc = (s: string) =>
|
||||
s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
|
||||
const html = `
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<h2 style="color: #1e40af;">Ihre Zugangsdaten zum Kundenportal</h2>
|
||||
<p>Hallo ${esc(name)},</p>
|
||||
<p>anbei Ihre Zugangsdaten zum Kundenportal:</p>
|
||||
<table style="border-collapse: collapse; margin: 16px 0;">
|
||||
<tr><td style="padding: 6px 12px; color: #6b7280;">Login-URL:</td>
|
||||
<td style="padding: 6px 12px;"><a href="${loginUrl}">${esc(loginUrl)}</a></td></tr>
|
||||
<tr><td style="padding: 6px 12px; color: #6b7280;">E-Mail:</td>
|
||||
<td style="padding: 6px 12px; font-family: monospace;">${esc(params.loginEmail)}</td></tr>
|
||||
<tr><td style="padding: 6px 12px; color: #6b7280;">Passwort:</td>
|
||||
<td style="padding: 6px 12px; font-family: monospace;">${esc(params.password)}</td></tr>
|
||||
</table>
|
||||
<p style="color: #6b7280; font-size: 14px;">
|
||||
Bitte ändern Sie Ihr Passwort nach dem ersten Login (im Portal unter „Mein Konto").
|
||||
</p>
|
||||
<hr style="border: none; border-top: 1px solid #e5e7eb; margin: 24px 0;">
|
||||
<p style="color: #9ca3af; font-size: 12px;">
|
||||
Diese Nachricht enthält sensible Zugangsdaten – bitte sicher verwahren oder nach
|
||||
dem Login löschen.
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
await sendEmail(
|
||||
credentials,
|
||||
systemEmail.emailAddress,
|
||||
{
|
||||
to: params.to,
|
||||
subject: 'Ihre Zugangsdaten zum Kundenportal',
|
||||
html,
|
||||
},
|
||||
{
|
||||
context: 'portal-credentials',
|
||||
triggeredBy: 'admin-action',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passwort-Reset-Link per Email senden.
|
||||
* Findet User/Customer per Email. Wirft keinen Error wenn nicht gefunden
|
||||
|
||||
Reference in New Issue
Block a user