Lines 89.65% 26 / 29
Methods 71.42% 5 / 7
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 supports 100.00% 1 / 1 100.00% 1 / 1 1
 authenticate 85.71% 12 / 14 0.00% 0 / 1 3.03
 onAuthenticationSuccess 100.00% 1 / 1 100.00% 1 / 1 1
 onAuthenticationFailure 0.00% 0 / 1 0.00% 0 / 1 2
 start 100.00% 1 / 1 100.00% 1 / 1 1
 unauthorized 100.00% 10 / 10 100.00% 1 / 1 1
27class GoogleApiTokenAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface
28{
29    private const string BEARER_PREFIX = 'Bearer ';
30
31    public function __construct(
32        private readonly GoogleIdTokenVerifierInterface $idTokenVerifier,
33        private readonly GoogleUserResolver $userResolver,
34        private readonly AvatarUpdater $avatarUpdater,
35    ) {
36    }
37
38    #[\Override]
39    public function supports(Request $request): bool
40    {
41        return \str_starts_with((string) $request->headers->get('Authorization'), self::BEARER_PREFIX);
42    }
43
44    #[\Override]
45    public function authenticate(Request $request): Passport
46    {
47        $idToken = \trim(\substr((string) $request->headers->get('Authorization'), \strlen(self::BEARER_PREFIX)));
48
49        if ('' === $idToken) {
50            throw new CustomUserMessageAuthenticationException('Missing ID token.');
51        }
52
53        $payload = $this->idTokenVerifier->verify($idToken);
54
55        if (null === $payload) {
56            throw new CustomUserMessageAuthenticationException('Invalid ID token.');
57        }
58
59        [$googleId, $email] = GoogleUserResolver::identity($payload);
60
61        return new SelfValidatingPassport(
62            new UserBadge($googleId, function () use ($googleId, $email, $payload): User {
63                $user = $this->userResolver->resolve($googleId, $email);
64                // The app signs in with Google only: its users get their picture here
65                $this->avatarUpdater->update($user, $payload['picture'] ?? null);
66
67                return $user;
68            })
69        );
70    }
71
72    #[\Override]
73    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
74    {
75        return null;
76    }
77
78    #[\Override]
79    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): JsonResponse
80    {
81        return $this->unauthorized(\strtr($exception->getMessageKey(), $exception->getMessageData()));
82    }
83
84    #[\Override]
85    public function start(Request $request, ?AuthenticationException $authException = null): JsonResponse
86    {
87        return $this->unauthorized('Authentication required.');
88    }
89
90    /**
91     * An RFC 7807 problem, like API Platform's own errors.
92     */
93    private function unauthorized(string $message): JsonResponse
94    {
95        return new JsonResponse(
96            [
97                'type' => '/errors/401',
98                'title' => 'An error occurred',
99                'status' => Response::HTTP_UNAUTHORIZED,
100                'detail' => $message,
101            ],
102            Response::HTTP_UNAUTHORIZED,
103            ['WWW-Authenticate' => 'Bearer', 'Content-Type' => 'application/problem+json'],
104        );
105    }
106}