account = $account; parent::__construct($config); } public function rules() { $bothScenarios = [self::SCENARIO_ACTIVATE, self::SCENARIO_DISABLE]; return [ ['account', 'validateOtpDisabled', 'on' => self::SCENARIO_ACTIVATE], ['account', 'validateOtpEnabled', 'on' => self::SCENARIO_DISABLE], ['token', 'required', 'message' => E::OTP_TOKEN_REQUIRED, 'on' => $bothScenarios], ['token', TotpValidator::class, 'account' => $this->account, 'window' => 30, 'on' => $bothScenarios], ['password', PasswordRequiredValidator::class, 'account' => $this->account, 'on' => $bothScenarios], ]; } public function getCredentials(): array { if (empty($this->account->otp_secret)) { $this->setOtpSecret(); } $provisioningUri = $this->getTotp()->getProvisioningUri(); return [ 'qr' => base64_encode($this->drawQrCode($provisioningUri)), 'uri' => $provisioningUri, 'secret' => $this->account->otp_secret, ]; } public function activate(): bool { if (!$this->validate()) { return false; } $account = $this->account; $account->is_otp_enabled = true; if (!$account->save()) { throw new ErrorException('Cannot enable otp for account'); } return true; } public function disable(): bool { if (!$this->validate()) { return false; } $account = $this->account; $account->is_otp_enabled = false; $account->otp_secret = null; if (!$account->save()) { throw new ErrorException('Cannot disable otp for account'); } return true; } public function validateOtpDisabled($attribute) { if ($this->account->is_otp_enabled) { $this->addError($attribute, E::OTP_ALREADY_ENABLED); } } public function validateOtpEnabled($attribute) { if (!$this->account->is_otp_enabled) { $this->addError($attribute, E::OTP_NOT_ENABLED); } } public function getAccount(): Account { return $this->account; } /** * @return TOTP */ public function getTotp(): TOTP { $totp = new TOTP($this->account->email, $this->account->otp_secret); $totp->setIssuer('Ely.by'); return $totp; } public function drawQrCode(string $content): string { $renderer = new Svg(); $renderer->setHeight(256); $renderer->setWidth(256); $renderer->setForegroundColor(new Rgb(32, 126, 92)); $renderer->setMargin(0); $renderer->addDecorator(new ElyDecorator()); $writer = new Writer($renderer); return $writer->writeString($content, Encoder::DEFAULT_BYTE_MODE_ECODING, ErrorCorrectionLevel::H); } protected function setOtpSecret(): void { $this->account->otp_secret = trim(Base32::encode(random_bytes(32)), '='); if (!$this->account->save()) { throw new ErrorException('Cannot set account otp_secret'); } } }