Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

+199 -174
View File
@@ -11,21 +11,46 @@
namespace IPS\MFA\Authy;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DomainException;
use IPS\DateTime;
use IPS\Helpers\Form;
use IPS\Helpers\Form\Address;
use IPS\Helpers\Form\CheckboxSet;
use IPS\Helpers\Form\Custom;
use IPS\Helpers\Form\Radio;
use IPS\Helpers\Form\Text;
use IPS\Http\Url;
use IPS\Log;
use IPS\Member;
use IPS\Member\Group;
use IPS\MFA\Authy\Exception as AuthyException;
use IPS\MFA\MFAHandler;
use IPS\Output;
use IPS\Request;
use IPS\Session;
use IPS\Settings;
use IPS\Theme;
use function count;
use function defined;
use function in_array;
use function intval;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Multi Factor Authentication Handler for Authy
*/
class _Handler extends \IPS\MFA\MFAHandler
class Handler extends MFAHandler
{
/**
* @brief Key
*/
protected $key = 'authy';
protected string $key = 'authy';
/* !Setup */
@@ -34,29 +59,29 @@ class _Handler extends \IPS\MFA\MFAHandler
*
* @return bool
*/
public function isEnabled()
public function isEnabled(): bool
{
return \IPS\Settings::i()->authy_enabled;
return Settings::i()->authy_enabled;
}
/**
* Member *can* use this handler (even if they have not yet configured it)
*
* @param \IPS\Member $member The member
* @param Member $member The member
* @return bool
*/
public function memberCanUseHandler( \IPS\Member $member )
public function memberCanUseHandler( Member $member ): bool
{
return \IPS\Settings::i()->authy_groups == '*' or $member->inGroup( explode( ',', \IPS\Settings::i()->authy_groups ) );
return Settings::i()->authy_groups == '*' or $member->inGroup( explode( ',', Settings::i()->authy_groups ) );
}
/**
* Member has configured this handler
*
* @param \IPS\Member $member The member
* @param Member $member The member
* @return bool
*/
public function memberHasConfiguredHandler( \IPS\Member $member )
public function memberHasConfiguredHandler( Member $member ): bool
{
return isset( $member->mfa_details['authy'] ) and $member->mfa_details['authy']['setup'];
}
@@ -64,17 +89,17 @@ class _Handler extends \IPS\MFA\MFAHandler
/**
* Show a setup screen
*
* @param \IPS\Member $member The member
* @param Member $member The member
* @param bool $showingMultipleHandlers Set to TRUE if multiple options are being displayed
* @param \IPS\Http\Url $url URL for page
* @param Url $url URL for page
* @return string
*/
public function configurationScreen( \IPS\Member $member, $showingMultipleHandlers, \IPS\Http\Url $url )
public function configurationScreen( Member $member, bool $showingMultipleHandlers, Url $url ): string
{
$mfaDetails = $member->mfa_details;
/* Starting again? */
if ( isset( $mfaDetails['authy']['pendingId'] ) and isset( \IPS\Request::i()->_new ) )
if ( isset( $mfaDetails['authy']['pendingId'] ) and isset( Request::i()->_new ) )
{
unset( $mfaDetails['authy']['pendingId'] );
$member->mfa_details = $mfaDetails;
@@ -85,42 +110,42 @@ class _Handler extends \IPS\MFA\MFAHandler
if ( isset( $mfaDetails['authy'] ) and isset( $mfaDetails['authy']['pendingId'] ) and !isset( $_SESSION['authyConfigureError'] ) )
{
/* Asking for a text or call instead? */
$availableMethods = explode( ',', \IPS\Settings::i()->authy_setup );
if ( isset( \IPS\Request::i()->authy_method ) and $mfaDetails['authy']['setupMethod'] == 'authy' and \in_array( \IPS\Request::i()->authy_method, $availableMethods ) )
$availableMethods = explode( ',', Settings::i()->authy_setup );
if ( isset( Request::i()->authy_method ) and $mfaDetails['authy']['setupMethod'] == 'authy' and in_array( Request::i()->authy_method, $availableMethods ) )
{
try
{
/* Send text or make call */
if ( \IPS\Request::i()->authy_method == 'phone' )
if ( Request::i()->authy_method == 'phone' )
{
static::totp( "call/{$mfaDetails['authy']['pendingId']}", 'get', array( 'force' => 'true' ) );
}
elseif ( \IPS\Request::i()->authy_method == 'sms' )
elseif ( Request::i()->authy_method == 'sms' )
{
static::totp( "sms/{$mfaDetails['authy']['pendingId']}", 'get', array( 'force' => 'true' ) );
}
/* Update details */
$mfaDetails['authy']['setupMethod'] = \IPS\Request::i()->authy_method;
$mfaDetails['authy']['setupMethod'] = Request::i()->authy_method;
$member->mfa_details = $mfaDetails;
$member->save();
}
catch ( \Exception $e )
{
\IPS\Log::log( $e, 'authy' );
$_SESSION['authyAuthError'] = \IPS\Member::loggedIn()->isAdmin() ? $e->getMessage() : 'authy_error';
Log::log( $e, 'authy' );
$_SESSION['authyAuthError'] = Member::loggedIn()->isAdmin() ? $e->getMessage() : 'authy_error';
}
}
/* Display */
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->authyAuthenticate( $mfaDetails['authy']['setupMethod'], TRUE, isset( $_SESSION['authyAuthError'] ) ? $_SESSION['authyAuthError'] : 'authy_error', TRUE, explode( ',', \IPS\Settings::i()->authy_setup ), NULL, $url );
return Theme::i()->getTemplate( 'login', 'core', 'global' )->authyAuthenticate( $mfaDetails['authy']['setupMethod'], TRUE, $_SESSION['authyAuthError'] ?? 'authy_error', TRUE, explode( ',', Settings::i()->authy_setup ), NULL, $url );
}
else
{
/* If they have used their allowed attempts, make them wait */
if ( isset( $mfaDetails['authy'] ) and isset( $mfaDetails['authy']['changeAttempts'] ) and $mfaDetails['authy']['changeAttempts'] >= \IPS\Settings::i()->authy_setup_tries )
if ( isset( $mfaDetails['authy'] ) and isset( $mfaDetails['authy']['changeAttempts'] ) and $mfaDetails['authy']['changeAttempts'] >= Settings::i()->authy_setup_tries )
{
$lockEndTime = $mfaDetails['authy']['lastChangeAttempt'] + ( \IPS\Settings::i()->authy_setup_lockout * 3600 );
$lockEndTime = $mfaDetails['authy']['lastChangeAttempt'] + ( Settings::i()->authy_setup_lockout * 3600 );
if ( $lockEndTime < time() )
{
$mfaDetails['authy']['changeAttempts'] = 0;
@@ -129,32 +154,32 @@ class _Handler extends \IPS\MFA\MFAHandler
}
else
{
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->authySetupLockout( $showingMultipleHandlers, \IPS\DateTime::ts( $lockEndTime ) );
return Theme::i()->getTemplate( 'login', 'core', 'global' )->authySetupLockout( $showingMultipleHandlers, DateTime::ts( $lockEndTime ) );
}
}
/* Otherwise show the form */
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->authySetup( isset( \IPS\Request::i()->countryCode ) ? \IPS\Request::i()->countryCode : \IPS\Helpers\Form\Address::calculateDefaultCountry(), isset( \IPS\Request::i()->phoneNumber ) ? \IPS\Request::i()->phoneNumber : '', $showingMultipleHandlers, explode( ',', \IPS\Settings::i()->authy_setup ), isset( $_SESSION['authyConfigureError'] ) ? $_SESSION['authyConfigureError'] : NULL );
return Theme::i()->getTemplate( 'login', 'core', 'global' )->authySetup( isset( Request::i()->countryCode ) ? Request::i()->countryCode : Address::calculateDefaultCountry(), isset( Request::i()->phoneNumber ) ? Request::i()->phoneNumber : '', $showingMultipleHandlers, explode( ',', Settings::i()->authy_setup ), $_SESSION['authyConfigureError'] ?? NULL );
}
}
/**
* Submit configuration screen. Return TRUE if was accepted
*
* @param \IPS\Member $member The member
* @param Member $member The member
* @return bool
*/
public function configurationScreenSubmit( \IPS\Member $member )
public function configurationScreenSubmit( Member $member ): bool
{
$mfaDetails = $member->mfa_details;
/* If we've enterred a code, verify it */
if ( isset( $mfaDetails['authy'] ) and isset( $mfaDetails['authy']['pendingId'] ) and isset( \IPS\Request::i()->authy_auth_code ) )
if ( isset( $mfaDetails['authy'] ) and isset( $mfaDetails['authy']['pendingId'] ) and isset( Request::i()->authy_auth_code ) )
{
$_SESSION['authyAuthError'] = NULL;
try
{
$response = static::totp( "verify/" . preg_replace( '/[^A-Z0-9]/i', '', \IPS\Request::i()->authy_auth_code ) . "/{$mfaDetails['authy']['pendingId']}", 'get' );
$response = static::totp( "verify/" . preg_replace( '/[^A-Z0-9]/i', '', Request::i()->authy_auth_code ) . "/{$mfaDetails['authy']['pendingId']}" );
$mfaDetails['authy'] = array( 'id' => $mfaDetails['authy']['pendingId'], 'setup' => true );
$member->mfa_details = $mfaDetails;
@@ -164,39 +189,39 @@ class _Handler extends \IPS\MFA\MFAHandler
return true;
}
catch ( Exception $e )
catch ( \IPS\Http\Request\Exception $e )
{
if ( \in_array( $e->getCode(), array( Exception::TOKEN_REUSED, Exception::TOKEN_INVALID ) ) )
if ( in_array( $e->getCode(), array( AuthyException::TOKEN_REUSED, AuthyException::TOKEN_INVALID ) ) )
{
$_SESSION['authyAuthError'] = $e->getUserMessage();
}
else
{
\IPS\Log::log( $e, 'authy' );
$_SESSION['authyAuthError'] = \IPS\Member::loggedIn()->isAdmin() ? $e->getMessage() : $e->getUserMessage();
Log::log( $e, 'authy' );
$_SESSION['authyAuthError'] = Member::loggedIn()->isAdmin() ? $e->getMessage() : $e->getUserMessage();
}
return false;
}
catch ( \Exception $e )
{
\IPS\Log::log( $e, 'authy' );
$_SESSION['authyAuthError'] = \IPS\Member::loggedIn()->isAdmin() ? $e->getMessage() : 'authy_error';
Log::log( $e, 'authy' );
$_SESSION['authyAuthError'] = Member::loggedIn()->isAdmin() ? $e->getMessage() : 'authy_error';
return false;
}
}
/* Otherwise we need to generate an ID */
elseif ( \IPS\Request::i()->phoneNumber )
elseif ( Request::i()->phoneNumber )
{
/* Do we need to wait a while? */
if ( isset( $mfaDetails['authy'] ) and isset( $mfaDetails['authy']['changeAttempts'] ) and $mfaDetails['authy']['changeAttempts'] >= \IPS\Settings::i()->authy_setup_tries )
if ( isset( $mfaDetails['authy'] ) and isset( $mfaDetails['authy']['changeAttempts'] ) and $mfaDetails['authy']['changeAttempts'] >= Settings::i()->authy_setup_tries )
{
return false;
}
/* Call Authy */
$availableMethods = explode( ',', \IPS\Settings::i()->authy_setup );
$method = ( isset( \IPS\Request::i()->method ) and \in_array( \IPS\Request::i()->method, $availableMethods ) ) ? \IPS\Request::i()->method : array_shift( $availableMethods );
$availableMethods = explode( ',', Settings::i()->authy_setup );
$method = ( isset( Request::i()->method ) and in_array( Request::i()->method, $availableMethods ) ) ? Request::i()->method : array_shift( $availableMethods );
$_SESSION['authyConfigureError'] = NULL;
try
{
@@ -204,11 +229,11 @@ class _Handler extends \IPS\MFA\MFAHandler
$data = array(
'user' => array(
'email' => $member->email,
'cellphone' => \IPS\Request::i()->phoneNumber,
'country_code' => explode( '-', \IPS\Request::i()->countryCode )[1]
'cellphone' => Request::i()->phoneNumber,
'country_code' => explode( '-', Request::i()->countryCode )[1]
)
);
if ( \IPS\Settings::i()->authy_method != 'authy' )
if ( Settings::i()->authy_method != 'authy' )
{
$data['send_install_link_via_sms'] = false;
}
@@ -228,23 +253,23 @@ class _Handler extends \IPS\MFA\MFAHandler
static::totp( "sms/{$response['user']['id']}", 'get', array( 'force' => 'true' ) );
}
}
catch ( Exception $e )
catch ( \IPS\Http\Request\Exception $e )
{
if ( \in_array( $e->getCode(), array( Exception::USER_INVALID, Exception::PHONE_NUMBER_INVALID ) ) )
if ( in_array( $e->getCode(), array( AuthyException::USER_INVALID, AuthyException::PHONE_NUMBER_INVALID ) ) )
{
$_SESSION['authyConfigureError'] = $e->getUserMessage();
}
else
{
\IPS\Log::log( $e, 'authy' );
$_SESSION['authyConfigureError'] = \IPS\Member::loggedIn()->isAdmin() ? $e->getMessage() : 'authy_error';
Log::log( $e, 'authy' );
$_SESSION['authyConfigureError'] = Member::loggedIn()->isAdmin() ? $e->getMessage() : 'authy_error';
}
return false;
}
catch ( \Exception $e )
{
\IPS\Log::log( $e, 'authy' );
$_SESSION['authyConfigureError'] = \IPS\Member::loggedIn()->isAdmin() ? $e->getMessage() : 'authy_error';
Log::log( $e, 'authy' );
$_SESSION['authyConfigureError'] = Member::loggedIn()->isAdmin() ? $e->getMessage() : 'authy_error';
return false;
}
@@ -278,14 +303,14 @@ class _Handler extends \IPS\MFA\MFAHandler
/**
* Get the form for a member to authenticate
*
* @param \IPS\Member $member The member
* @param \IPS\Http\Url $url URL for page
* @param Member $member The member
* @param Url $url URL for page
* @return string
*/
public function authenticationScreen( \IPS\Member $member, \IPS\Http\Url $url )
public function authenticationScreen( Member $member, Url $url ): string
{
$mfaDetails = $member->mfa_details;
$availableMethods = explode( ',', \IPS\Settings::i()->authy_method );
$availableMethods = explode( ',', Settings::i()->authy_method );
/* If we sent a code, but it was more than one minute ago, log a failure and reset */
if ( isset( $mfaDetails['authy']['sent'] ) and $mfaDetails['authy']['sent']['time'] < ( time() - 60 ) )
@@ -297,46 +322,46 @@ class _Handler extends \IPS\MFA\MFAHandler
}
/* If Authy app is one of the available options... */
if ( \in_array( 'authy', $availableMethods ) )
if ( in_array( 'authy', $availableMethods ) )
{
/* Are we getting a onetouch status? */
if ( \IPS\Request::i()->onetouchCheck and \IPS\Request::i()->isAjax() )
if ( Request::i()->onetouchCheck and Request::i()->isAjax() )
{
\IPS\Output::i()->json( array( 'status' => \intval( $this->_onetouchCheck( $member, \IPS\Request::i()->onetouchCheck ) ) ) );
Output::i()->json( array( 'status' => intval( $this->_onetouchCheck( $member, Request::i()->onetouchCheck ) ) ) );
}
/* If it is not the only option... */
if ( \count( $availableMethods ) > 1 )
if ( count( $availableMethods ) > 1 )
{
/* If they have asked for a text/call instead, do that */
if ( !isset( $mfaDetails['authy']['sent'] ) and isset( \IPS\Request::i()->authy_method ) and \in_array( \IPS\Request::i()->authy_method, $availableMethods ) )
if ( !isset( $mfaDetails['authy']['sent'] ) and isset( Request::i()->authy_method ) and in_array( Request::i()->authy_method, $availableMethods ) )
{
try
{
/* Send text or make call */
if ( \IPS\Request::i()->authy_method == 'phone' )
if ( Request::i()->authy_method == 'phone' )
{
static::totp( "call/{$mfaDetails['authy']['id']}", 'get', array( 'force' => 'true' ) );
}
elseif ( \IPS\Request::i()->authy_method == 'sms' )
elseif ( Request::i()->authy_method == 'sms' )
{
static::totp( "sms/{$mfaDetails['authy']['id']}", 'get', array( 'force' => 'true' ) );
}
/* Update details */
$mfaDetails['authy']['sent'] = array( 'method' => \IPS\Request::i()->authy_method, 'time' => time() );
$mfaDetails['authy']['sent'] = array( 'method' => Request::i()->authy_method, 'time' => time() );
$member->mfa_details = $mfaDetails;
$member->save();
}
catch ( \Exception $e )
{
\IPS\Log::log( $e, 'authy' );
$_SESSION['authyAuthError'] = \IPS\Member::loggedIn()->isAdmin() ? $e->getMessage() : 'authy_error';
Log::log( $e, 'authy' );
$_SESSION['authyAuthError'] = Member::loggedIn()->isAdmin() ? $e->getMessage() : 'authy_error';
}
}
if ( isset( $mfaDetails['authy']['sent'] ) )
{
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->authyAuthenticate( $mfaDetails['authy']['sent']['method'], TRUE, isset( $_SESSION['authyAuthError'] ) ? $_SESSION['authyAuthError'] : 'authy_error', FALSE, $availableMethods, NULL, $url );
return Theme::i()->getTemplate( 'login', 'core', 'global' )->authyAuthenticate( $mfaDetails['authy']['sent']['method'], TRUE, $_SESSION['authyAuthError'] ?? 'authy_error', FALSE, $availableMethods, NULL, $url );
}
/* Otherwise, check if they have the app installed. If they do, show the Authy authenticate page */
@@ -354,7 +379,7 @@ class _Handler extends \IPS\MFA\MFAHandler
if ( $userHasAuthyApp )
{
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->authyAuthenticate( 'authy', TRUE, isset( $_SESSION['authyAuthError'] ) ? $_SESSION['authyAuthError'] : 'authy_error', FALSE, $availableMethods, $this->_onetouchInit( $member, $url ), $url );
return Theme::i()->getTemplate( 'login', 'core', 'global' )->authyAuthenticate( 'authy', TRUE, $_SESSION['authyAuthError'] ?? 'authy_error', FALSE, $availableMethods, $this->_onetouchInit( $member, $url ), $url );
}
}
catch ( \Exception $e ) { }
@@ -363,12 +388,12 @@ class _Handler extends \IPS\MFA\MFAHandler
/* If it is the only option, show it anyway */
else
{
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->authyAuthenticate( 'authy', TRUE, isset( $_SESSION['authyAuthError'] ) ? $_SESSION['authyAuthError'] : 'authy_error', FALSE, $availableMethods, $this->_onetouchInit( $member, $url ), $url );
return Theme::i()->getTemplate( 'login', 'core', 'global' )->authyAuthenticate( 'authy', TRUE, $_SESSION['authyAuthError'] ?? 'authy_error', FALSE, $availableMethods, $this->_onetouchInit( $member, $url ), $url );
}
}
/* If text message is the only available option, or we have chosen that option, do that... */
if ( \in_array( 'sms', $availableMethods ) and ( \count( $availableMethods ) == 1 ) or ( isset( $mfaDetails['authy']['sent'] ) and $mfaDetails['authy']['sent']['method'] == 'sms' ) or ( isset( \IPS\Request::i()->authy_method ) and \IPS\Request::i()->authy_method == 'sms' ) )
if ( in_array( 'sms', $availableMethods ) and ( count( $availableMethods ) == 1 ) or ( isset( $mfaDetails['authy']['sent'] ) and $mfaDetails['authy']['sent']['method'] == 'sms' ) or ( isset( Request::i()->authy_method ) and Request::i()->authy_method == 'sms' ) )
{
/* Send the text if we haven't already */
if ( !isset( $mfaDetails['authy']['sent'] ) )
@@ -379,8 +404,8 @@ class _Handler extends \IPS\MFA\MFAHandler
}
catch ( \Exception $e )
{
\IPS\Log::log( $e, 'authy' );
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->authyError( $e->getMessage() );
Log::log( $e, 'authy' );
return Theme::i()->getTemplate( 'login', 'core', 'global' )->authyError( $e->getMessage() );
}
$mfaDetails['authy']['sent'] = array( 'method' => 'sms', 'time' => time() );
@@ -389,11 +414,11 @@ class _Handler extends \IPS\MFA\MFAHandler
}
/* Show screen */
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->authyAuthenticate( 'sms', TRUE, isset( $_SESSION['authyAuthError'] ) ? $_SESSION['authyAuthError'] : 'authy_error', FALSE, $availableMethods, NULL, $url );
return Theme::i()->getTemplate( 'login', 'core', 'global' )->authyAuthenticate( 'sms', TRUE, $_SESSION['authyAuthError'] ?? 'authy_error', FALSE, $availableMethods, NULL, $url );
}
/* If we have confirmed the phone call, do that now */
if ( ( isset( $mfaDetails['authy']['sent'] ) and $mfaDetails['authy']['sent']['method'] == 'phone' ) or ( isset( \IPS\Request::i()->authy_method ) and \IPS\Request::i()->authy_method == 'phone' ) )
if ( ( isset( $mfaDetails['authy']['sent'] ) and $mfaDetails['authy']['sent']['method'] == 'phone' ) or ( isset( Request::i()->authy_method ) and Request::i()->authy_method == 'phone' ) )
{
/* Send the text if we haven't already */
if ( !isset( $mfaDetails['authy']['sent'] ) )
@@ -404,8 +429,8 @@ class _Handler extends \IPS\MFA\MFAHandler
}
catch ( \Exception $e )
{
\IPS\Log::log( $e, 'authy' );
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->authyError( $e->getMessage() );
Log::log( $e, 'authy' );
return Theme::i()->getTemplate( 'login', 'core', 'global' )->authyError( $e->getMessage() );
}
$mfaDetails['authy']['sent'] = array( 'method' => 'call', 'time' => time() );
@@ -414,29 +439,29 @@ class _Handler extends \IPS\MFA\MFAHandler
}
/* Show screen */
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->authyAuthenticate( 'phone', TRUE, isset( $_SESSION['authyAuthError'] ) ? $_SESSION['authyAuthError'] : 'authy_error', FALSE, $availableMethods, NULL, $url );
return Theme::i()->getTemplate( 'login', 'core', 'global' )->authyAuthenticate( 'phone', TRUE, $_SESSION['authyAuthError'] ?? 'authy_error', FALSE, $availableMethods, NULL, $url );
}
/* Otherwise we're going to show a screen */
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->authyAuthenticate( \count( $availableMethods ) == 1 ? 'phone' : 'choose', FALSE, isset( $_SESSION['authyAuthError'] ) ? $_SESSION['authyAuthError'] : 'authy_error', FALSE, $availableMethods, NULL, $url );
return Theme::i()->getTemplate( 'login', 'core', 'global' )->authyAuthenticate( count( $availableMethods ) == 1 ? 'phone' : 'choose', FALSE, $_SESSION['authyAuthError'] ?? 'authy_error', FALSE, $availableMethods, NULL, $url );
}
/**
* If enabled, initiate a OneTouch request and get the ID
*
* @param \IPS\Member $member The member
* @param \IPS\Http\Url $url URL for page
* @param Member $member The member
* @param Url $url URL for page
* @return string|null
*/
protected function _onetouchInit( \IPS\Member $member, \IPS\Http\Url $url )
protected function _onetouchInit( Member $member, Url $url ): ?string
{
if ( \IPS\Settings::i()->authy_onetouch )
if ( Settings::i()->authy_onetouch )
{
$mfaDetails = $member->mfa_details;
if ( isset( $mfaDetails['onetouch'] ) and $mfaDetails['onetouch']['time'] > ( time() - 30 ) )
{
$response = static::onetouch( "approval_requests/" . preg_replace( '/[^A-Z0-9\-]/i', '', $mfaDetails['onetouch']['id'] ), 'get' );
$response = static::onetouch( "approval_requests/" . preg_replace( '/[^A-Z0-9\-]/i', '', $mfaDetails['onetouch']['id'] ) );
if ( $response['approval_request']['status'] === 'pending' )
{
@@ -464,23 +489,23 @@ class _Handler extends \IPS\MFA\MFAHandler
}
return NULL;
}
/**
* Check the status of a onetouch request
*
* @param \IPS\Member $member The member
* @param string $id The onetouch request ID
* @return string|null
* @param Member $member The member
* @param string $id The onetouch request ID
* @return bool|string|null
*/
protected function _onetouchCheck( \IPS\Member $member, $id )
protected function _onetouchCheck( Member $member, string $id ): bool|string|null
{
if ( \IPS\Settings::i()->authy_onetouch )
if ( Settings::i()->authy_onetouch )
{
$mfaDetails = $member->mfa_details;
try
{
$response = static::onetouch( "approval_requests/" . preg_replace( '/[^A-Z0-9\-]/i', '', $id ), 'get' );
$response = static::onetouch( "approval_requests/" . preg_replace( '/[^A-Z0-9\-]/i', '', $id ) );
return $response['approval_request']['status'] === 'approved';
}
catch ( \Exception $e ) {}
@@ -491,10 +516,10 @@ class _Handler extends \IPS\MFA\MFAHandler
/**
* Submit authentication screen. Return TRUE if was accepted
*
* @param \IPS\Member $member The member
* @param Member $member The member
* @return bool
*/
public function authenticationScreenSubmit( \IPS\Member $member )
public function authenticationScreenSubmit( Member $member ): bool
{
$mfaDetails = $member->mfa_details;
@@ -502,14 +527,14 @@ class _Handler extends \IPS\MFA\MFAHandler
try
{
if ( isset( \IPS\Request::i()->authy_auth_code ) )
if ( isset( Request::i()->authy_auth_code ) )
{
$response = static::totp( "verify/" . preg_replace( '/[^A-Z0-9]/i', '', \IPS\Request::i()->authy_auth_code ) . "/{$mfaDetails['authy']['id']}", 'get' );
$response = static::totp( "verify/" . preg_replace( '/[^A-Z0-9]/i', '', Request::i()->authy_auth_code ) . "/{$mfaDetails['authy']['id']}" );
$mfaDetails['authy'] = array( 'id' => $mfaDetails['authy']['id'], 'setup' => true );
}
elseif ( isset( \IPS\Request::i()->onetouch ) )
elseif ( isset( Request::i()->onetouch ) )
{
return $this->_onetouchCheck( $member, \IPS\Request::i()->onetouch );
return $this->_onetouchCheck( $member, Request::i()->onetouch );
}
else
{
@@ -520,23 +545,23 @@ class _Handler extends \IPS\MFA\MFAHandler
return true;
}
catch ( Exception $e )
catch ( \IPS\Http\Request\Exception $e )
{
if ( \in_array( $e->getCode(), array( Exception::TOKEN_REUSED, Exception::TOKEN_INVALID ) ) )
if ( in_array( $e->getCode(), array( AuthyException::TOKEN_REUSED, AuthyException::TOKEN_INVALID ) ) )
{
$_SESSION['authyAuthError'] = $e->getUserMessage();
}
else
{
\IPS\Log::log( $e, 'authy' );
$_SESSION['authyAuthError'] = \IPS\Member::loggedIn()->isAdmin() ? $e->getMessage() : $e->getUserMessage();
Log::log( $e, 'authy' );
$_SESSION['authyAuthError'] = Member::loggedIn()->isAdmin() ? $e->getMessage() : $e->getUserMessage();
}
return false;
}
catch ( \Exception $e )
{
\IPS\Log::log( $e, 'authy' );
$_SESSION['authyAuthError'] = \IPS\Member::loggedIn()->isAdmin() ? $e->getMessage() : 'authy_error';
Log::log( $e, 'authy' );
$_SESSION['authyAuthError'] = Member::loggedIn()->isAdmin() ? $e->getMessage() : 'authy_error';
return false;
}
}
@@ -547,22 +572,22 @@ class _Handler extends \IPS\MFA\MFAHandler
* Toggle
*
* @param bool $enabled On/Off
* @return bool
* @return void
*/
public function toggle( $enabled )
public function toggle( bool $enabled ): void
{
/* This handler is deprecated, so if it's already disabled, don't allow it to be re-enabled */
if( !$this->isEnabled() )
{
return FALSE;
return;
}
if ( $enabled )
{
static::verifyApiKey( \IPS\Settings::i()->authy_key );
static::verifyApiKey( Settings::i()->authy_key );
}
\IPS\Settings::i()->changeValues( array( 'authy_enabled' => $enabled ) );
Settings::i()->changeValues( array( 'authy_enabled' => $enabled ) );
}
/**
@@ -570,53 +595,53 @@ class _Handler extends \IPS\MFA\MFAHandler
*
* @return string
*/
public function acpSettings()
public function acpSettings(): string
{
if( !$this->isEnabled() )
{
\IPS\Output::i()->error( 'authy_deprecated_message', '2C345/3' );
Output::i()->error( 'authy_deprecated_message', '2C345/3' );
}
$form = new \IPS\Helpers\Form;
$form = new Form;
$form->add( new \IPS\Helpers\Form\Text( 'authy_key', \IPS\Settings::i()->authy_key, TRUE, array(), function( $val ) {
$details = \IPS\MFA\Authy\Handler::verifyApiKey( $val );
if ( !$details['app']['sms_enabled'] and ( array_key_exists( 'sms', \IPS\Request::i()->authy_setup ) or array_key_exists( 'sms', \IPS\Request::i()->authy_method ) ) )
$form->add( new Text( 'authy_key', Settings::i()->authy_key, TRUE, array(), function( $val ) {
$details = Handler::verifyApiKey( $val );
if ( !$details['app']['sms_enabled'] and ( array_key_exists( 'sms', Request::i()->authy_setup ) or array_key_exists( 'sms', Request::i()->authy_method ) ) )
{
throw new \DomainException('authy_key_no_sms');
throw new DomainException('authy_key_no_sms');
}
if ( !$details['app']['phone_calls_enabled'] and ( array_key_exists( 'phone', \IPS\Request::i()->authy_setup ) or array_key_exists( 'phone', \IPS\Request::i()->authy_method ) ) )
if ( !$details['app']['phone_calls_enabled'] and ( array_key_exists( 'phone', Request::i()->authy_setup ) or array_key_exists( 'phone', Request::i()->authy_method ) ) )
{
throw new \DomainException('authy_key_no_sms');
throw new DomainException('authy_key_no_sms');
}
if ( !$details['app']['onetouch_enabled'] and \IPS\Request::i()->authy_onetouch )
if ( !$details['app']['onetouch_enabled'] and Request::i()->authy_onetouch )
{
throw new \DomainException('authy_key_no_onetouch');
throw new DomainException('authy_key_no_onetouch');
}
}, NULL, \IPS\Member::loggedIn()->language()->addToStack('authy_key_suffix') ) );
}, NULL, Member::loggedIn()->language()->addToStack('authy_key_suffix') ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'authy_groups', \IPS\Settings::i()->authy_groups == '*' ? '*' : explode( ',', \IPS\Settings::i()->authy_groups ), FALSE, array(
$form->add( new CheckboxSet( 'authy_groups', Settings::i()->authy_groups == '*' ? '*' : explode( ',', Settings::i()->authy_groups ), FALSE, array(
'multiple' => TRUE,
'options' => array_combine( array_keys( \IPS\Member\Group::groups() ), array_map( function( $_group ) { return (string) $_group; }, \IPS\Member\Group::groups() ) ),
'options' => array_combine( array_keys( Group::groups() ), array_map( function( $_group ) { return (string) $_group; }, Group::groups() ) ),
'unlimited' => '*',
'unlimitedLang' => 'everyone',
'impliedUnlimited' => TRUE
) ) );
$form->addHeader('authy_setup_header');
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'authy_setup', explode( ',', \IPS\Settings::i()->authy_setup ), TRUE, array( 'options' => array(
$form->add( new CheckboxSet( 'authy_setup', explode( ',', Settings::i()->authy_setup ), TRUE, array( 'options' => array(
'authy' => 'authy_method_authy',
'sms' => 'authy_method_sms',
'phone' => 'authy_method_phone',
) ) ) );
$form->add( new \IPS\Helpers\Form\Custom( 'authy_setup_protection', array( \IPS\Settings::i()->authy_setup_tries, \IPS\Settings::i()->authy_setup_lockout ), FALSE, array(
$form->add( new Custom( 'authy_setup_protection', array( Settings::i()->authy_setup_tries, Settings::i()->authy_setup_lockout ), FALSE, array(
'getHtml' => function( $field ) {
return \IPS\Theme::i()->getTemplate('settings')->authySetupProtection( $field->value );
return Theme::i()->getTemplate('settings')->authySetupProtection( $field->value );
}
) ) );
$form->addHeader('authy_authenticate_header');
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'authy_method', explode( ',', \IPS\Settings::i()->authy_method ), TRUE, array(
$form->add( new CheckboxSet( 'authy_method', explode( ',', Settings::i()->authy_method ), TRUE, array(
'options' => array(
'authy' => 'authy_method_authy',
'sms' => 'authy_method_sms',
@@ -626,7 +651,7 @@ class _Handler extends \IPS\MFA\MFAHandler
'authy' => array( 'authy_onetouch' )
)
) ) );
$form->add( new \IPS\Helpers\Form\Radio( 'authy_onetouch', \IPS\Settings::i()->authy_onetouch, TRUE, array(
$form->add( new Radio( 'authy_onetouch', Settings::i()->authy_onetouch, TRUE, array(
'options' => array(
'1' => 'authy_onetouch_on',
'0' => 'authy_onetouch_off',
@@ -643,8 +668,8 @@ class _Handler extends \IPS\MFA\MFAHandler
$values['authy_method'] = isset( $values['authy_method'] ) ? implode( ',', $values['authy_method'] ) : '';
$form->saveAsSettings( $values );
\IPS\Session::i()->log( 'acplogs__mfa_handler_enabled', array( "mfa_authy_title" => TRUE ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=settings&controller=mfa' ), 'saved' );
Session::i()->log( 'acplogs__mfa_handler_enabled', array( "mfa_authy_title" => TRUE ) );
Output::i()->redirect( Url::internal( 'app=core&module=settings&controller=mfa' ), 'saved' );
}
return (string) $form;
@@ -657,10 +682,10 @@ class _Handler extends \IPS\MFA\MFAHandler
/**
* If member has configured this handler, disable it
*
* @param \IPS\Member $member The member
* @return bool
* @param Member $member The member
* @return void
*/
public function disableHandlerForMember( \IPS\Member $member )
public function disableHandlerForMember( Member $member ): void
{
$mfaDetails = $member->mfa_details;
@@ -669,7 +694,7 @@ class _Handler extends \IPS\MFA\MFAHandler
try
{
static::totp( "users/{$mfaDetails['authy']['id']}/delete", 'post', array(
'user_ip' => \IPS\Request::i()->ipAddress()
'user_ip' => Request::i()->ipAddress()
) );
}
catch ( \Exception $e ) { }
@@ -688,21 +713,21 @@ class _Handler extends \IPS\MFA\MFAHandler
*
* @return string
*/
public function ucpTitle()
public function ucpTitle(): string
{
$availableMethods = explode( ',', \IPS\Settings::i()->authy_method );
$availableMethods = explode( ',', Settings::i()->authy_method );
if ( \in_array( 'authy', $availableMethods ) )
if ( in_array( 'authy', $availableMethods ) )
{
return \IPS\Member::loggedIn()->language()->addToStack('mfa_authy_title');
return Member::loggedIn()->language()->addToStack('mfa_authy_title');
}
elseif ( \in_array( 'sms', $availableMethods ) and \count( $availableMethods ) == 1 )
elseif ( in_array( 'sms', $availableMethods ) and count( $availableMethods ) == 1 )
{
return \IPS\Member::loggedIn()->language()->addToStack('mfa_sms_title');
return Member::loggedIn()->language()->addToStack('mfa_sms_title');
}
else
{
return \IPS\Member::loggedIn()->language()->addToStack('mfa_phone_title');
return Member::loggedIn()->language()->addToStack('mfa_phone_title');
}
}
@@ -711,32 +736,32 @@ class _Handler extends \IPS\MFA\MFAHandler
*
* @return string
*/
public function ucpDesc()
public function ucpDesc(): string
{
$availableMethods = explode( ',', \IPS\Settings::i()->authy_method );
$availableMethods = explode( ',', Settings::i()->authy_method );
if ( \in_array( 'authy', $availableMethods ) )
if ( in_array( 'authy', $availableMethods ) )
{
if ( \count( $availableMethods ) == 1 )
if ( count( $availableMethods ) == 1 )
{
return \IPS\Member::loggedIn()->language()->addToStack('mfa_authy_only_desc_user');
return Member::loggedIn()->language()->addToStack('mfa_authy_only_desc_user');
}
else
{
return \IPS\Member::loggedIn()->language()->addToStack('mfa_authy_mixed_desc_user');
return Member::loggedIn()->language()->addToStack('mfa_authy_mixed_desc_user');
}
}
elseif ( \in_array( 'sms', $availableMethods ) and \count( $availableMethods ) == 1 )
elseif ( in_array( 'sms', $availableMethods ) and count( $availableMethods ) == 1 )
{
return \IPS\Member::loggedIn()->language()->addToStack('mfa_sms_desc_user');
return Member::loggedIn()->language()->addToStack('mfa_sms_desc_user');
}
elseif ( \in_array( 'phone', $availableMethods ) and \count( $availableMethods ) == 1 )
elseif ( in_array( 'phone', $availableMethods ) and count( $availableMethods ) == 1 )
{
return \IPS\Member::loggedIn()->language()->addToStack('mfa_phone_desc_user');
return Member::loggedIn()->language()->addToStack('mfa_phone_desc_user');
}
else
{
return \IPS\Member::loggedIn()->language()->addToStack('mfa_sms_or_phone_desc_user');
return Member::loggedIn()->language()->addToStack('mfa_sms_or_phone_desc_user');
}
}
@@ -745,21 +770,21 @@ class _Handler extends \IPS\MFA\MFAHandler
*
* @return string
*/
public function recoveryButton()
public function recoveryButton(): string
{
$availableMethods = explode( ',', \IPS\Settings::i()->authy_method );
$availableMethods = explode( ',', Settings::i()->authy_method );
if ( \in_array( 'authy', $availableMethods ) and \count( $availableMethods ) == 1 )
if ( in_array( 'authy', $availableMethods ) and count( $availableMethods ) == 1 )
{
return \IPS\Member::loggedIn()->language()->addToStack('mfa_authy_recovery');
return Member::loggedIn()->language()->addToStack('mfa_authy_recovery');
}
elseif ( \in_array( 'sms', $availableMethods ) and \count( $availableMethods ) == 1 )
elseif ( in_array( 'sms', $availableMethods ) and count( $availableMethods ) == 1 )
{
return \IPS\Member::loggedIn()->language()->addToStack('mfa_sms_recovery');
return Member::loggedIn()->language()->addToStack('mfa_sms_recovery');
}
else
{
return \IPS\Member::loggedIn()->language()->addToStack('mfa_phone_recovery');
return Member::loggedIn()->language()->addToStack('mfa_phone_recovery');
}
}
@@ -768,12 +793,12 @@ class _Handler extends \IPS\MFA\MFAHandler
/**
* Make TOTP API Call
*
* @param string $endpoint The endpoint to call
* @param string $method 'get' or 'post'
* @param array $data Post data or additional query string parameters
* @param string $endpoint The endpoint to call
* @param string $method 'get' or 'post'
* @param array|null $data Post data or additional query string parameters
* @return array
*/
public static function totp( $endpoint, $method='get', $data=NULL )
public static function totp( string $endpoint, string $method='get', array $data=NULL ): array
{
return static::_api( "protected/json/{$endpoint}", $method, $data );
}
@@ -781,12 +806,12 @@ class _Handler extends \IPS\MFA\MFAHandler
/**
* Make OneTouch API Call
*
* @param string $endpoint The endpoint to call
* @param string $method 'get' or 'post'
* @param array $data Post data or additional query string parameters
* @param string $endpoint The endpoint to call
* @param string $method 'get' or 'post'
* @param array|null $data Post data or additional query string parameters
* @return array
*/
public static function onetouch( $endpoint, $method='get', $data=NULL )
public static function onetouch( string $endpoint, string $method='get', array $data=NULL ): array
{
return static::_api( "onetouch/json/{$endpoint}", $method, $data );
}
@@ -794,14 +819,14 @@ class _Handler extends \IPS\MFA\MFAHandler
/**
* Make API Call
*
* @param string $endpoint The endpoint to call
* @param string $method 'get' or 'post'
* @param array $data Post data or additional query string parameters
* @param string $endpoint The endpoint to call
* @param string $method 'get' or 'post'
* @param array|null $data Post data or additional query string parameters
* @return array
*/
protected static function _api( $endpoint, $method='get', $data=NULL )
protected static function _api( string $endpoint, string $method='get', array $data=NULL ): array
{
$url = \IPS\Http\Url::external("https://api.authy.com/{$endpoint}")->setQueryString( 'api_key', \IPS\Settings::i()->authy_key );
$url = Url::external("https://api.authy.com/{$endpoint}")->setQueryString( 'api_key', Settings::i()->authy_key );
if ( $method == 'get' )
{
@@ -816,7 +841,7 @@ class _Handler extends \IPS\MFA\MFAHandler
if ( !$response['success'] )
{
throw new Exception( $response['message'], $response['error_code'] );
throw new \IPS\Http\Request\Exception( $response['message'], $response['error_code'] );
}
return $response;
@@ -825,19 +850,19 @@ class _Handler extends \IPS\MFA\MFAHandler
/**
* Verify an Authy API Key
*
* @param string $val The API key submitted
* @param string $val The API key submitted
* @return array
* @throws \DomainException
* @throws DomainException
*/
public static function verifyApiKey( $val )
public static function verifyApiKey( string $val ): array
{
try
{
return \IPS\Http\Url::external("https://api.authy.com/protected/json/app/details")->setQueryString( 'api_key', $val )->request()->get()->decodeJson();
return Url::external("https://api.authy.com/protected/json/app/details")->setQueryString( 'api_key', $val )->request()->get()->decodeJson();
}
catch ( \IPS\Http\Request\Exception $e )
{
throw new \DomainException( $e->getMessage() );
throw new DomainException( $e->getMessage() );
}
}