. */ declare(strict_types=1); namespace App\Security\TwoFactor; use App\Entity\UserSystem\WebauthnKey; use Doctrine\ORM\EntityManagerInterface; use Jbtronics\TFAWebauthn\Services\UserPublicKeyCredentialSourceRepository; use Jbtronics\TFAWebauthn\Services\WebauthnProvider; use Scheb\TwoFactorBundle\Security\TwoFactor\AuthenticationContextInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorFormRendererInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderInterface; use Symfony\Component\DependencyInjection\Attribute\AsDecorator; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated; /** * This class decorates the Webauthn TwoFactorProvider and adds additional logic which allows us to set a last used date * on the used webauthn key, which can be viewed in the user settings. */ #[AsDecorator('jbtronics_webauthn_tfa.two_factor_provider')] class WebauthnKeyLastUseTwoFactorProvider implements TwoFactorProviderInterface { public function __construct( #[AutowireDecorated] private TwoFactorProviderInterface $decorated, private EntityManagerInterface $entityManager, #[Autowire(service: 'jbtronics_webauthn_tfa.user_public_key_source_repo')] private UserPublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, #[Autowire(service: 'jbtronics_webauthn_tfa.webauthn_provider')] private WebauthnProvider $webauthnProvider, ) { } public function beginAuthentication(AuthenticationContextInterface $context): bool { return $this->decorated->beginAuthentication($context); } public function prepareAuthentication(object $user): void { $this->decorated->prepareAuthentication($user); } public function validateAuthenticationCode(object $user, string $authenticationCode): bool { //Try to extract the used webauthn key from the code $webauthnKey = $this->getWebauthnKeyFromCode($authenticationCode); //Perform the actual validation like normal $tmp = $this->decorated->validateAuthenticationCode($user, $authenticationCode); //Update the last used date of the webauthn key, if the validation was successful if($tmp && $webauthnKey !== null) { $webauthnKey->updateLastTimeUsed(); $this->entityManager->flush(); } return $tmp; } public function getFormRenderer(): TwoFactorFormRendererInterface { return $this->decorated->getFormRenderer(); } private function getWebauthnKeyFromCode(string $authenticationCode): ?WebauthnKey { $publicKeyCredentialLoader = $this->webauthnProvider->getPublicKeyCredentialLoader(); //Try to load the public key credential from the code $publicKeyCredential = $publicKeyCredentialLoader->load($authenticationCode); //Find the credential source for the given credential id $publicKeyCredentialSource = $this->publicKeyCredentialSourceRepository->findOneByCredentialId($publicKeyCredential->rawId); //If the credential source is not an instance of WebauthnKey, return null if(!($publicKeyCredentialSource instanceof WebauthnKey)) { return null; } return $publicKeyCredentialSource; } }