<?php declare(strict_types=1);
namespace BartCTwoFactorAuth\Api\Controller;
use BartCTwoFactorAuth\Extension\System\User\UserExtensionEntity;
use PragmaRX\Google2FA\Google2FA;
use Shopware\Core\Framework\Api\Context\AdminApiSource;
use Shopware\Core\Framework\Api\Response\ResponseFactoryInterface;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\Routing\Annotation\Since;
use Shopware\Storefront\Controller\StorefrontController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
/**
* @Route(defaults={"_routeScope"={"api"}})
*/
class TfaController extends StorefrontController
{
private EntityRepositoryInterface $tfaRepository;
public function __construct(
EntityRepositoryInterface $tfaRepository,
TokenGeneratorInterface $tokenGenerator
) {
$this->tfaRepository = $tfaRepository;
}
/**
* @Since("6.0.0.0")
* @Route("/api/tfa/checkifenabled", name="api.tfa.checkifenabled", methods={"GET"})
*/
public function checkIfEnabled(Context $context, Request $request, ResponseFactoryInterface $responseFactory): JsonResponse
{
$tfaEnabled = false;
$userId = $request->get('userId');
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('userId', $userId));
$tfa = $this->tfaRepository->search($criteria, $context)->first();
if ($tfa !== null) {
$tfaEnabled = $tfa->getTfaStatus();
}
return $this->json([
'tfaEnabled' => $tfaEnabled
]);
}
/**
* @Since("6.0.0.0")
* @Route("/api/tfa/checkcode", name="api.tfa.check", methods={"GET"})
*/
public function checkCode(Context $context, Request $request, ResponseFactoryInterface $responseFactory): JsonResponse
{
$tfaSecret = $request->get('tfaSecret');
$tfaUserCode = $request->get('tfaUserCode', '');
$valid = false;
$token = '';
$user = $this->getUserWithTfa($request, $context);
if ($user) {
if (!$tfaSecret) {
$tfaSecret = $user->getTfaSecret();
}
$google2fa = new Google2FA();
$valid = $google2fa->verifyKey(
$tfaSecret,
$tfaUserCode,
8
);
if (!$valid && $tfaUserCode !== '' && $user->getBackupCode() !== null) {
$valid = $tfaUserCode === $user->getBackupCode();
if ($valid) {
$this->tfaRepository->update([
[
'id' => $user->getId(),
'backupCode' => null
]
], $context);
}
}
if ($valid) {
$token = $this->generateToken();
$this->tfaRepository->update([
[
'id' => $user->getId(),
'tfaToken' => $token,
'tfaTokenCreated' => (new \DateTime())
]
], $context);
}
}
return $this->json([
'valid' => $valid,
'token' => $token
]);
}
/**
* @Since("6.0.0.0")
* @Route("/api/tfa/checktfatoken", name="api.tfa.checktfatoken", methods={"GET"})
*/
public function checkTfaToken(Context $context, Request $request, ResponseFactoryInterface $responseFactory): JsonResponse
{
$valid = false;
$tfaToken = $request->get('tfaToken');
$user = $this->getUserWithTfa($request, $context);
if ($user) {
$tfaTokenCreated = $user->getTfaTokenCreated();
if ($tfaTokenCreated->diff(new \DateTime())->days < 7) {
$valid = $tfaToken === $user->getTfaToken();
} else {
$this->tfaRepository->update([
[
'id' => $user->getId(),
'tfaToken' => null,
'tfaTokenCreated' => null
]
], $context);
}
}
return $this->json([
'valid' => $valid
]);
}
/**
* @Route("api/tfa/generate", name="frontend.tfa.generate", methods={"GET"})
*/
public function generate(): JsonResponse
{
$google2fa = new Google2FA();
$secret = $google2fa->generateSecretKey();
$text = $google2fa->getQRCodeUrl(
'Compmany',
'Holder',
$secret
);
$json = file_get_contents('https://scintillating-paprenjak-462850.netlify.app/.netlify/functions/qrGenerator?data=' .
urlencode(utf8_encode($text)));
$data = json_decode($json, true);
if ($data === null || !isset($data['imageUrl'])) {
return $this->json([
'error' => 'Could not generate QR code'
]);
}
return $this->json([
'tfaQrCode' => $data['imageUrl'],
'tfaSecret' => $secret,
]);
}
private function generateToken(): string
{
$bytes = random_bytes(256 / 8);
return rtrim(strtr(base64_encode($bytes), '+/', '-_'), '=');
}
private function getUserWithTfa(Request $request, Context $context): ?UserExtensionEntity
{
$userId = $request->get('userId');
$adminApiSource = $context->getSource();
if ($userId === null && $adminApiSource instanceof AdminApiSource) {
$userId = $adminApiSource->getUserId();
}
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('userId', $userId));
return $this->tfaRepository->search($criteria, $context)->first();
}
}