<?php
namespace App\Security;
use App\Entity\Groups;
use App\Entity\User; // your user entity
use App\Entity\LoginLogs;
use App\Service\Browser;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Security\Authenticator\OAuth2Authenticator;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
class DiscordAuthenticator extends OAuth2Authenticator
{
private $clientRegistry;
private $entityManager;
private $router;
private $browser;
public function __construct(ClientRegistry $clientRegistry, EntityManagerInterface $entityManager, RouterInterface $router, Browser $browser)
{
$this->clientRegistry = $clientRegistry;
$this->entityManager = $entityManager;
$this->router = $router;
$this->browser = $browser;
}
public function supports(Request $request): ?bool
{
// continue ONLY if the current ROUTE matches the check ROUTE
return $request->attributes->get('_route') === 'connect_discord_check';
}
public function authenticate(Request $request): Passport
{
$client = $this->clientRegistry->getClient('discord');
$accessToken = $this->fetchAccessToken($client);
return new SelfValidatingPassport(
new UserBadge($accessToken->getToken(), function() use ($accessToken, $client, $request) {
/** @var DiscordUser $discordUser */
$discordUser = $client->fetchUserFromToken($accessToken);
$email = $discordUser->getEmail();
// 1) have they logged in with Discord before? Easy!
$existingUser = $this->entityManager->getRepository(User::class, 'default')->findOneBy(['discordId' => $discordUser->getId()]);
if ($existingUser) {
$existingUser->setUsername($discordUser->getUsername());
$existingUser->setEmail($discordUser->getEmail());
if ( !empty( $discordUser->getAvatarHash() ) ) {
$existingUser->setAvatar($discordUser->getAvatarHash());
}
$this->entityManager->persist($existingUser);
$this->entityManager->flush();
$loginLogs = new LoginLogs();
$loginLogs->setCreatedAt(new \DateTimeImmutable('NOW'));
$loginLogs->setUser($existingUser);
$loginLogs->setBrowser($this->browser->getBrowser() . ' (' . $this->browser->getVersion() . ') / ' . $this->browser->getPlatform() );
$loginLogs->setAddressIp($request->getClientIp());
$this->entityManager->persist($loginLogs);
$this->entityManager->flush();
return $existingUser;
}
// 2) do we have a matching user by email?
$user = $this->entityManager->getRepository(User::class, 'default')->findOneBy(['discordId' => $discordUser->getId()]);
$group = $this->entityManager->getRepository(Groups::class)->find(2);
// 3) Maybe you just want to "register" them by creating
// a User object
$user = new User();
$user->setDiscordId($discordUser->getId());
$user->setGroupId($group);
// $user->setSubgroups(array());
$user->setEmail($discordUser->getEmail());
if ( !empty( $discordUser->getAvatarHash() ) ) {
$user->setAvatar($discordUser->getAvatarHash());
}
$user->setUsername($discordUser->getUsername());
$user->setRoles(['ROLE_USER']);
$user->setJoinDate(new \DateTimeImmutable('NOW'));
$this->entityManager->persist($user);
$this->entityManager->flush();
$loginLogs = new LoginLogs();
$loginLogs->setCreatedAt(new \DateTimeImmutable('NOW'));
$loginLogs->setUser($user);
$loginLogs->setBrowser($this->browser->getBrowser() . ' (' . $this->browser->getVersion() . ') / ' . $this->browser->getPlatform() );
$loginLogs->setAddressIp($request->getClientIp());
$this->entityManager->persist($loginLogs);
$this->entityManager->flush();
return $user;
})
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
// change "app_homepage" to some route in your app
$targetUrl = $this->router->generate('app_home');
return new RedirectResponse($targetUrl);
// or, on success, let the request continue to be handled by the controller
//return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
$message = strtr($exception->getMessageKey(), $exception->getMessageData());
return new Response($message, Response::HTTP_FORBIDDEN);
}
}