Created
February 13, 2020 20:50
-
-
Save bigfoot90/9f7ebaeee1466d6698ef15a2a2284d31 to your computer and use it in GitHub Desktop.
Symfony Telegram Authenticator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Security; | |
use App\Models\Entity\User; | |
use App\Models\ValueObject\TelegramAccount; | |
use Doctrine\ORM\EntityManagerInterface; | |
use Symfony\Component\HttpFoundation\RedirectResponse; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | |
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | |
use Symfony\Component\Security\Core\Exception\AuthenticationException; | |
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; | |
use Symfony\Component\Security\Core\Security; | |
use Symfony\Component\Security\Core\User\UserInterface; | |
use Symfony\Component\Security\Core\User\UserProviderInterface; | |
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; | |
use Symfony\Component\Security\Http\Util\TargetPathTrait; | |
final class TelegramAuthenticator extends AbstractGuardAuthenticator | |
{ | |
use TargetPathTrait; | |
private EntityManagerInterface $entityManager; | |
private UrlGeneratorInterface $urlGenerator; | |
private string $secret; | |
private const REQUIRED_FIELDS = [ | |
'id', | |
'first_name', | |
'last_name', | |
'auth_date', | |
'hash', | |
]; | |
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, string $telegramBotToken) | |
{ | |
$this->entityManager = $entityManager; | |
$this->urlGenerator = $urlGenerator; | |
$this->secret = hash('sha256', $telegramBotToken, true); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function supports(Request $request) | |
{ | |
$route = $request->attributes->get('_route'); | |
$data = $request->query->all(); | |
return $route === '_telegram_login' | |
&& array_intersect(static::REQUIRED_FIELDS, array_keys($data)) === static::REQUIRED_FIELDS; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getCredentials(Request $request) | |
{ | |
$credentials = $request->query->all(); | |
$request->getSession()->set( | |
Security::LAST_USERNAME, | |
$credentials['username'] ?? null | |
); | |
return $credentials; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getUser($credentials, UserProviderInterface $userProvider) | |
{ | |
$this->validate($credentials); | |
$user = $this->entityManager->getRepository(User::class)->findOneBy(['telegram.id' => $credentials['id']]); | |
// $user = $userProvider->loadUserByUsername($credentials['username']); | |
if (!$user) { | |
// If a User was not found in our database, return a new instance created on the fly, | |
// you should then persist it by yourself. | |
return new User(new TelegramAccount( | |
$credentials['id'], | |
$credentials['first_name'].' '.$credentials['last_name'], | |
$credentials['username'] ?? null, | |
'en', | |
$credentials['photo_url'] ?? null | |
)); | |
} | |
return $user; | |
} | |
/** | |
* Check the data integrity. | |
*/ | |
private function validate(array $data) | |
{ | |
// Check for data expiration | |
// This is optional, but recommended step ;) | |
if ((time() - $data['auth_date']) > 3600) { | |
throw new CustomUserMessageAuthenticationException('Login data expired'); | |
} | |
$hash = $data['hash']; | |
unset($data['hash']); | |
ksort($data); | |
$data_check_string = implode("\n", array_map( | |
function ($key, $value) { return "$key=$value"; }, | |
array_keys($data), | |
$data | |
)); | |
// Check for data integrity | |
$hmac = hash_hmac('sha256', $data_check_string, $this->secret); | |
if ($hmac !== $hash) { | |
throw new CustomUserMessageAuthenticationException('Invalid data checksum'); | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function checkCredentials($credentials, UserInterface $user) | |
{ | |
// The check was done by `validate` method | |
return true; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function start(Request $request, AuthenticationException $authException = null) | |
{ | |
$url = $this->getLoginUrl(); | |
return new RedirectResponse($url); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): RedirectResponse | |
{ | |
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) { | |
return new RedirectResponse($targetPath); | |
} | |
return new RedirectResponse($this->urlGenerator->generate('user_profile')); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): RedirectResponse | |
{ | |
if ($request->hasSession()) { | |
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception); | |
} | |
$url = $this->getLoginUrl(); | |
return new RedirectResponse($url); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function supportsRememberMe() | |
{ | |
return true; | |
} | |
protected function getLoginUrl() | |
{ | |
return $this->urlGenerator->generate('login'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How it`s work in this case ?
I got a real problem when Telegram send me reqest without last_name