Lines 100.00% 20 / 20
Functions and Methods 100.00% 3 / 3
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
GoogleUserResolver 100.00% 20 / 20 100.00% 3 / 3 10 100.00% 1 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 identity 100.00% 7 / 7 100.00% 1 / 1 6
 resolve 100.00% 12 / 12 100.00% 1 / 1 3
1<?php
2
3declare(strict_types=1);
4
5namespace App\Security;
6
7use App\Entity\User;
8use App\Notifier\ChatActivity;
9use App\Repository\UserRepository;
10use Doctrine\ORM\EntityManagerInterface;
11use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
12
13/**
14 * The user of a verified Google ID token, for the web sign-in and the API alike.
15 */
16final readonly class GoogleUserResolver
17{
18    public function __construct(
19        private UserRepository $userRepository,
20        private EntityManagerInterface $entityManager,
21        private ChatActivity $chatActivity,
22    ) {
23    }
24
25    /**
26     * The token's Google ID and email: both required, and the email must be verified, as it may
27     * link an existing account.
28     *
29     * @param array<string, mixed> $payload the verified token payload
30     *
31     * @return array{string, string}
32     *
33     * @throws CustomUserMessageAuthenticationException
34     */
35    public static function identity(array $payload): array
36    {
37        if (true !== ($payload['email_verified'] ?? false)) {
38            throw new CustomUserMessageAuthenticationException('Email address is not verified.');
39        }
40
41        $googleId = \is_string($payload['sub'] ?? null) ? $payload['sub'] : '';
42        $email = \is_string($payload['email'] ?? null) ? $payload['email'] : '';
43
44        if ('' === $googleId || '' === $email) {
45            throw new CustomUserMessageAuthenticationException('Invalid ID token.');
46        }
47
48        return [$googleId, $email];
49    }
50
51    /**
52     * The user with this Google ID; else links the account with this email to it, or registers a
53     * new user (ROLE_USER), posted to Slack and Telegram.
54     */
55    public function resolve(string $googleId, string $email): User
56    {
57        $user = $this->userRepository->findOneBy(['googleId' => $googleId]);
58
59        if (null !== $user) {
60            return $user;
61        }
62
63        $user = $this->userRepository->findOneBy(['identifier' => $email]);
64        $registered = null === $user;
65        $user ??= new User()->setIdentifier($email);
66
67        $user->setGoogleId($googleId);
68
69        $this->entityManager->persist($user);
70        $this->entityManager->flush();
71
72        if ($registered) {
73            $this->chatActivity->userRegistered($user);
74        }
75
76        return $user;
77    }
78}