diff --git a/config/routes.yaml b/config/routes.yaml index cef258c..086ebd7 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -8,4 +8,7 @@ # bin/console debug:router controllers: - resource: routing.controllers + resource: + path: ../src/Infrastructure/Http/Controller/ + namespace: App\Infrastructure\Http\Controller + type: attribute diff --git a/config/routes/easyadmin.yaml b/config/routes/easyadmin.yaml new file mode 100644 index 0000000..f853be1 --- /dev/null +++ b/config/routes/easyadmin.yaml @@ -0,0 +1,3 @@ +_security_logout: + resource: security.route_loader.logout + type: service diff --git a/src/Domain/Auth/User.php b/src/Domain/Auth/User.php index 33c8ba1..543905a 100644 --- a/src/Domain/Auth/User.php +++ b/src/Domain/Auth/User.php @@ -5,13 +5,16 @@ declare(strict_types=1); namespace App\Domain\Auth; use Doctrine\ORM\Mapping as ORM; +use Scheb\TwoFactorBundle\Model\Totp\TotpConfiguration; +use Scheb\TwoFactorBundle\Model\Totp\TotpConfigurationInterface; +use Scheb\TwoFactorBundle\Model\Totp\TwoFactorInterface as TotpTwoFactorInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Uid\Uuid; #[ORM\Entity] #[ORM\Table(name: 'users', schema: 'app')] -class User implements UserInterface, PasswordAuthenticatedUserInterface +class User implements UserInterface, PasswordAuthenticatedUserInterface, TotpTwoFactorInterface { #[ORM\Id] #[ORM\Column(type: 'uuid')] @@ -73,6 +76,27 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface { } + public function isTotpAuthenticationEnabled(): bool + { + return null !== $this->totpSecret; + } + + public function getTotpAuthenticationUsername(): string + { + \assert('' !== $this->email); + + return $this->email; + } + + public function getTotpAuthenticationConfiguration(): ?TotpConfigurationInterface + { + if (null === $this->totpSecret) { + return null; + } + + return new TotpConfiguration($this->totpSecret, TotpConfiguration::ALGORITHM_SHA1, 30, 6); + } + public function getTotpSecret(): ?string { return $this->totpSecret; @@ -88,6 +112,11 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this->isActive; } + public function setPasswordHash(string $passwordHash): void + { + $this->passwordHash = $passwordHash; + } + public function setIsActive(bool $active): void { $this->isActive = $active; diff --git a/src/Infrastructure/Http/Controller/Admin/DashboardController.php b/src/Infrastructure/Http/Controller/Admin/DashboardController.php new file mode 100644 index 0000000..adb81b0 --- /dev/null +++ b/src/Infrastructure/Http/Controller/Admin/DashboardController.php @@ -0,0 +1,51 @@ +render('admin/dashboard.html.twig'); + } + + public function configureUserMenu(UserInterface $user): UserMenu + { + return parent::configureUserMenu($user) + ->addMenuItems([ + MenuItem::linkToRoute('Passwort ändern', 'fa fa-key', 'app_change_password'), + ]); + } + + public function configureDashboard(): Dashboard + { + return Dashboard::new()->setTitle('SuperSeller3000'); + } + + public function configureMenuItems(): iterable + { + yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home'); + yield MenuItem::linkTo(ArticleCrudController::class, 'Articles', 'fa fa-box'); + yield MenuItem::linkTo(ArticleTypeCrudController::class, 'Article Types', 'fa fa-tags'); + yield MenuItem::linkTo(AIPipelineJobCrudController::class, 'AI Pipeline Jobs', 'fa fa-robot'); + yield MenuItem::linkTo(UserCrudController::class, 'Users', 'fa fa-users'); + yield MenuItem::linkTo(LogEntryCrudController::class, 'Logs', 'fa fa-list'); + yield MenuItem::section('Verkauf'); + yield MenuItem::linkTo(OrderCrudController::class, 'Bestellungen', 'fa fa-shopping-cart'); + yield MenuItem::linkTo(CustomerCrudController::class, 'Kunden', 'fa fa-users'); + yield MenuItem::linkTo(InvoiceCrudController::class, 'Rechnungen', 'fa fa-file-invoice'); + } +} diff --git a/src/Infrastructure/Http/Controller/ChangePasswordController.php b/src/Infrastructure/Http/Controller/ChangePasswordController.php new file mode 100644 index 0000000..7881554 --- /dev/null +++ b/src/Infrastructure/Http/Controller/ChangePasswordController.php @@ -0,0 +1,63 @@ +getUser(); + + $error = null; + + if ($request->isMethod('POST')) { + $token = $request->request->getString('_csrf_token'); + if (!$this->isCsrfTokenValid('change_password', $token)) { + $error = 'Ungültiges Formular-Token. Bitte erneut versuchen.'; + } else { + $current = $request->request->getString('current_password'); + $new = $request->request->getString('new_password'); + $confirm = $request->request->getString('confirm_password'); + + if (!$this->hasher->isPasswordValid($user, $current)) { + $error = 'Das aktuelle Passwort ist falsch.'; + } elseif (mb_strlen($new) < 8) { + $error = 'Das neue Passwort muss mindestens 8 Zeichen lang sein.'; + } elseif ($new !== $confirm) { + $error = 'Die neuen Passwörter stimmen nicht überein.'; + } else { + $user->setPasswordHash($this->hasher->hashPassword($user, $new)); + $this->users->save($user); + + $this->addFlash('success', 'Passwort erfolgreich geändert.'); + + return $this->redirectToRoute('app_change_password'); + } + } + } + + return $this->render('security/change_password.html.twig', [ + 'error' => $error, + ]); + } +} diff --git a/templates/security/change_password.html.twig b/templates/security/change_password.html.twig new file mode 100644 index 0000000..bcbc7b7 --- /dev/null +++ b/templates/security/change_password.html.twig @@ -0,0 +1,37 @@ +{% extends 'base.html.twig' %} + +{% block title %}Passwort ändern{% endblock %} + +{% block body %} +