Mise en place intégration Discord

This commit is contained in:
Angels-dev
2024-08-22 02:37:00 +02:00
parent 2be55bc65d
commit bc4489098d
34 changed files with 4054 additions and 4 deletions

View File

@@ -0,0 +1,86 @@
<?php
namespace App\Controller;
use DateTime;
use DateInterval;
use App\Entity\User;
use App\Service\DiscordApiService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class AuthController extends AbstractController
{
private $discordApiService;
public function __construct(DiscordApiService $discordApiService)
{
$this->discordApiService = $discordApiService;
}
/**
* @Route("/discord/auth/redirect", name="discord_auth_redirect")
*/
public function redirectToDiscord(): RedirectResponse
{
$clientId = $_ENV['DISCORD_CLIENT_ID'];
$redirectUri = $_ENV['DISCORD_BACKEND_REDIRECT_URI'];
$scope = 'identify email guilds'; // Ajouter les scopes dont tu as besoin
$discordAuthUrl = "https://discord.com/oauth2/authorize?client_id={$clientId}&redirect_uri={$redirectUri}&response_type=code&scope={$scope}";
return $this->redirect($discordAuthUrl);
}
/**
* @Route("/discord/auth/callback", name="discord_auth_callback")
*/
public function handleDiscordCallback(Request $request, EntityManagerInterface $entityManager, SessionInterface $session): Response
{
$code = $request->query->get('code');
if (!$code) return new Response('Authorization code not found', Response::HTTP_BAD_REQUEST);
// Obtenir le token d'accès et de refresh depuis Discord
$tokenData = $this->discordApiService->exchangeCodeForToken($code);
$accessToken = $tokenData['access_token'];
$refreshToken = $tokenData['refresh_token'];
$expiresIn = $tokenData['expires_in'];
// Calcul du délai d'expiration
$tokenExpiresAt = (new DateTime())->add(new DateInterval('PT' . $expiresIn . 'S'));
// Obtenir les informations utilisateur depuis Discord
$discordUser = $this->discordApiService->getUserData($accessToken);
// Chercher l'utilisateur dans la base de données par son discordId
$user = $entityManager->getRepository(User::class)->findOneBy(['discordId' => $discordUser['id']]);
if (!$user) {
// Si l'utilisateur n'existe pas, on le crée
$user = new User();
$user->setDiscordId($discordUser['id']);
}
// Créer ou mettre à jour les informations de l'utilisateur
$user->setUsername($discordUser['username']);
$user->setEmail($discordUser['email'] ?? null);
$user->setAccessToken($accessToken);
$user->setRefreshToken($refreshToken);
$user->setTokenExpiresAt($tokenExpiresAt);
$entityManager->persist($user);
$entityManager->flush();
// Stocker l'utilisateur dans la session
$session->set('user_id', $user->getId());
// Redirection vers le frontend
return $this->redirect($_ENV['DISCORD_FRONTEND_REDIRECT_URI']);
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Controller;
use DateTime;
use DateInterval;
use App\Entity\User;
use App\Service\DiscordApiService;
use App\Exception\DiscordApiException;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class BotController extends AbstractController
{
private $discordApiService;
public function __construct(DiscordApiService $discordApiService)
{
$this->discordApiService = $discordApiService;
}
private function getUserToken(SessionInterface $session, EntityManagerInterface $entityManager): ?User
{
// Vérifier si une session est en cours
if (!$session->has('user_id')) throw new DiscordApiException(Response::HTTP_UNAUTHORIZED, 'Non authentifié');
$user = $entityManager->getRepository(User::class)->find($session->get('user_id'));
if (!$user) throw new DiscordApiException(Response::HTTP_NOT_FOUND, 'Utilisateur non trouvé');
// Vérifier si l'utilisateur a un token d'accès et qu'il n'est pas expiré
if (!$user->getAccessToken() || $user->getTokenExpiresAt() < new DateTime()) {
if ($user->getRefreshToken()) {
// Rafraîchir le token d'accès
$tokenData = $this->discordApiService->refreshToken($user->getRefreshToken());
$user->setAccessToken($tokenData['access_token']);
$user->setRefreshToken($tokenData['refresh_token']);
$user->setTokenExpiresAt((new DateTime())->add(new DateInterval('PT' . $tokenData['expires_in'] . 'S')));
} throw new DiscordApiException(Response::HTTP_UNAUTHORIZED, "Token d'accès expiré et/ou token de refresh invalide");
}
return $user;
}
/**
* @Route("/discord/bots", name="discord_bots")
*/
public function getBotsDetails(SessionInterface $session, EntityManagerInterface $entityManager): Response
{
try { $user = $this->getUserToken($session, $entityManager); }
catch (DiscordApiException $e) { return new JsonResponse(['error' => $e->getMessage()], $e->getStatusCode());}
// Obtenir les informations utilisateur supplémentaires depuis Discord
try { $discordBots = $this->discordApiService->getBotsData($user->getAccessToken()); }
catch (DiscordApiException $e) { return new JsonResponse(['error' => $e->getMessage()], $e->getStatusCode());}
// Renvoyer les détails de l'utilisateur en JSON
return new JsonResponse($discordUser);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
class RootController extends AbstractController
{
/**
* @Route("/", name="slash")
*/
public function redirectToFrontend(): RedirectResponse
{
return $this->redirect($_ENV['FRONTEND_REDIRECT_URI']);
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace App\Controller;
use DateTime;
use DateInterval;
use App\Entity\User;
use App\Service\DiscordApiService;
use App\Exception\DiscordApiException;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class UserController extends AbstractController
{
private $discordApiService;
public function __construct(DiscordApiService $discordApiService)
{
$this->discordApiService = $discordApiService;
}
private function getUserToken(SessionInterface $session, EntityManagerInterface $entityManager): ?User
{
// Vérifier si une session est en cours
if (!$session->has('user_id')) throw new DiscordApiException(Response::HTTP_UNAUTHORIZED, 'Non authentifié');
$user = $entityManager->getRepository(User::class)->find($session->get('user_id'));
if (!$user) throw new DiscordApiException(Response::HTTP_NOT_FOUND, 'Utilisateur non trouvé');
// Vérifier si l'utilisateur a un token d'accès et qu'il n'est pas expiré
if (!$user->getAccessToken() || $user->getTokenExpiresAt() < new DateTime()) {
if ($user->getRefreshToken()) {
// Rafraîchir le token d'accès
$tokenData = $this->discordApiService->refreshToken($user->getRefreshToken());
$user->setAccessToken($tokenData['access_token']);
$user->setRefreshToken($tokenData['refresh_token']);
$user->setTokenExpiresAt((new DateTime())->add(new DateInterval('PT' . $tokenData['expires_in'] . 'S')));
} throw new DiscordApiException(Response::HTTP_UNAUTHORIZED, "Token d'accès expiré et/ou token de refresh invalide");
}
return $user;
}
/**
* @Route("/discord/user", name="discord_user")
*/
public function getUserDetails(SessionInterface $session, EntityManagerInterface $entityManager): Response
{
try { $user = $this->getUserToken($session, $entityManager); }
catch (DiscordApiException $e) { return new JsonResponse(['error' => $e->getMessage()], $e->getStatusCode());}
// Obtenir les informations utilisateur supplémentaires depuis Discord
try { $discordUser = $this->discordApiService->getUserData($user->getAccessToken()); }
catch (DiscordApiException $e) { return new JsonResponse(['error' => $e->getMessage()], $e->getStatusCode());}
// Mettre à jour les informations utilisateur dans la base de données
$user->setUsername($discordUser['username']);
$user->setEmail($discordUser['email'] ?? null);
$entityManager->persist($user);
$entityManager->flush();
// Renvoyer les détails de l'utilisateur en JSON
return new JsonResponse($discordUser);
}
/**
* @Route("/discord/user/guilds", name="discord_user_guilds")
*/
public function getUserGuilds(SessionInterface $session, EntityManagerInterface $entityManager): Response
{
try { $user = $this->getUserToken($session, $entityManager); }
catch (DiscordApiException $e) { return new JsonResponse(['error' => $e->getMessage()], $e->getStatusCode());}
// Obtenir les informations utilisateur supplémentaires depuis Discord
try { $discordUserGuilds = $this->discordApiService->getUserGuilds($user->getAccessToken()); }
catch (DiscordApiException $e) { return new JsonResponse(['error' => $e->getMessage()], $e->getStatusCode());}
// Renvoyer les détails de l'utilisateur en JSON
return new JsonResponse($discordUserGuilds);
}
}

0
src/Entity/.gitignore vendored Normal file
View File

134
src/Entity/User.php Normal file
View File

@@ -0,0 +1,134 @@
<?php
namespace App\Entity;
use DateTime;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column()]
private int $id;
#[ORM\Column(length: 255, unique: true)]
private string $discordId;
#[ORM\Column(length: 255)]
private string $username;
#[ORM\Column(length: 255, nullable: true)]
private string $email;
#[ORM\Column(length: 255, nullable: true)]
private string $accessToken;
#[ORM\Column(length: 255, nullable: true)]
private string $refreshToken;
#[ORM\Column(length: 255, nullable: true)]
private DateTime $tokenExpiresAt;
public function getId(): ?int
{
return $this->id;
}
public function getDiscordId(): ?string
{
return $this->discordId;
}
public function setDiscordId(string $discordId): self
{
$this->discordId = $discordId;
return $this;
}
public function getUsername(): ?string
{
return $this->username;
}
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(?string $email): self
{
$this->email = $email;
return $this;
}
public function getAccessToken(): ?string
{
return $this->accessToken;
}
public function setAccessToken(string $accessToken): self
{
$this->accessToken = $accessToken;
return $this;
}
public function getRefreshToken(): ?string
{
return $this->refreshToken;
}
public function setRefreshToken(string $refreshToken): self
{
$this->refreshToken = $refreshToken;
return $this;
}
public function getTokenExpiresAt(): ?DateTime
{
return $this->tokenExpiresAt;
}
public function setTokenExpiresAt(DateTime $tokenExpiresAt): self
{
$this->tokenExpiresAt = $tokenExpiresAt;
return $this;
}
// Implémentation de la méthode getUserIdentifier()
public function getUserIdentifier(): string
{
return $this->discordId;
}
// Implémentation des méthodes de l'interface UserInterface
public function getRoles(): array
{
return ['ROLE_USER'];
}
public function getPassword(): ?string
{
// Pas de mot de passe car on utilise Discord pour l'authentification
return null;
}
public function getSalt(): ?string
{
return null;
}
public function eraseCredentials()
{
// Pas nécessaire ici car nous n'avons pas de données sensibles
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Exception;
use Symfony\Component\HttpKernel\Exception\HttpException;
class DiscordApiException extends HttpException
{
public function __construct(int $statusCode, string $message = '', \Throwable $previous = null, array $headers = [], int $code = 0)
{
parent::__construct($statusCode, $message, $previous, $headers, $code);
}
}

0
src/Repository/.gitignore vendored Normal file
View File

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<User>
*
* @method User|null find($id, $lockMode = null, $lockVersion = null)
* @method User|null findOneBy(array $criteria, array $orderBy = null)
* @method User[] findAll()
* @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class UserRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace App\Service;
use App\Exception\DiscordApiException;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
class DiscordApiService
{
private $client;
private $clientId;
private $clientSecret;
private $redirectUri;
private $baseUrl;
public function __construct(HttpClientInterface $client)
{
$this->client = $client;
$this->clientId = $_ENV['DISCORD_CLIENT_ID'];
$this->clientSecret = $_ENV['DISCORD_CLIENT_SECRET'];
$this->redirectUri = $_ENV['DISCORD_BACKEND_REDIRECT_URI'];
$this->baseUrl = $_ENV['DISCORD_API_BASE_URL'];
}
public function exchangeCodeForToken(string $code): array
{
try {
$response = $this->client->request('POST', $this->baseUrl . '/oauth2/token', [
'body' => [
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => $this->redirectUri,
],
]);
return $response->toArray();
} catch (ClientExceptionInterface | ServerExceptionInterface | RedirectionExceptionInterface | TransportExceptionInterface $e) {
throw new DiscordApiException($e->getCode(), 'Erreur lors de la requête à l\'API Discord: ' . $e->getMessage(), $e);
}
}
public function refreshToken(string $refreshToken): array
{
try {
$response = $this->client->request('POST', $this->baseUrl . '/oauth2/token', [
'body' => [
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'redirect_uri' => $this->redirectUri,
],
]);
return $response->toArray();
} catch (ClientExceptionInterface | ServerExceptionInterface | RedirectionExceptionInterface | TransportExceptionInterface $e) {
throw new DiscordApiException($e->getCode(), 'Erreur lors de la requête à l\'API Discord: ' . $e->getMessage(), $e);
}
}
public function getBotsData(string $accessToken): array
{
/**
$output = array();
$botsId = [$_ENV['DISCORD_BOT_TAMISEUR_ID'], $_ENV['DISCORD_BOT_GROOVE_ID'], $_ENV['DISCORD_BOT_FUNKY_ID'], $_ENV['DISCORD_BOT_JUJUL_ID'], $_ENV['DISCORD_BOT_CHANTIER_ID']];
foreach($botsId as $botId)
{
try {
$response = $this->client->request('GET', $this->baseUrl . '/users/' . $botId, [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
],
]);
array_push($output, $response->toArray());
} catch (ClientExceptionInterface | ServerExceptionInterface | RedirectionExceptionInterface | TransportExceptionInterface $e) {
throw new DiscordApiException($e->getCode(), 'Erreur lors de la requête à l\'API Discord: ' . $e->getMessage(), $e);
}
}
return new JsonResponse($output);
*/
try {
$response = $this->client->request('GET', $this->baseUrl . '/users/' . '262299921082875904', [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
],
]);
return $response->toArray();
} catch (ClientExceptionInterface | ServerExceptionInterface | RedirectionExceptionInterface | TransportExceptionInterface $e) {
throw new DiscordApiException($e->getCode(), 'Erreur lors de la requête à l\'API Discord: ' . $e->getMessage(), $e);
}
}
public function getUserData(string $accessToken): array
{
try {
$response = $this->client->request('GET', $this->baseUrl . '/users/@me', [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
],
]);
return $response->toArray();
} catch (ClientExceptionInterface | ServerExceptionInterface | RedirectionExceptionInterface | TransportExceptionInterface $e) {
throw new DiscordApiException($e->getCode(), 'Erreur lors de la requête à l\'API Discord: ' . $e->getMessage(), $e);
}
}
public function getUserGuilds(string $accessToken): array
{
try {
$response = $this->client->request('GET', $this->baseUrl . '/users/@me/guilds', [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
],
]);
return $response->toArray();
} catch (ClientExceptionInterface | ServerExceptionInterface | RedirectionExceptionInterface | TransportExceptionInterface $e) {
throw new DiscordApiException($e->getCode(), 'Erreur lors de la requête à l\'API Discord: ' . $e->getMessage(), $e);
}
}
}