Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial OTP implementation with phone and email #67

Draft
wants to merge 2 commits into
base: 7.next-cake4
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion config/permissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@
'requestResetPassword',
// UserValidationTrait used in PasswordManagementTrait
'resendTokenValidation',
'linkSocial'
'linkSocial',
'code2f',
'code2fRegister',
'code2fAuthenticate'
],
'bypassAuth' => true,
],
Expand Down
45 changes: 42 additions & 3 deletions src/Authentication/AuthenticationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class AuthenticationService extends BaseService

public const NEED_U2F_VERIFY = 'NEED_U2F_VERIFY';

public const CODE2F_SESSION_KEY = 'Code2f.User';

public const NEED_CODE2F_VERIFY = 'NEED_CODE2F_VERIFY';

public const NEED_WEBAUTHN_2FA_VERIFY = 'NEED_WEBAUTHN2FA_VERIFY';

public const WEBAUTHN_2FA_SESSION_KEY = 'Webauthn2fa.User';
Expand All @@ -42,7 +46,7 @@ class AuthenticationService extends BaseService
protected $failures = [];

/**
* Proceed to google verify action after a valid result result
* Proceed to google verify action after a valid result
*
* @param \Psr\Http\Message\ServerRequestInterface $request The request.
* @param \Authentication\Authenticator\ResultInterface $result The original result
Expand All @@ -61,7 +65,7 @@ protected function proceedToGoogleVerify(ServerRequestInterface $request, Result
return $this->_result = $result;
}
/**
* Proceed to webauthn2fa flow after a valid result result
* Proceed to webauthn2fa flow after a valid result
*
* @param \Psr\Http\Message\ServerRequestInterface $request response to manipulate
* @param \Authentication\Authenticator\ResultInterface $result valid result
Expand All @@ -81,7 +85,7 @@ protected function proceedToWebauthn2fa(ServerRequestInterface $request, ResultI
}

/**
* Proceed to U2f flow after a valid result result
* Proceed to U2f flow after a valid result
*
* @param \Psr\Http\Message\ServerRequestInterface $request response to manipulate
* @param \Authentication\Authenticator\ResultInterface $result valid result
Expand All @@ -100,6 +104,26 @@ protected function proceedToU2f(ServerRequestInterface $request, ResultInterface
return $this->_result = $result;
}

/**
* Proceed to Code2f flow after a valid result
*
* @param \Psr\Http\Message\ServerRequestInterface $request response to manipulate
* @param \Authentication\Authenticator\ResultInterface $result valid result
* @return \Authentication\Authenticator\ResultInterface with result, request and response keys
*/
protected function proceedToCode2f(ServerRequestInterface $request, ResultInterface $result)
{
/**
* @var \Cake\Http\Session $session
*/
$session = $request->getAttribute('session');
$session->write(self::CODE2F_SESSION_KEY, $result->getData());
$result = new Result(null, self::NEED_CODE2F_VERIFY);
$this->_successfulAuthenticator = null;

return $this->_result = $result;
}

/**
* Get the configured one-time password authentication checker
*
Expand Down Expand Up @@ -130,6 +154,16 @@ protected function getU2fAuthenticationChecker()
return (new U2fAuthenticationCheckerFactory())->build();
}

/**
* Get the configured one-time password authentication checker
*
* @return \CakeDC\Auth\Authentication\OneTimePasswordAuthenticationCheckerInterface
*/
protected function getCode2fAuthenticationChecker()
{
return (new Code2fAuthenticationCheckerFactory())->build();
}

/**
* {@inheritDoc}
*
Expand Down Expand Up @@ -164,6 +198,11 @@ public function authenticate(ServerRequestInterface $request): ResultInterface
return $this->proceedToGoogleVerify($request, $result);
}

$code2fCheck = $this->getCode2fAuthenticationChecker();
if ($skipTwoFactorVerify !== true && $code2fCheck->isRequired($userData, $request)) {
return $this->proceedToCode2f($request, $result);
}

$this->_successfulAuthenticator = $authenticator;
$this->_result = $result;

Expand Down
40 changes: 40 additions & 0 deletions src/Authentication/Code2fAuthenticationCheckerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);

/**
* Copyright 2010 - 2022, Cake Development Corporation (https://www.cakedc.com)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2010 - 2019, Cake Development Corporation (https://www.cakedc.com)
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace CakeDC\Auth\Authentication;

use Cake\Core\Configure;

/**
* Factory for two authentication checker
*
* @package CakeDC\Auth\Authentication
*/
class Code2fAuthenticationCheckerFactory
{
/**
* Get the two factor authentication checker
*
* @return \CakeDC\Auth\Authentication\Code2fAuthenticationCheckerInterface
*/
public function build()
{
$className = Configure::read('Code2f.checker');
$interfaces = class_implements($className);
$required = Code2fAuthenticationCheckerInterface::class;

if (in_array($required, $interfaces)) {
return new $className();
}
throw new \InvalidArgumentException("Invalid config for 'Code2f.checker', '$className' does not implement '$required'");
}
}
37 changes: 37 additions & 0 deletions src/Authentication/Code2fAuthenticationCheckerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);

