<?php
namespace XF\Tfa;
use Authy\AuthyFormatException;
use XF\Entity\TfaProvider;
use XF\PrintableException;
use function strval;
class Authy extends AbstractProvider
{
public function renderOptions(TfaProvider $provider)
{
$params = [
'provider' => $provider
];
return \XF::app()->templater()->renderTemplate('admin:two_step_config_authy', $params);
}
public function isUsable()
{
return !empty($this->getProvider()->options['authy_api_key']);
}
public function requiresConfig()
{
return true;
}
public function handleConfig(
\XF\Mvc\Controller $controller, \XF\Entity\TfaProvider $provider, \XF\Entity\User $user, array &$config
)
{
if ($controller->filter('setup', 'bool'))
{
$email = $controller->filter('email', 'str');
if (!$email)
{
$email = \XF::visitor()->email;
}
if (!$email)
{
throw $controller->exception(
$controller->error(\XF::phrase('please_enter_valid_email'))
);
}
$dialCode = $controller->filter('dial_code', 'uint');
$intlNumb = $controller->filter('intl_numb', 'str');
$number = preg_replace('/^\+' . $dialCode .'/', '', $intlNumb);
$authy = $this->getAuthy();
$authyUser = $authy->registerUser($email, $number, $dialCode);
if (!$authyUser->ok())
{
$errors = (array)$authyUser->errors();
if (!$errors)
{
$errors = [\XF::phrase('something_went_wrong_please_try_again')];
}
throw $controller->exception(
$controller->error($errors)
);
}
$config['authy_id'] = $authyUser->id();
return null;
}
else
{
$viewParams = [
'provider' => $provider,
'config' => $config
];
return $controller->view('XF:Account\TwoStepAuthyConfig', 'account_two_step_authy_config', $viewParams);
}
}
public function generateInitialData(\XF\Entity\User $user, array $config = [])
{
return $config;
}
public function trigger($context, \XF\Entity\User $user, array &$config, \XF\Http\Request $request)
{
return [];
}
public function render($context, \XF\Entity\User $user, array $config, array $triggerData)
{
$uuid = null;
if ($context == 'login')
{
$authy = $this->getAuthy();
$authyRequest = $authy->createApprovalRequest(
$config['authy_id'],
strval(\XF::phrase('approve_login_request_for_x_at_y', [
'username' => $user->username,
'boardTitle' => \XF::options()->boardTitle
])),
[
'details' => [
'user_id' => $user->user_id,
'username' => $user->username
]
]
);
if (!$authyRequest->ok())
{
$errorJson = json_encode($authyRequest->errors());
$errors = json_decode($errorJson, true);
throw new PrintableException($errors);
}
$approvalRequest = $authyRequest->bodyvar('approval_request');
$uuid = $approvalRequest->uuid;
}
$params = [
'config' => $config,
'context' => $context,
'uuid' => $uuid
];
return \XF::app()->templater()->renderTemplate('public:two_step_authy', $params);
}
public function verify($context, \XF\Entity\User $user, array &$config, \XF\Http\Request $request)
{
$this->bypassFailedAttemptLog = false;
if ($context == 'setup')
{
$code = $request->filter('code', 'str');
$code = preg_replace('/[^0-9]/', '', $code);
if (!$code)
{
return false;
}
$authy = $this->getAuthy();
try
{
$authyVerification = $authy->verifyToken($config['authy_id'], $code);
}
catch (AuthyFormatException $e)
{
return false;
}
if (!$authyVerification->ok())
{
return false;
}
}
else
{
$uuid = $request->filter('uuid', 'str');
if (!$uuid)
{
throw new PrintableException('authy_no_uuid');
}
$authy = $this->getAuthy();
$authyApprovalCheck = $authy->getApprovalRequest($uuid);
if (!$authyApprovalCheck->ok())
{
throw new PrintableException('authy_no_approval_request');
}
$approvalRequest = $authyApprovalCheck->bodyvar('approval_request');
if ($approvalRequest->status == 'pending')
{
$this->bypassFailedAttemptLog = true;
return false;
}
else if ($approvalRequest->status == 'denied')
{
throw new PrintableException('authy_denied');
}
}
return true;
}
/**
* @return \Authy\AuthyApi
*/
protected function getAuthy()
{
$provider = $this->getProvider();
return new \Authy\AuthyApi($provider->options['authy_api_key']);
}
}