* * @see Story 1.4 - User login (AC2: no account existence disclosure) */ final readonly class DatabaseUserProvider implements UserProviderInterface { public function __construct( private UserRepository $userRepository, private TenantResolver $tenantResolver, private RequestStack $requestStack, private SecurityUserFactory $securityUserFactory, ) { } public function loadUserByIdentifier(string $identifier): UserInterface { $tenantId = $this->getCurrentTenantId(); try { $email = new Email($identifier); } catch (EmailInvalideException) { // Malformed email = treat as user not found (security: generic error) throw new SymfonyUserNotFoundException(); } $user = $this->userRepository->findByEmail($email, $tenantId); // Generic message to not reveal account existence if ($user === null) { throw new SymfonyUserNotFoundException(); } // Do not allow login if the account is not active if (!$user->peutSeConnecter()) { throw new SymfonyUserNotFoundException(); } return $this->securityUserFactory->fromDomainUser($user); } public function refreshUser(UserInterface $user): UserInterface { if (!$user instanceof SecurityUser) { throw new InvalidArgumentException('Expected instance of ' . SecurityUser::class); } return $this->loadUserByIdentifier($user->email()); } public function supportsClass(string $class): bool { return $class === SecurityUser::class; } /** * Resolves the current tenant from the request host. * * @throws SymfonyUserNotFoundException if tenant cannot be resolved (security: generic error) */ private function getCurrentTenantId(): TenantId { $request = $this->requestStack->getCurrentRequest(); if ($request === null) { throw new SymfonyUserNotFoundException(); } $host = $request->getHost(); // Dev/test fallback: localhost uses ecole-alpha tenant if ($host === 'localhost' || $host === '127.0.0.1') { try { return $this->tenantResolver->resolve('ecole-alpha.classeo.local')->tenantId; } catch (TenantNotFoundException) { throw new SymfonyUserNotFoundException(); } } try { $tenantConfig = $this->tenantResolver->resolve($host); return $tenantConfig->tenantId; } catch (TenantNotFoundException) { // Don't reveal tenant doesn't exist - use same error as invalid credentials throw new SymfonyUserNotFoundException(); } } }