<?php declare(strict_types=1); namespace api\validators; use Carbon\FactoryImmutable; use common\helpers\Error as E; use common\models\Account; use OTPHP\TOTP; use Psr\Clock\ClockInterface; use RangeException; use yii\base\InvalidConfigException; use yii\validators\Validator; final class TotpValidator extends Validator { public ?Account $account = null; public $skipOnEmpty = false; private ClockInterface $clock; /** * @throws InvalidConfigException */ public function init(): void { parent::init(); if (!$this->account instanceof Account) { throw new InvalidConfigException('This validator must be instantiated with the account param'); } if (empty($this->account->otp_secret)) { throw new InvalidConfigException('account should have not empty otp_secret'); } $this->clock = FactoryImmutable::getDefaultInstance(); } public function setClock(ClockInterface $clock): void { $this->clock = $clock; } protected function validateValue($value): ?array { try { // @phpstan-ignore argument.type (it is non empty, its checked in the init method) $totp = TOTP::create($this->account->otp_secret); // @phpstan-ignore argument.type,argument.type,argument.type (all types are fine, they're just not declared well) if (!$totp->verify((string)$value, $this->clock->now()->getTimestamp(), $totp->getPeriod() - 1)) { return [E::TOTP_INCORRECT, []]; } } catch (RangeException) { return [E::TOTP_INCORRECT, []]; } return null; } }