1 | <?php |
||
2 | declare(strict_types = 1); |
||
3 | /** |
||
4 | * /src/EventSubscriber/LockedUserSubscriber.php |
||
5 | * |
||
6 | * @author TLe, Tarmo Leppänen <[email protected]> |
||
7 | */ |
||
8 | |||
9 | namespace App\EventSubscriber; |
||
10 | |||
11 | use App\Entity\LogLoginFailure; |
||
12 | use App\Entity\User; |
||
13 | use App\Repository\UserRepository; |
||
14 | use App\Resource\LogLoginFailureResource; |
||
15 | use App\Security\SecurityUser; |
||
16 | use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationFailureEvent; |
||
17 | use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent; |
||
18 | use Lexik\Bundle\JWTAuthenticationBundle\Events; |
||
19 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; |
||
20 | use Symfony\Component\HttpFoundation\Request; |
||
21 | use Symfony\Component\HttpFoundation\RequestStack; |
||
22 | use Symfony\Component\Security\Core\Exception\LockedException; |
||
23 | use Symfony\Component\Security\Core\Exception\UnsupportedUserException; |
||
24 | use Throwable; |
||
25 | use function assert; |
||
26 | use function count; |
||
27 | use function is_string; |
||
28 | |||
29 | /** |
||
30 | * Class LockedUserSubscriber |
||
31 | * |
||
32 | * @package App\EventSubscriber |
||
33 | * @author TLe, Tarmo Leppänen <[email protected]> |
||
34 | */ |
||
35 | class LockedUserSubscriber implements EventSubscriberInterface |
||
36 | { |
||
37 | 22 | public function __construct( |
|
38 | private readonly UserRepository $userRepository, |
||
39 | private readonly LogLoginFailureResource $logLoginFailureResource, |
||
40 | private readonly RequestStack $requestStack, |
||
41 | ) { |
||
42 | 22 | } |
|
43 | |||
44 | /** |
||
45 | * {@inheritdoc} |
||
46 | */ |
||
47 | 1 | public static function getSubscribedEvents(): array |
|
48 | { |
||
49 | 1 | return [ |
|
50 | 1 | AuthenticationSuccessEvent::class => [ |
|
51 | 1 | 'onAuthenticationSuccess', |
|
52 | 1 | 128, |
|
53 | 1 | ], |
|
54 | 1 | Events::AUTHENTICATION_SUCCESS => [ |
|
55 | 1 | 'onAuthenticationSuccess', |
|
56 | 1 | 128, |
|
57 | 1 | ], |
|
58 | 1 | AuthenticationFailureEvent::class => 'onAuthenticationFailure', |
|
59 | 1 | Events::AUTHENTICATION_FAILURE => 'onAuthenticationFailure', |
|
60 | 1 | ]; |
|
61 | } |
||
62 | |||
63 | /** |
||
64 | * @throws Throwable |
||
65 | */ |
||
66 | 18 | public function onAuthenticationSuccess(AuthenticationSuccessEvent $event): void |
|
67 | { |
||
68 | 18 | $user = $this->getUser($event->getUser()) ?? throw new UnsupportedUserException('Unsupported user.'); |
|
69 | |||
70 | 17 | if (count($user->getLogsLoginFailure()) > 10) { |
|
71 | 2 | throw new LockedException('Locked account.'); |
|
72 | } |
||
73 | |||
74 | 15 | $this->logLoginFailureResource->reset($user); |
|
75 | } |
||
76 | |||
77 | /** |
||
78 | * @throws Throwable |
||
79 | */ |
||
80 | 5 | public function onAuthenticationFailure(): void |
|
81 | { |
||
82 | 5 | $request = $this->requestStack->getCurrentRequest(); |
|
83 | |||
84 | assert($request instanceof Request); |
||
85 | |||
86 | 5 | $user = $this->getUser( |
|
87 | 5 | (string)($request->query->get('username') ?? $request->request->get('username', '')) |
|
88 | 5 | ); |
|
89 | |||
90 | 5 | if ($user !== null) { |
|
91 | 3 | $this->logLoginFailureResource->save(new LogLoginFailure($user), true); |
|
92 | } |
||
93 | } |
||
94 | |||
95 | /** |
||
96 | * @throws Throwable |
||
97 | */ |
||
98 | 22 | private function getUser(string | object $user): ?User |
|
99 | { |
||
100 | 22 | return match (true) { |
|
101 | 22 | is_string($user) => $this->userRepository->loadUserByIdentifier($user, false), |
|
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
102 | 22 | $user instanceof SecurityUser => |
|
103 | 22 | $this->userRepository->loadUserByIdentifier($user->getUserIdentifier(), true), |
|
104 | 22 | default => null, |
|
105 | 22 | }; |
|
106 | } |
||
107 | } |
||
108 |