/**
* Copyright 2010 - 2022, Cake Development Corporation (https://www.cakedc.com)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2010 - 2019, Cake Development Corporation (https://www.cakedc.com)
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace CakeDC\Auth\Authentication;

use Psr\Http\Message\ServerRequestInterface;

interface Code2fAuthenticationCheckerInterface
{

const CODE2F_TYPE_EMAIL = 'email';
const CODE2F_TYPE_PHONE = 'phone';

/**
* Check if two factor authentication is enabled
*
* @return bool
*/
public function isEnabled();

/**
* Check if two factor authentication is required for a user
*
* @param array $user user data
* @return bool
*/
public function isRequired(?array $user = null, ServerRequestInterface $request);
}
48 changes: 48 additions & 0 deletions src/Authentication/DefaultCode2fAuthenticationChecker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);

/**
* Copyright 2010 - 2019, Cake Development Corporation (https://www.cakedc.com)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2010 - 2018, Cake Development Corporation (https://www.cakedc.com)
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace CakeDC\Auth\Authentication;

use Cake\Core\Configure;
use Psr\Http\Message\ServerRequestInterface;

/**
* Default class to check if two factor authentication is enabled and required
*
* @package CakeDC\Auth\Auth
*/
class DefaultCode2fAuthenticationChecker implements Code2fAuthenticationCheckerInterface
{
/**
* Check if two factor authentication is enabled
*
* @return bool
*/
public function isEnabled()
{
if (!in_array(Configure::read('Code2f.type'), [self::CODE2F_TYPE_EMAIL, self::CODE2F_TYPE_PHONE])) {
throw new \UnexpectedValueException(__d('cake_d_c/users', 'Code2F type must be: {0}, {1}', self::CODE2F_TYPE_EMAIL, self::CODE2F_TYPE_PHONE));
}
return Configure::read('Code2f.enabled') !== false;
}

/**
* Check if two factor authentication is required for a user
*
* @param array $user user data
* @return bool
*/
public function isRequired(?array $user = null, ServerRequestInterface $request)
{
return !empty($user) && $this->isEnabled();
}
}
48 changes: 48 additions & 0 deletions src/Authentication/FingerprintCode2fAuthenticationChecker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);

/**
* Copyright 2010 - 2019, Cake Development Corporation (https://www.cakedc.com)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2010 - 2018, Cake Development Corporation (https://www.cakedc.com)
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace CakeDC\Auth\Authentication;

use Cake\Core\Configure;
use Psr\Http\Message\ServerRequestInterface;

/**
* Default class to check if two factor authentication is enabled and required
*
* @package CakeDC\Auth\Auth
*/
class FingerprintCode2fAuthenticationChecker implements Code2fAuthenticationCheckerInterface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this one used? Seems to be the same as DefaultCode2fAuthenticationChecker

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to implement that soon. As well as TimeBasedCode2fAuthenticationChecker

{
/**
* Check if two factor authentication is enabled
*
* @return bool
*/
public function isEnabled()
{
if (!in_array(Configure::read('Code2f.type'), [self::CODE2F_TYPE_EMAIL, self::CODE2F_TYPE_PHONE])) {
throw new \UnexpectedValueException(__d('cake_d_c/users', 'Code2F type must be: {0}, {1}', self::CODE2F_TYPE_EMAIL, self::CODE2F_TYPE_PHONE));
}
return Configure::read('Code2f.enabled') !== false;
}

/**
* Check if two factor authentication is required for a user
*
* @param array $user user data
* @return bool
*/
public function isRequired(?array $user = null, ServerRequestInterface $request)
{
return !empty($user) && $this->isEnabled();
}
}
3 changes: 3 additions & 0 deletions src/Middleware/TwoFactorMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
case AuthenticationService::NEED_U2F_VERIFY:
$url = Configure::read('U2f.startAction');
break;
case AuthenticationService::NEED_CODE2F_VERIFY:
$url = Configure::read('Code2f.verifyAction');
break;
case AuthenticationService::NEED_WEBAUTHN_2FA_VERIFY:
$url = Configure::read('Webauthn2fa.startAction');
break;
Expand Down