Absender Adresse bei SMTP

This commit is contained in:
Thomas Peterson 2026-06-25 14:55:37 +02:00
parent d9ec021f3c
commit 5588a52edf
7 changed files with 132 additions and 6 deletions

View File

@ -4,11 +4,11 @@ scheb_two_factor:
- Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken
- Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken
# E-Mail-Code-Verfahren (Code geht an das separate 2FA-Feld des Benutzers)
# E-Mail-Code-Verfahren (Code geht an das separate 2FA-Feld des Benutzers).
# Absender + SMTP kommen über den eigenen Mailer aus den Systemeinstellungen.
email:
enabled: true
sender_email: noreply@printshopcreator.de
sender_name: PrintshopCreator
mailer: PSC\System\SettingsBundle\Mailer\InstanceAuthCodeMailer
digits: 6
# TOTP / Authenticator-App

View File

@ -4,6 +4,7 @@ namespace PSC\Backend\DashboardBundle\Controller;
use Doctrine\ORM\EntityManagerInterface;
use PSC\Shop\EntityBundle\Entity\Contact;
use PSC\System\SettingsBundle\Service\Instance;
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Totp\TotpAuthenticatorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
@ -22,6 +23,7 @@ class TwoFactorController extends AbstractController
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly TotpAuthenticatorInterface $totpAuthenticator,
private readonly Instance $instanceService,
) {
}
@ -47,6 +49,7 @@ class TwoFactorController extends AbstractController
'totpEnabled' => $user->isTotpAuthenticationEnabled(),
'pendingSecret' => $pendingSecret,
'qrContent' => $qrContent,
'mailReady' => $this->instanceService->getInstance()->isMailReady(),
]);
}
@ -63,6 +66,14 @@ class TwoFactorController extends AbstractController
$user = $this->getUser();
$enabled = (bool) $request->request->get('enabled');
// E-Mail-2FA setzt vollständige System-SMTP-Einstellungen voraus.
if ($enabled && !$this->instanceService->getInstance()->isMailReady()) {
$this->addFlash('error', 'E-Mail-2FA ist nicht verfügbar: Es sind keine vollständigen SMTP-/Absender-Einstellungen in den Systemeinstellungen hinterlegt.');
return $this->redirectToRoute('psc_backend_2fa_settings');
}
$user->setTwoFactorEmail($request->request->get('two_factor_email'));
$user->setTwoFactorEmailEnabled($enabled);

View File

@ -17,19 +17,24 @@
<div class="rounded-sm border bg-white px-5 py-4 shadow-lg dark:border-strokedark dark:bg-boxdark">
<h2 class="text-lg font-medium mb-1">E-Mail-Code</h2>
<p class="text-sm text-gray-500 mb-4">Bei jedem Login wird ein Einmal-Code an die unten angegebene E-Mail-Adresse geschickt.</p>
{% if not mailReady %}
<div class="rounded bg-red-50 border border-red-200 text-red-700 text-sm px-3 py-2 mb-4">
E-Mail-2FA ist nicht verfügbar: Bitte zuerst SMTP-Zugangsdaten und eine Absender-E-Mail in den <strong>Systemeinstellungen</strong> hinterlegen.
</div>
{% endif %}
<form method="post" action="{{ path('psc_backend_2fa_email') }}" class="space-y-4">
<input type="hidden" name="_token" value="{{ csrf_token('2fa_email') }}">
<label class="flex items-center gap-2 text-sm">
<input type="checkbox" name="enabled" value="1" {{ emailEnabled ? 'checked' : '' }} class="rounded border-gray-300">
<input type="checkbox" name="enabled" value="1" {{ emailEnabled ? 'checked' : '' }} {{ not mailReady ? 'disabled' : '' }} class="rounded border-gray-300">
E-Mail-2FA aktivieren
</label>
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">E-Mail für den Sicherheitscode</label>
<input type="email" name="two_factor_email" value="{{ twoFactorEmail }}" placeholder="{{ loginEmail }}"
<input type="email" name="two_factor_email" value="{{ twoFactorEmail }}" placeholder="{{ loginEmail }}" {{ not mailReady ? 'disabled' : '' }}
class="w-full rounded-md border border-gray-300 p-2 text-sm">
<p class="text-xs text-gray-400 mt-1">Leer = Login-Adresse ({{ loginEmail }}) verwenden.</p>
</div>
<button type="submit" class="inline-flex items-center px-4 py-2 rounded-sm text-sm font-medium text-white bg-psc-500 hover:bg-psc-600 shadow-sm">Speichern</button>
<button type="submit" {{ not mailReady ? 'disabled' : '' }} class="inline-flex items-center px-4 py-2 rounded-sm text-sm font-medium text-white bg-psc-500 hover:bg-psc-600 shadow-sm">Speichern</button>
</form>
</div>

