src/Controller/AuthController.php line 148

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use Doctrine\DBAL\Connection;
  4. use Symfony\Component\HttpFoundation\Cookie;
  5. use Symfony\Component\HttpFoundation\JsonResponse;
  6. use Symfony\Component\HttpFoundation\Request;
  7. use Symfony\Component\HttpFoundation\Response;
  8. use Symfony\Component\Routing\Annotation\Route;
  9. class AuthController extends BaseController
  10. {
  11.     /**
  12.      * @Route("/auth/register", name="auth_register", methods={"POST"})
  13.      */
  14.     public function register(Request $requestConnection $db): Response
  15.     {
  16.         $this->ensureSchema($db);
  17.         $payload $this->payload($request);
  18.         $login trim((string) ($payload['login'] ?? ''));
  19.         $email trim((string) ($payload['email'] ?? ''));
  20.         $password = (string) ($payload['password'] ?? '');
  21.         $passwordRepeat = (string) ($payload['passwordRepeat'] ?? $payload['password_repeat'] ?? '');
  22.         $redirect $this->safeLocalRedirect((string) ($payload['redirect'] ?? ''));
  23.         if ($login === '' || $email === '' || $password === '') {
  24.             return $this->error('Все поля обязательны'422);
  25.         }
  26.         if (mb_strlen($login) < || mb_strlen($login) > 50) {
  27.             return $this->error('Логин должен быть от 3 до 50 символов'422);
  28.         }
  29.         if (!filter_var($emailFILTER_VALIDATE_EMAIL)) {
  30.             return $this->error('Некорректный e-mail'422);
  31.         }
  32.         if (mb_strlen($password) < 6) {
  33.             return $this->error('Пароль короче 6 символов'422);
  34.         }
  35.         if ($passwordRepeat !== '' && $password !== $passwordRepeat) {
  36.             return $this->error('Пароли не совпадают'422);
  37.         }
  38.         $exists $db->fetchOne(
  39.             'SELECT 1 FROM users WHERE login = ? OR email = ? LIMIT 1',
  40.             [$login$email]
  41.         );
  42.         if ($exists) {
  43.             return $this->error('Такой логин или e-mail уже зарегистрирован'409);
  44.         }
  45.         $plainToken bin2hex(random_bytes(32));
  46.         $userHash 'local:' $login;
  47.         $db->insert('users', [
  48.             'login' => $login,
  49.             'email' => $email,
  50.             'password_hash' => password_hash($passwordPASSWORD_DEFAULT),
  51.             'balance' => 0,
  52.             'hash' => $userHash,
  53.             'img' => '/assets/profile.svg',
  54.             'auth_token_hash' => hash('sha256'$plainToken),
  55.             'ip' => (string) $request->getClientIp(),
  56.             'ref_id' => 0,
  57.             'refs' => 0,
  58.             'created_at' => (new \DateTimeImmutable())->format('Y-m-d H:i:s'),
  59.         ]);
  60.         $request->getSession()->migrate(true);
  61.         $request->getSession()->set('hash'$userHash);
  62.         return $this->tokenResponse($plainToken, [
  63.             'ok' => true,
  64.             'redirect' => $redirect,
  65.             'user' => [
  66.                 'login' => $login,
  67.                 'email' => $email,
  68.                 'balance' => 0,
  69.             ],
  70.         ], $request);
  71.     }
  72.     /**
  73.      * @Route("/auth/login", name="auth_login", methods={"POST"})
  74.      */
  75.     public function login(Request $requestConnection $db): Response
  76.     {
  77.         $this->ensureSchema($db);
  78.         $payload $this->payload($request);
  79.         $login trim((string) ($payload['login'] ?? ''));
  80.         $password = (string) ($payload['password'] ?? '');
  81.         $redirect $this->safeLocalRedirect((string) ($payload['redirect'] ?? ''));
  82.         if ($login === '' || $password === '') {
  83.             return $this->error('Введите логин и пароль'422);
  84.         }
  85.         $user $db->fetchAssociative(
  86.             'SELECT * FROM users WHERE login = ? OR email = ? LIMIT 1',
  87.             [$login$login]
  88.         );
  89.         if (!$user || !password_verify($password, (string) $user['password_hash'])) {
  90.             return $this->error('Неверный логин или пароль'401);
  91.         }
  92.         $plainToken bin2hex(random_bytes(32));
  93.         $db->update('users', ['auth_token_hash' => hash('sha256'$plainToken)], ['id' => $user['id']]);
  94.         $request->getSession()->migrate(true);
  95.         $request->getSession()->set('hash'$user['hash']);
  96.         return $this->tokenResponse($plainToken, [
  97.             'ok' => true,
  98.             'redirect' => $redirect,
  99.             'user' => $this->publicUser($user),
  100.         ], $request);
  101.     }
  102.     /**
  103.      * @Route("/auth/logout", name="auth_logout", methods={"POST"})
  104.      */
  105.     public function logout(Request $requestConnection $db): Response
  106.     {
  107.         $user $this->getAuthorizedUser($request$db);
  108.         if ($user) {
  109.             $db->update('users', ['auth_token_hash' => null], ['id' => $user['id']]);
  110.         }
  111.         $request->getSession()->invalidate();
  112.         $response = new JsonResponse(['ok' => true]);
  113.         $response->headers->clearCookie('auth_token''/');
  114.         return $response;
  115.     }
  116.     /**
  117.      * @Route("/auth/me", name="auth_me", methods={"GET"})
  118.      */
  119.     public function me(Request $requestConnection $db): Response
  120.     {
  121.         $this->ensureSchema($db);
  122.         return new JsonResponse([
  123.             'user' => $this->publicUser($this->getAuthorizedUser($request$db)),
  124.         ], 200, [], false);
  125.     }
  126.     private function payload(Request $request): array
  127.     {
  128.         $decoded json_decode((string) $request->getContent(), true);
  129.         if (is_array($decoded)) {
  130.             return $decoded;
  131.         }
  132.         return $request->request->all();
  133.     }
  134.     private function error(string $messageint $status): JsonResponse
  135.     {
  136.         return new JsonResponse(['error' => $message], $status, [], false);
  137.     }
  138.     private function safeLocalRedirect(string $redirect): string
  139.     {
  140.         $redirect trim($redirect);
  141.         if ($redirect === '' || $redirect[0] !== '/' || strpos($redirect'//') === 0) {
  142.             return '';
  143.         }
  144.         return $redirect;
  145.     }
  146.     private function tokenResponse(string $plainToken, array $payloadRequest $request): JsonResponse
  147.     {
  148.         $response = new JsonResponse($payload200, [], false);
  149.         $response->headers->setCookie(Cookie::create(
  150.             'auth_token',
  151.             $plainToken,
  152.             new \DateTimeImmutable('+7 days'),
  153.             '/',
  154.             null,
  155.             $request->isSecure(),
  156.             true,
  157.             false,
  158.             Cookie::SAMESITE_STRICT
  159.         ));
  160.         return $response;
  161.     }
  162.     private function ensureSchema(Connection $db): void
  163.     {
  164.         $platform $db->getDatabasePlatform()->getName();
  165.         if (strpos($platform'mysql') !== false) {
  166.             $db->executeStatement(
  167.                 "CREATE TABLE IF NOT EXISTS users (
  168.                     id INT AUTO_INCREMENT NOT NULL,
  169.                     login VARCHAR(50) NOT NULL,
  170.                     email VARCHAR(180) NOT NULL,
  171.                     password_hash VARCHAR(255) NOT NULL,
  172.                     balance DECIMAL(10, 2) NOT NULL DEFAULT 0,
  173.                     hash VARCHAR(50) NOT NULL,
  174.                     img VARCHAR(512) NOT NULL DEFAULT '/assets/profile.svg',
  175.                     auth_token VARCHAR(225) DEFAULT NULL,
  176.                     auth_token_hash VARCHAR(64) DEFAULT NULL,
  177.                     ip VARCHAR(50) DEFAULT '',
  178.                     ref_id INT NOT NULL DEFAULT 0,
  179.                     refs INT NOT NULL DEFAULT 0,
  180.                     created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  181.                     UNIQUE INDEX uniq_users_login (login),
  182.                     UNIQUE INDEX uniq_users_email (email),
  183.                     UNIQUE INDEX uniq_users_hash (hash),
  184.                     UNIQUE INDEX uniq_users_auth_token_hash (auth_token_hash),
  185.                     PRIMARY KEY(id)
  186.                 ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB"
  187.             );
  188.             return;
  189.         }
  190.         $db->executeStatement(
  191.             "CREATE TABLE IF NOT EXISTS users (
  192.                 id INTEGER PRIMARY KEY AUTOINCREMENT,
  193.                 login VARCHAR(50) NOT NULL UNIQUE,
  194.                 email VARCHAR(180) NOT NULL UNIQUE,
  195.                 password_hash VARCHAR(255) NOT NULL,
  196.                 balance NUMERIC NOT NULL DEFAULT 0,
  197.                 hash VARCHAR(50) NOT NULL UNIQUE,
  198.                 img VARCHAR(512) NOT NULL DEFAULT '/assets/profile.svg',
  199.                 auth_token VARCHAR(225) DEFAULT NULL,
  200.                 auth_token_hash VARCHAR(64) DEFAULT NULL UNIQUE,
  201.                 ip VARCHAR(50) DEFAULT '',
  202.                 ref_id INTEGER NOT NULL DEFAULT 0,
  203.                 refs INTEGER NOT NULL DEFAULT 0,
  204.                 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
  205.             )"
  206.         );
  207.     }
  208. }