Comus Party 1.0.0
Application web de mini-jeux en ligne
Chargement...
Recherche...
Aucune correspondance
ControllerAuth.class.php
Aller à la documentation de ce fichier.
1<?php
9
10namespace ComusParty\Controllers;
11
12use ComusParty\App\Cookie;
13use ComusParty\App\Exceptions\AuthenticationException;
14use ComusParty\App\Exceptions\MalformedRequestException;
15use ComusParty\App\Mailer;
16use ComusParty\App\MessageHandler;
17use ComusParty\App\Validator;
18use ComusParty\Models\ArticleDAO;
19use ComusParty\Models\ModeratorDao;
20use ComusParty\Models\PasswordResetToken;
21use ComusParty\Models\PasswordResetTokenDAO;
22use ComusParty\Models\PlayerDAO;
23use ComusParty\Models\RememberToken;
24use ComusParty\Models\RememberTokenDAO;
25use ComusParty\Models\User;
26use ComusParty\Models\UserDAO;
27use DateMalformedStringException;
28use DateTime;
29use Exception;
30use Random\RandomException;
31use Twig\Environment;
32use Twig\Error\LoaderError;
33use Twig\Error\RuntimeError;
34use Twig\Error\SyntaxError;
35use Twig\Loader\FilesystemLoader;
36
37
43{
44
50 public function __construct(FilesystemLoader $loader, Environment $twig)
51 {
52 parent::__construct($loader, $twig);
53 }
54
62 public function showLoginPage(): void
63 {
64 global $twig;
65 echo $twig->render('login.twig', [
66 'turnstile_siteKey' => CF_TURNSTILE_SITEKEY
67 ]);
68 }
69
77 public function showForgotPasswordPage(): void
78 {
79 global $twig;
80 echo $twig->render('forgot-password.twig');
81 }
82
90 public function sendResetPasswordLink(string $email): void
91 {
92 $validator = new Validator([
93 'email' => [
94 'required' => true,
95 'type' => 'string',
96 'format' => FILTER_VALIDATE_EMAIL
97 ]
98 ]);
99
100 if (!$validator->validate(['email' => $email])) {
101 MessageHandler::addExceptionParametersToSession(new MalformedRequestException("Adresse e-mail invalide"));
102 header('Location: /forgot-password');
103 return;
104 }
105
106 $userManager = new UserDAO($this->getPdo());
107 $user = $userManager->findByEmail($email);
108
109 if (is_null($user)) {
110 MessageHandler::addMessageParametersToSession("Un lien de réinitialisation de mot de passe vous a été envoyé par e-mail");
111 header('Location: /login');
112 exit;
113 }
114
115 $tokenManager = new PasswordResetTokenDAO($this->getPdo());
116
117 if ($tokenManager->findByUserId($user->getId())) {
118 MessageHandler::addMessageParametersToSession("Un lien de réinitialisation de mot de passe vous a été envoyé par e-mail");
119 header('Location: /login');
120 exit;
121 }
122
123 $token = new PasswordResetToken($user->getId(), bin2hex(random_bytes(30)), new DateTime());
124 $tokenManager->insert($token);
125
126 $url = BASE_URL . "/reset-password/" . $token->getToken();
127 $to = $user->getEmail();
128
129 $subject = '🔑 Réinitialiser votre mot de passe';
130 $message =
131 '<p>Bonjour,</p>
132 <p>Il semblerait que vous ayez fait une demande de réinitialisation de mot de passe.</p>
133 <p>Pour ce faire, cliquez sur le lien ci-dessous et renseignez votre nouveau mot de passe :</p>
134 <a href="' . $url . '">✅ Changer le mot de passe</a>
135 <p>À très bientôt dans l’arène ! 🎲,<br>
136 L\'équipe Comus Party 🚀</p>
137 <br/><br/>
138 <p style="font-size: 9px;">Si vous n\'êtes pas à l\'origine de cette demande, vous pouvez ignorer le présent mail.</p>';
139
140 try {
141 $mailer = new Mailer([$to], $subject, $message);
142 $mailer->generateHTMLMessage();
143
144 $mailer->send();
145 } catch (Exception $e) {
146 MessageHandler::addExceptionParametersToSession($e);
147 header('Location: /forgot-password');
148 return;
149 }
150
151 MessageHandler::addMessageParametersToSession("Un lien de réinitialisation de mot de passe vous a été envoyé par e-mail");
152 header('Location: /login');
153 exit;
154 }
155
165 public function showResetPasswordPage(string $token): void
166 {
167 global $twig;
168
169 $tokenManager = new PasswordResetTokenDAO($this->getPdo());
170 $token = $tokenManager->findByToken($token);
171
172 if (is_null($token)) {
173 throw new MalformedRequestException("Token invalide");
174 }
175
176 echo $twig->render('reset-password.twig');
177 }
178
189 public function resetPassword(string $token, string $password, string $passwordConfirm)
190 {
191 $rules = [
192 'password' => [
193 'required' => true,
194 'minLength' => 8,
195 'maxLength' => 120,
196 'format' => '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?_&^#])[A-Za-z\d@$!%*?_&^#]{8,}$/'
197 ],
198 'passwordConfirm' => [
199 'required' => true,
200 'minLength' => 8,
201 'maxLength' => 120,
202 'format' => '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?_&^#])[A-Za-z\d@$!%*?_&^#]{8,}$/'
203 ]
204 ];
205
206 try {
207 $validator = new Validator($rules);
208 $validated = $validator->validate([
209 'password' => $password,
210 'passwordConfirm' => $passwordConfirm
211 ]);
212
213 if (!$validated) {
214 throw new MalformedRequestException("Les mot de passe ne respectent pas les règles de validation de mot de passe.");
215 }
216
217 if ($password !== $passwordConfirm) {
218 throw new MalformedRequestException("Les mots de passe ne correspondent pas.");
219 }
220
221 $tokenManager = new PasswordResetTokenDAO($this->getPdo());
222 $token = $tokenManager->findByToken($token);
223
224 if (is_null($token)) {
225 throw new MalformedRequestException("Token invalide");
226 }
227
228 $userManager = new UserDAO($this->getPdo());
229
230 $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
231 $user = $userManager->findById($token->getUserId());
232 $user->setPassword($hashedPassword);
233
234 if (!$userManager->update($user)) {
235 throw new Exception("Erreur lors de la mise à jour du mot de passe", 500);
236 }
237
238 if (!$tokenManager->delete($token->getUserId())) {
239 throw new Exception("Erreur lors de la suppression du token", 500);
240 }
241
242 echo MessageHandler::sendJsonMessage("Votre mot de passe a bien été réinitialisé");
243 exit;
244 } catch (Exception $e) {
245 MessageHandler::sendJsonException($e);
246 }
247 }
248
249
257 public function showRegistrationPage(): void
258 {
259 global $twig;
260 echo $twig->render('sign-up.twig', [
261 "turnstile_siteKey" => CF_TURNSTILE_SITEKEY
262 ]);
263 }
264
278 public function login(?string $email, ?string $password, ?bool $rememberMe, ?string $cloudflareCaptchaToken): bool
279 {
280 $regles = [
281 'email' => [
282 'required' => true,
283 'type' => 'string',
284 'format' => FILTER_VALIDATE_EMAIL
285 ],
286 'password' => [
287 'required' => true,
288 'type' => 'string',
289 'min-length' => 8
290 ]
291 ];
292
293 $validator = new Validator($regles);
294
295 try {
296 if (!$this->verifyCaptcha($cloudflareCaptchaToken)) {
297 throw new AuthenticationException("Impossible de vérifier le captcha");
298 }
299
300 if (!$validator->validate(['email' => $email, 'password' => $password])) {
301 throw new AuthenticationException("Adresse e-mail ou mot de passe invalide");
302 }
303
304 $userManager = new UserDAO($this->getPdo());
305 $user = $userManager->findByEmail($email);
306
307 if (is_null($user)) {
308 throw new AuthenticationException("Adresse e-mail ou mot de passe invalide");
309 }
310
311 if (is_null($user->getEmailVerifiedAt())) {
312 throw new AuthenticationException("Merci de vérifier votre adresse e-mail");
313 }
314
315 if ($user->getDisabled()) {
316 throw new AuthenticationException("Votre compte est désactivé");
317 }
318
319 if (!password_verify($password, $user->getPassword())) {
320 throw new AuthenticationException("Adresse e-mail ou mot de passe invalide");
321 }
322
323 if ($this->authenticate($user)) {
324 if ($rememberMe) {
325 $token = new RememberToken($user->getId());
326 $token->generateToken();
327 $key = $token->generateKey();
328
329 Cookie::set('rmb_token', base64_encode($token->getToken()), $token->getExpiresAt()->getTimestamp());
330 Cookie::set('rmb_usr', base64_encode($user->getId()), $token->getExpiresAt()->getTimestamp());
331 Cookie::set('rmb_key', base64_encode($key), $token->getExpiresAt()->getTimestamp());
332
333 (new RememberTokenDAO($this->getPdo()))->insert($token);
334 }
335
336 echo MessageHandler::sendJsonMessage("Vous êtes connecté en tant que " . ($_SESSION['role'] === 'player' ? "joueur" : "modérateur"));
337 return true;
338 } else {
339 throw new AuthenticationException("Erreur lors de l'authentification");
340 }
341 } catch (Exception $e) {
342 MessageHandler::sendJsonException($e);
343 return false;
344 }
345 }
346
353 private function verifyCaptcha(string $cloudflareCaptchaToken): bool
354 {
355 $url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
356 $data = [
357 'secret' => CF_TURNSTILE_SECRETKEY,
358 'response' => $cloudflareCaptchaToken,
359 'remoteip' => $_SERVER['REMOTE_ADDR']
360 ];
361
362 $curl = curl_init();
363
364 curl_setopt($curl, CURLOPT_URL, $url);
365 curl_setopt($curl, CURLOPT_POST, true);
366 curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
367 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
368 curl_setopt($curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
369
370 $result = curl_exec($curl);
371
372 if (curl_errno($curl)) {
373 curl_close($curl);
374 throw new Exception("Erreur lors de la vérification du captcha : " . curl_error($curl));
375 } else {
376 $response = json_decode($result);
377 curl_close($curl);
378 return $response->success;
379 }
380 }
381
390 private function authenticate(User $user): bool
391 {
392 if (is_null($user->getEmailVerifiedAt())) {
393 return false;
394 }
395
396 if ($user->getDisabled()) {
397 return false;
398 }
399
400 $moderatorManager = new ModeratorDAO($this->getPdo());
401 $moderator = $moderatorManager->findByUserId($user->getId());
402
403 $playerManager = new PlayerDAO($this->getPdo());
404 $player = $playerManager->findByUserId($user->getId());
405
406 if (is_null($player) && is_null($moderator)) {
407 throw new AuthenticationException("Aucun joueur ou modérateur n'est associé à votre compte. Veuillez contacter un administrateur.");
408 }
409
410 if (!is_null($player)) {
411 $articleManager = new ArticleDAO($this->getPdo());
412 $activePfp = $articleManager->findActivePfpByPlayerUuid($player->getUuid());
413 $player->setActivePfp($activePfp == null ? "default-pfp.jpg" : $activePfp->getFilePath());
414 $_SESSION['role'] = 'player';
415 $_SESSION['uuid'] = $player->getUuid();
416 $_SESSION['username'] = $player->getUsername();
417 $_SESSION['comusCoin'] = $player->getComusCoin();
418 $_SESSION['elo'] = $player->getElo();
419 $_SESSION['xp'] = $player->getXp();
420 $_SESSION['basket'] = [];
421 $_SESSION['pfpPath'] = $player->getActivePfp();
422 } else {
423 $_SESSION['role'] = 'moderator';
424 $_SESSION['uuid'] = $moderator->getUuid();
425 $_SESSION['firstName'] = $moderator->getFirstName();
426 $_SESSION['lastName'] = $moderator->getLastName();
427 }
428 return true;
429 }
430
438 public function restoreSession(): bool
439 {
440 $token = Cookie::get('rmb_token');
441 $userId = Cookie::get('rmb_usr');
442 $key = Cookie::get('rmb_key');
443
444 if (!$token && !$userId && !$key) {
445 return false;
446 }
447
448 $tokenManager = new RememberTokenDAO($this->getPdo());
449 $rmbToken = $tokenManager->find(intval(base64_decode($userId)), base64_decode($token));
450
451 if (!$rmbToken) {
452 $this->clearRememberCookies();
453 return false;
454 }
455
456 if (!$rmbToken->isValid(base64_decode($key))) {
457 $this->clearRememberCookies();
458 return false;
459 }
460
461 if ($rmbToken->isExpired()) {
462 $tokenManager->delete($rmbToken);
463 $this->clearRememberCookies();
464 return false;
465 }
466
467 $user = (new UserDAO($this->getPdo()))->findById(intval(base64_decode($userId)));
468
469 if (!$user) {
470 $this->clearRememberCookies();
471 return false;
472 }
473
474 $ok = $this->authenticate($user);
475 if ($ok) {
476 // TODO: Trouver comment déplacer cette fonctionnalité pour qu'il n'y ai pas de code dupliqué...
477 $this->getTwig()->addGlobal('auth', [
478 'pfpPath' => $_SESSION['pfpPath'] ?? null,
479 'loggedIn' => isset($_SESSION['uuid']),
480 'loggedUuid' => $_SESSION['uuid'] ?? null,
481 'loggedUsername' => $_SESSION['username'] ?? null,
482 'loggedComusCoin' => $_SESSION['comusCoin'] ?? null,
483 'loggedElo' => $_SESSION['elo'] ?? null,
484 'loggedXp' => $_SESSION['xp'] ?? null,
485 'role' => $_SESSION['role'] ?? null,
486 'firstName' => $_SESSION['firstName'] ?? null,
487 'lastName' => $_SESSION['lastName'] ?? null,
488 ]);
489 }
490 return $ok;
491 }
492
497 private function clearRememberCookies(): void
498 {
499 Cookie::delete('rmb_token');
500 Cookie::delete('rmb_usr');
501 Cookie::delete('rmb_key');
502 }
503
510 public function logOut(): void
511 {
512 session_unset();
513 session_destroy();
514
515 // Supprimer les cookies de connexion si existants
516 $token = Cookie::get('rmb_token');
517 $userId = Cookie::get('rmb_usr');
518
519 if ($token && $userId) {
520 $tokenManager = new RememberTokenDAO($this->getPdo());
521 $rmbToken = $tokenManager->find(intval(base64_decode($userId)), base64_decode($token));
522 if ($rmbToken) {
523 $tokenManager->delete($rmbToken);
524 }
525 }
526
527 $this->clearRememberCookies();
528
529 header('Location: /login');
530 }
531
551 public function register(?string $username, ?string $email, ?string $password, ?string $passwordConfirm, ?bool $termsOfServiceIsChecked, ?bool $privacyPolicyIsChecked, ?string $cloudflareCaptchaToken): void
552 {
553 $rules = [
554 'username' => [
555 'required' => true,
556 'type' => 'string',
557 'min-length' => 3,
558 'max-length' => 25,
559 'format' => '/^[a-zA-Z0-9_-]+$/'
560 ],
561 'email' => [
562 'required' => true,
563 'type' => 'string',
564 'format' => FILTER_VALIDATE_EMAIL
565 ],
566 'password' => [
567 'required' => true,
568 'type' => 'string',
569 'min-length' => 8,
570 'max-length' => 120,
571 'format' => '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?_&^#])[A-Za-z\d@$!%*?_&^#]{8,}$/'
572 ],
573 'passwordConfirm' => [
574 'required' => true,
575 'type' => 'string',
576 'min-length' => 8,
577 'max-length' => 120,
578 'format' => '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?_&^#])[A-Za-z\d@$!%*?_&^#]{8,}$/'
579 ]
580 ];
581
582 $validator = new Validator($rules);
583
584 try {
585 if (!$this->verifyCaptcha($cloudflareCaptchaToken)) {
586 throw new AuthenticationException("Impossible de vérifier le captcha");
587 }
588
589 if (!$validator->validate(['username' => $username, 'email' => $email, 'password' => $password, 'passwordConfirm' => $passwordConfirm])) {
590 throw new AuthenticationException("Nom d'utilisateur, adresse e-mail ou mot de passe invalide");
591 }
592
593 if ($password !== $passwordConfirm) {
594 throw new AuthenticationException("Les mots de passe ne correspondent pas");
595 }
596
597 if (!$termsOfServiceIsChecked || !$privacyPolicyIsChecked) {
598 throw new AuthenticationException("Vous devez accepter les conditions d'utilisation et la politique de confidentialité pour vous inscrire");
599 }
600
601 // Hash le mot de passe
602 $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
603
604 $userDAO = new UserDAO($this->getPdo());
605 $playerDAO = new PlayerDAO($this->getPdo());
606
607 // Vérifier si l'utilisateur et le joueur existent
608 $existingUser = $userDAO->findByEmail($email) !== null;
609 $existingPlayer = $playerDAO->findByUsername($username) !== null;
610
611 if ($existingUser) {
612 throw new AuthenticationException("L'adresse e-mail est déjà utilisée");
613 }
614
615 if ($existingPlayer) {
616 throw new AuthenticationException("Le nom d'utilisateur est déjà utilisé");
617 }
618
619 // Si l'utilisateur et le joueur n'existent pas, créer l'utilisateur
620 $emailVerifToken = bin2hex(random_bytes(30)); // Générer un token de vérification de l'email
621 $resultUser = $userDAO->createUser($email, $hashedPassword, $emailVerifToken);
622
623 if (!$resultUser) {
624 throw new AuthenticationException("Erreur lors de la création de l'utilisateur");
625 }
626
627 $subject = '🎉 Bienvenue sur Comus Party !';
628 $message =
629 '<p>Merci d\'avoir créé un compte sur notre plateforme de mini-jeux en ligne. 🎮</p>
630 <p>Pour commencer à jouer et rejoindre nos parties endiablées, il ne vous reste plus qu\'une étape :</p>
631 <a href="' . BASE_URL . '/confirm-email/' . urlencode($emailVerifToken) . '">✅ Confirmer votre compte ici</a>
632 <p>À très bientôt dans l’arène ! 🎲,<br>
633 L\'équipe Comus Party 🚀</p>';
634
635 $confirmMail = new Mailer(array($email), $subject, $message);
636 $confirmMail->generateHTMLMessage();
637 $confirmMail->send();
638
639 // Créer le joueur si l'utilisateur est créé avec succès
640 $playerDAO->createPlayer($username, $email);
641
642 $userManager = new UserDAO($this->getPdo());
643 $user = $userManager->findByEmail($email);
644
645 if (is_null($user)) {
646 throw new AuthenticationException("Erreur lors de la création de l'utilisateur");
647 }
648
649 $playerManager = new PlayerDAO($this->getPdo());
650 $player = $playerManager->findWithDetailByUserId($user->getId());
651
652 if (is_null($player)) {
653 throw new AuthenticationException("Erreur lors de la création du joueur");
654 }
655
656 echo MessageHandler::sendJsonMessage("Votre compte a été créé et un mail de confirmation vous a été envoyé. Veuillez confirmer votre compte pour pouvoir vous connecter.");
657 exit;
658
659 } catch (Exception $e) {
660 MessageHandler::sendJsonException($e);
661 }
662 }
663
675 public function confirmEmail(string $emailVerifToken, bool $isLoggedIn): void
676 {
677 $userDAO = new UserDAO($this->getPdo());
678 $user = $userDAO->findByEmailVerifyToken($emailVerifToken);
679 if ($user) {
680 $userDAO->confirmUser($emailVerifToken);
681 MessageHandler::addMessageParametersToSession("Votre compte a bien été confirmé. Vous pouvez maintenant vous connecter.");
682
683 if ($isLoggedIn) {
684 header('Location: /');
685 } else {
686 header('Location: /login');
687 }
688 exit;
689 } else {
690 header('Location: /register');
691 throw new AuthenticationException("La confirmation a echoué");
692 }
693 }
694
695}
static delete(string $name)
Supprime un cookie.
Definition Cookie.php:45
static set(string $name, string $value, ?int $expire=null)
Crée un cookie ou met à jour un cookie existant.
Definition Cookie.php:26
static get(string $name)
Récupère la valeur d'un cookie.
Definition Cookie.php:36
Classe Mailer.
Definition Mailer.php:21
Classe Validator.
Definition Validator.php:17
showLoginPage()
La méthode showLoginPage permet d'afficher la page de connexion.
verifyCaptcha(string $cloudflareCaptchaToken)
Permet de vérifier le token de captcha auprès de Cloudflare.
clearRememberCookies()
Supprime les cookies de connexion.
login(?string $email, ?string $password, ?bool $rememberMe, ?string $cloudflareCaptchaToken)
Traite la demande de connexion de l'utilisateur.
__construct(FilesystemLoader $loader, Environment $twig)
Constructeur de la classe ControllerAuth.
resetPassword(string $token, string $password, string $passwordConfirm)
Réinitialise le mot de passe de l'utilisateur.
sendResetPasswordLink(string $email)
Envoie un lien de réinitialisation de mot de passe à l'adresse e-mail fournie.
showRegistrationPage()
La méthode showRegistrationPage permet d'afficher la page d'inscription.
confirmEmail(string $emailVerifToken, bool $isLoggedIn)
Confirme l'adresse e-mail d'un utilisateur à l'aide du token de vérification.
showResetPasswordPage(string $token)
Affiche la page de réinitialisation de mot de passe.
authenticate(User $user)
Permet d'authentifier l'utilisateur.
restoreSession()
Permet de reprendre la session via un cookie.
showForgotPasswordPage()
Affiche la page de réinitialisation de mot de passe.
getTwig()
Retourne l'attribut twig, correspondant à l'environnement de Twig.
getPdo()
Retourne l'attribut PDO, correspondant à la connexion à la base de données.
getId()
Retourne l'identifiant de l'utilisateur.
getEmailVerifiedAt()
Retourne la date de vérification de l'adresse e-mail.
getDisabled()
Retourne l'état de l'utilisateur (activé ou désactivé)
const CF_TURNSTILE_SITEKEY
Clef du site pour CloudFlare Turnstile.
Definition const.php:31
const CF_TURNSTILE_SECRETKEY
Clef secrète d'authentification pour CloudFlare Turnstile.
Definition const.php:36
const BASE_URL
URL complète de l'application.
Definition const.php:21