View File

@ -52,6 +52,12 @@ class Instance implements UserInterface
protected $smtpPassword;
#[Field(type: 'string')]
protected $smtpUsername;
/** Absender-E-Mail für System-Mails (z.B. 2FA-Code). */
#[Field(type: 'string')]
protected $smtpSenderEmail;
/** Absender-Name für System-Mails. */
#[Field(type: 'string')]
protected $smtpSenderName;
#[Field(type: 'string')]
protected null|string $numberPattern;
@ -329,6 +335,37 @@ class Instance implements UserInterface
$this->smtpUsername = $smtpUsername;
}
public function getSmtpSenderEmail()
{
return $this->smtpSenderEmail;
}
public function setSmtpSenderEmail($smtpSenderEmail)
{
$this->smtpSenderEmail = $smtpSenderEmail;
}
public function getSmtpSenderName()
{
return $this->smtpSenderName;
}
public function setSmtpSenderName($smtpSenderName)
{
$this->smtpSenderName = $smtpSenderName;
}
/**
* Sind die System-SMTP-Einstellungen inkl. Absender vollständig hinterlegt?
* Grundlage dafür, ob die E-Mail-2FA genutzt werden kann.
*/
public function isMailReady(): bool
{
return (bool) $this->smtpOwn
&& (string) $this->smtpHost !== ''
&& (string) $this->smtpSenderEmail !== '';
}
/**
* @return int
*/

View File

@ -88,6 +88,8 @@ class SettingsType extends AbstractType
->add('smtpHost', TextType::class, ['required' => false, 'label' => 'Host'])
->add('smtpUsername', TextType::class, ['required' => false, 'label' => 'Username'])
->add('smtpPassword', PasswordType::class, ['required' => false, 'label' => 'Password'])
->add('smtpSenderEmail', TextType::class, ['required' => false, 'label' => 'Absender E-Mail'])
->add('smtpSenderName', TextType::class, ['required' => false, 'label' => 'Absender Name'])
->add('extraSettings', TextareaType::class, ['required' => false, 'label' => 'Extra Einstellungen'])
->add('maintenanceMode', CheckboxType::class, ['required' => false, 'label' => 'Wartungsmodus'])
->add('maintenanceTitle', TextType::class, ['required' => false, 'label' => 'Wartung Titel'])

View File

@ -0,0 +1,48 @@
<?php
namespace PSC\System\SettingsBundle\Mailer;
use PSC\System\SettingsBundle\Service\Instance;
use Scheb\TwoFactorBundle\Mailer\AuthCodeMailerInterface;
use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
/**
* Versendet den 2FA-E-Mail-Code immer mit Absender und SMTP aus den
* Systemeinstellungen (Instance). Der MailerInterface routet ohne Shop-Header
* automatisch auf den System-/Instance-SMTP (siehe PSCRoutingTransport).
*/
class InstanceAuthCodeMailer implements AuthCodeMailerInterface
{
public function __construct(
private readonly MailerInterface $mailer,
private readonly Instance $instanceService,
) {
}
public function sendAuthCode(TwoFactorInterface $user): void
{
$authCode = $user->getEmailAuthCode();
if ($authCode === null) {
return;
}
$instance = $this->instanceService->getInstance();
$message = new Email();
$message
->to($user->getEmailAuthRecipient())
->subject('Ihr Sicherheitscode')
->text("Ihr Sicherheitscode lautet: " . $authCode);
$senderEmail = (string) $instance->getSmtpSenderEmail();
if ($senderEmail !== '') {
$senderName = (string) $instance->getSmtpSenderName();
$message->from($senderName !== '' ? new Address($senderEmail, $senderName) : new Address($senderEmail));
}
$this->mailer->send($message);
}
}

View File

@ -288,6 +288,29 @@
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<h6 class="mt-2 mb-2">Für Systememails</h6>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="row mb-3">
<label class="col-md-3 form-control-label">{{ form_label(form.smtpSenderEmail) }}</label>
<div class="col-md-9">
{{ form_widget(form.smtpSenderEmail) }}
</div>
</div>
</div>
<div class="col-md-4">
<div class="row mb-3">
<label class="col-md-3 form-control-label">{{ form_label(form.smtpSenderName) }}</label>
<div class="col-md-9">
{{ form_widget(form.smtpSenderName) }}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-8"><a href="{{ path('psc_backend_system_settings_smtptest') }}" target="_blank" class="btn btn-sm btn-info w-100">Einstellungen testen</a></div>
</div>