diff --git a/src/Entity/RecoveryCode.php b/src/Entity/RecoveryCode.php index e1785d1..76db90e 100644 --- a/src/Entity/RecoveryCode.php +++ b/src/Entity/RecoveryCode.php @@ -37,6 +37,11 @@ class RecoveryCode */ private string $secret; + /** + * @ORM\Column(type="boolean") + */ + private bool $downloaded = false; + public function getId(): int { return $this->id; @@ -61,4 +66,14 @@ public function setSecret(string $secret): void { $this->secret = $secret; } + + public function isDownloaded(): bool + { + return $this->downloaded; + } + + public function setDownloaded(bool $downloaded): void + { + $this->downloaded = $downloaded; + } } diff --git a/src/Handler/DownloadedRecoveryCodeHandler.php b/src/Handler/DownloadedRecoveryCodeHandler.php new file mode 100644 index 0000000..82e0a8f --- /dev/null +++ b/src/Handler/DownloadedRecoveryCodeHandler.php @@ -0,0 +1,58 @@ +doctrine = $doctrine; + $this->tokenStorage = $tokenStorage; + } + + /** + * Set downloaded to true on recovery code(s) for logged on user. + */ + public function __invoke(DownloadedRecoveryCode $recoveryCode): void + { + $user = $this->tokenStorage->getToken()->getUser(); + + if ($user instanceof TwoFactorUserInterface) { + $this->setDownloaded($user->getGoogleAuthenticatorSecret(), $recoveryCode->getValue()); + } else { + throw new \RuntimeException('Unable to set downloaded on recovery codes for non-2fa users'); + } + } + + /** + * Set downloaded to true on recovery code(s) for the given User. + */ + private function setDownloaded(string $secret, bool $value): void + { + $currentCodes = $this->doctrine->getRepository(RecoveryCode::class)->findBy(['secret' => $secret]); + $manager = $this->doctrine->getManagerForClass(RecoveryCode::class); + + array_walk($currentCodes, fn (RecoveryCode $recoveryCode) => $recoveryCode->setDownloaded($value)); + + $manager->flush(); + } +} diff --git a/src/Message/DownloadedRecoveryCode.php b/src/Message/DownloadedRecoveryCode.php new file mode 100644 index 0000000..9a1c74d --- /dev/null +++ b/src/Message/DownloadedRecoveryCode.php @@ -0,0 +1,38 @@ +value = $value; + } + + public function getValue(): bool + { + return $this->value; + } +} diff --git a/tests/Handler/DownloadedRecoveryCodeHandlerTest.php b/tests/Handler/DownloadedRecoveryCodeHandlerTest.php new file mode 100644 index 0000000..2860cca --- /dev/null +++ b/tests/Handler/DownloadedRecoveryCodeHandlerTest.php @@ -0,0 +1,61 @@ +createMock(ManagerRegistry::class); + $manager = $this->createMock(EntityManager::class); + $repository = $this->createMock(EntityRepository::class); + $tokenStorage = new TokenStorage(); + $user = new User(); + $recoveryCodes = [ + new RecoveryCodeEntity(), + new RecoveryCodeEntity(), + new RecoveryCodeEntity() + ]; + + $user->setGoogleAuthenticatorSecret('verysecret!'); + $tokenStorage->setToken(new JWTUserToken([], $user)); + + $repository + ->method('findBy') + ->willReturn($recoveryCodes); + + $manager + ->expects($this->exactly(1)) + ->method('flush'); + + $doctrine + ->method('getManagerForClass') + ->willReturn($manager); + + $doctrine + ->method('getRepository') + ->willReturn($repository); + + $handler = new DownloadedRecoveryCodeHandler($doctrine, $tokenStorage); + $handler(new DownloadedRecoveryCode(true)); + foreach ($recoveryCodes as $code) { + $this->assertSame(true, $code->isDownloaded()); + } + } +}