feat: local user registration

This commit is contained in:
Björn Fromme committed 2026-06-23 16:27:08 +02:00
1 parent 8bb7f08074
commit 161029ec7c
29 files changed
+1549 -601

No files matched your search

+1 -1
View File
@@ -8,12 +8,12 @@ use App\Email\Mailer;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\UriSigner;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Psr\Log\LoggerInterface;
class PasswordResetManager
{
@@ -1,47 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Service;
use App\BusProNet\Model\PersonalData;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* Validates profile completeness for booking requirements.
*
* Checks whether a user's personal data contains all required fields needed to create
* or edit bookings using Symfony's validation system with the 'personal_data' group.
* Required fields include name, gender, date of birth, nationality, contact information,
* and full address data.
*/
class ProfileCompletenessChecker
{
private const VALIDATION_GROUP = 'personal_data';
public function __construct(
private readonly ValidatorInterface $validator,
) {
}
/**
* Checks if the personal data contains all required fields for booking operations.
*
* Validates against the 'personal_data' group which includes:
* - firstName and name (personal identification)
* - gender and dateOfBirth (personal attributes)
* - nationality (booking form requirement)
* - email and mobile (communication)
* - street, postCode, city, country (applicant address)
*
* @param PersonalData $personalData The personal data to validate
*
* @return bool True if all required fields are present and valid, false otherwise
*/
public function isComplete(PersonalData $personalData): bool
{
$violations = $this->validator->validate($personalData, null, [self::VALIDATION_GROUP]);
return 0 === $violations->count();
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace App\Service;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\RateLimiter\RateLimiterFactory;
class PublicFormRateLimiter
{
public const string THROTTLED_MESSAGE = 'Zu viele Versuche. Bitte versuche es später erneut.';
public function __construct(
private readonly RateLimiterFactory $registrationLimiter,
private readonly RateLimiterFactory $passwordResetLimiter,
) {
}
public function isRegistrationAllowed(string $email, Request $request): bool
{
return $this->consume($this->registrationLimiter, $email, $request);
}
public function isPasswordResetAllowed(string $email, Request $request): bool
{
return $this->consume($this->passwordResetLimiter, $email, $request);
}
public function createBucketKey(string $email, ?string $clientIp): string
{
return hash('sha256', $this->normalizeEmail($email).'|'.($clientIp ?? 'unknown'));
}
private function consume(RateLimiterFactory $limiterFactory, string $email, Request $request): bool
{
return $limiterFactory->create($this->createBucketKey($email, $request->getClientIp()))
->consume()
->isAccepted();
}
private function normalizeEmail(string $email): string
{
return mb_strtolower(trim($email));
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\Service;
use App\Entity\User;
final class UserBootstrapper
{
/**
* @param list<string> $roles
* @param list<string> $hotelCodes
*/
public function bootstrap(
User $user,
string $passwordHash,
?int $addressId,
?int $personId,
array $roles,
array $hotelCodes,
bool $profileComplete,
): User {
$user
->setPassword($passwordHash)
->setPersonId($personId)
->setAddressId($addressId)
->setRoles($roles)
->setHotelCodes($hotelCodes)
->setProfileComplete($profileComplete)
;
return $user;
}
}