Lines
67.64%
23 / 34
Methods
62.50%
5 / 8
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 | 73.33% | 11 / 15 | 0.00% | 0 / 1 | 9.21 | ||
| 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 | ||
| findUser | 33.33% | 3 / 9 | 0.00% | 0 / 1 | 5.67 | ||
| unauthorized | 100.00% | 5 / 5 | 100.00% | 1 / 1 | 1 | ||
| 27 | class 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 UserRepository $userRepository, | |
| 34 | private readonly EntityManagerInterface $entityManager, | |
| 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 | if (true !== ($payload['email_verified'] ?? false)) { | |
| 60 | throw new CustomUserMessageAuthenticationException('Email address is not verified.'); | |
| 61 | } | |
| 62 | ||
| 63 | $googleId = \is_string($payload['sub'] ?? null) ? $payload['sub'] : ''; | |
| 64 | $email = \is_string($payload['email'] ?? null) ? $payload['email'] : ''; | |
| 65 | ||
| 66 | if ('' === $googleId || '' === $email) { | |
| 67 | throw new CustomUserMessageAuthenticationException('Invalid ID token.'); | |
| 68 | } | |
| 69 | ||
| 70 | return new SelfValidatingPassport( | |
| 71 | new UserBadge($googleId, fn (): User => $this->findUser($googleId, $email)) | |
| 72 | ); | |
| 73 | } | |
| 74 | ||
| 75 | #[\Override] | |
| 76 | public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response | |
| 77 | { | |
| 78 | return null; | |
| 79 | } | |
| 80 | ||
| 81 | #[\Override] | |
| 82 | public function onAuthenticationFailure(Request $request, AuthenticationException $exception): JsonResponse | |
| 83 | { | |
| 84 | return $this->unauthorized(\strtr($exception->getMessageKey(), $exception->getMessageData())); | |
| 85 | } | |
| 86 | ||
| 87 | #[\Override] | |
| 88 | public function start(Request $request, ?AuthenticationException $authException = null): JsonResponse | |
| 89 | { | |
| 90 | return $this->unauthorized('Authentication required.'); | |
| 91 | } | |
| 92 | ||
| 93 | private function findUser(string $googleId, string $email): User | |
| 94 | { | |
| 95 | $user = $this->userRepository->findOneBy(['googleId' => $googleId]); | |
| 96 | ||
| 97 | if (null !== $user) { | |
| 98 | return $user; | |
| 99 | } | |
| 100 | ||
| 101 | // Link an existing account with the same email to this Google account | |
| 102 | $user = $this->userRepository->findOneBy(['identifier' => $email]); | |
| 103 | ||
| 104 | if (null === $user) { | |
| 105 | throw new CustomUserMessageAuthenticationException('Unknown user.'); | |
| 106 | } | |
| 107 | ||
| 108 | $user->setGoogleId($googleId); | |
| 109 | $this->entityManager->flush(); | |
| 110 | ||
| 111 | return $user; | |
| 112 | } | |
| 113 | ||
| 114 | private function unauthorized(string $message): JsonResponse | |
| 115 | { | |
| 116 | return new JsonResponse( | |
| 117 | ['error' => $message], | |
| 118 | Response::HTTP_UNAUTHORIZED, | |
| 119 | ['WWW-Authenticate' => 'Bearer'], | |
| 120 | ); | |
| 121 | } | |
| 122 | } |