TwoFactorAuthRateLimiter.php 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. <?php
  2. /*
  3. * This file is part of Packagist.
  4. *
  5. * (c) Jordi Boggiano <j.boggiano@seld.be>
  6. * Nils Adermann <naderman@naderman.de>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Packagist\WebBundle\Security;
  12. use Predis\Client;
  13. use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvent;
  14. use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvents;
  15. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  16. use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
  17. /**
  18. * @author Colin O'Dell <colinodell@gmail.com>
  19. */
  20. class TwoFactorAuthRateLimiter implements EventSubscriberInterface
  21. {
  22. const MAX_ATTEMPTS = 5;
  23. const RATE_LIMIT_DURATION = 15; // in minutes
  24. /** @var Client */
  25. protected $redis;
  26. public function __construct(Client $redis)
  27. {
  28. $this->redis = $redis;
  29. }
  30. public function onAuthAttempt(TwoFactorAuthenticationEvent $event)
  31. {
  32. $key = '2fa-failures:'.$event->getToken()->getUsername();
  33. $count = (int)$this->redis->get($key);
  34. if ($count >= self::MAX_ATTEMPTS) {
  35. throw new CustomUserMessageAuthenticationException(sprintf('Too many authentication failures. Try again in %d minutes.', self::RATE_LIMIT_DURATION));
  36. }
  37. }
  38. public function onAuthFailure(TwoFactorAuthenticationEvent $event)
  39. {
  40. $key = '2fa-failures:'.$event->getToken()->getUsername();
  41. $this->redis->multi();
  42. $this->redis->incr($key);
  43. $this->redis->expire($key, self::RATE_LIMIT_DURATION * 60);
  44. $this->redis->exec();
  45. }
  46. /**
  47. * {@inheritDoc}
  48. */
  49. public static function getSubscribedEvents()
  50. {
  51. return [
  52. TwoFactorAuthenticationEvents::FAILURE => 'onAuthFailure',
  53. TwoFactorAuthenticationEvents::ATTEMPT => 'onAuthAttempt',
  54. ];
  55. }
  56. }