Close #15 added two factor authentication
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
## 1.0.14 - Work in progress
|
## 1.0.14 - Work in progress
|
||||||
|
- Enh #56: Added two factor authentication (tonydspaniard)
|
||||||
- Fix #63: Fix selectize version (tonydspaniard)
|
- Fix #63: Fix selectize version (tonydspaniard)
|
||||||
- Enh #65: Updated Romanian translation (mrbig00)
|
- Enh #65: Updated Romanian translation (mrbig00)
|
||||||
- Enh #61: Updated Russian translation (faenir)
|
- Enh #61: Updated Russian translation (faenir)
|
||||||
|
|||||||
@ -47,10 +47,12 @@
|
|||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.5",
|
"php": ">=5.5",
|
||||||
"2amigos/yii2-selectize-widget": "~1.1",
|
"2amigos/yii2-selectize-widget": "^1.1",
|
||||||
"yiisoft/yii2-authclient": "^2.1.0",
|
"yiisoft/yii2-authclient": "^2.1.0",
|
||||||
"yiisoft/yii2-bootstrap": "^2.0.0",
|
"yiisoft/yii2-bootstrap": "^2.0.0",
|
||||||
"yiisoft/yii2-swiftmailer": "^2.0.0"
|
"yiisoft/yii2-swiftmailer": "^2.0.0",
|
||||||
|
"2amigos/2fa-library": "^1.0",
|
||||||
|
"2amigos/qrcode-library": "^1.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"cebe/assetfree-yii2": "~2.0.0",
|
"cebe/assetfree-yii2": "~2.0.0",
|
||||||
@ -71,14 +73,12 @@
|
|||||||
"Da\\User\\": "./src/User"
|
"Da\\User\\": "./src/User"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"config": {
|
"repositories": [
|
||||||
"fxp-asset": {
|
{
|
||||||
"installer-paths": {
|
"type": "composer",
|
||||||
"npm-asset-library": "vendor/npm",
|
"url": "https://asset-packagist.org"
|
||||||
"bower-asset-library": "vendor/bower"
|
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
},
|
|
||||||
"extra": {
|
"extra": {
|
||||||
"bootstrap": "Da\\User\\Bootstrap"
|
"bootstrap": "Da\\User\\Bootstrap"
|
||||||
}
|
}
|
||||||
|
|||||||
1756
composer.lock
generated
1756
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,6 @@ use Da\User\Contracts\AuthClientInterface;
|
|||||||
use Da\User\Event\FormEvent;
|
use Da\User\Event\FormEvent;
|
||||||
use Da\User\Event\UserEvent;
|
use Da\User\Event\UserEvent;
|
||||||
use Da\User\Form\LoginForm;
|
use Da\User\Form\LoginForm;
|
||||||
use Da\User\Model\User;
|
|
||||||
use Da\User\Query\SocialNetworkAccountQuery;
|
use Da\User\Query\SocialNetworkAccountQuery;
|
||||||
use Da\User\Service\SocialNetworkAccountConnectService;
|
use Da\User\Service\SocialNetworkAccountConnectService;
|
||||||
use Da\User\Service\SocialNetworkAuthenticateService;
|
use Da\User\Service\SocialNetworkAuthenticateService;
|
||||||
@ -64,7 +63,7 @@ class SecurityController extends Controller
|
|||||||
'rules' => [
|
'rules' => [
|
||||||
[
|
[
|
||||||
'allow' => true,
|
'allow' => true,
|
||||||
'actions' => ['login', 'auth', 'blocked'],
|
'actions' => ['login', 'confirm', 'auth', 'blocked'],
|
||||||
'roles' => ['?'],
|
'roles' => ['?'],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@ -113,16 +112,27 @@ class SecurityController extends Controller
|
|||||||
|
|
||||||
/** @var LoginForm $form */
|
/** @var LoginForm $form */
|
||||||
$form = $this->make(LoginForm::class);
|
$form = $this->make(LoginForm::class);
|
||||||
|
|
||||||
/** @var FormEvent $event */
|
/** @var FormEvent $event */
|
||||||
$event = $this->make(FormEvent::class, [$form]);
|
$event = $this->make(FormEvent::class, [$form]);
|
||||||
|
|
||||||
if (Yii::$app->request->isAjax && $form->load(Yii::$app->request->post())) {
|
if (Yii::$app->request->isAjax && $form->load(Yii::$app->request->post())) {
|
||||||
|
|
||||||
Yii::$app->response->format = Response::FORMAT_JSON;
|
Yii::$app->response->format = Response::FORMAT_JSON;
|
||||||
|
|
||||||
return ActiveForm::validate($form);
|
return ActiveForm::validate($form);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($form->load(Yii::$app->request->post())) {
|
if ($form->load(Yii::$app->request->post())) {
|
||||||
|
|
||||||
|
if ($this->module->enableTwoFactorAuthentication && $form->validate()) {
|
||||||
|
if ($form->getUser()->auth_tf_enabled) {
|
||||||
|
Yii::$app->session->set('credentials', ['login' => $form->login, 'pwd' => $form->password]);
|
||||||
|
|
||||||
|
return $this->redirect(['confirm']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->trigger(FormEvent::EVENT_BEFORE_LOGIN, $event);
|
$this->trigger(FormEvent::EVENT_BEFORE_LOGIN, $event);
|
||||||
if ($form->login()) {
|
if ($form->login()) {
|
||||||
$form->getUser()->updateAttributes(['last_login_at' => time()]);
|
$form->getUser()->updateAttributes(['last_login_at' => time()]);
|
||||||
@ -142,6 +152,59 @@ class SecurityController extends Controller
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function actionConfirm()
|
||||||
|
{
|
||||||
|
if (!Yii::$app->user->getIsGuest()) {
|
||||||
|
return $this->goHome();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Yii::$app->session->has('credentials')) {
|
||||||
|
return $this->redirect(['login']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$credentials = Yii::$app->session->get('credentials');
|
||||||
|
/** @var LoginForm $form */
|
||||||
|
$form = $this->make(LoginForm::class);
|
||||||
|
$form->login = $credentials['login'];
|
||||||
|
$form->password = $credentials['pwd'];
|
||||||
|
$form->setScenario('2fa');
|
||||||
|
|
||||||
|
/** @var FormEvent $event */
|
||||||
|
$event = $this->make(FormEvent::class, [$form]);
|
||||||
|
|
||||||
|
if (Yii::$app->request->isAjax && $form->load(Yii::$app->request->post())) {
|
||||||
|
|
||||||
|
Yii::$app->response->format = Response::FORMAT_JSON;
|
||||||
|
|
||||||
|
return ActiveForm::validate($form);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($form->load(Yii::$app->request->post())) {
|
||||||
|
|
||||||
|
$this->trigger(FormEvent::EVENT_BEFORE_LOGIN, $event);
|
||||||
|
|
||||||
|
if ($form->login()) {
|
||||||
|
|
||||||
|
Yii::$app->session->set('credentials', null);
|
||||||
|
|
||||||
|
$form->getUser()->updateAttributes(['last_login_at' => time()]);
|
||||||
|
|
||||||
|
$this->trigger(FormEvent::EVENT_AFTER_LOGIN, $event);
|
||||||
|
|
||||||
|
return $this->goBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render(
|
||||||
|
'confirm',
|
||||||
|
[
|
||||||
|
'model' => $form,
|
||||||
|
'module' => $this->module,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public function actionLogout()
|
public function actionLogout()
|
||||||
{
|
{
|
||||||
$event = $this->make(UserEvent::class, [Yii::$app->getUser()->getIdentity()]);
|
$event = $this->make(UserEvent::class, [Yii::$app->getUser()->getIdentity()]);
|
||||||
|
|||||||
@ -24,14 +24,17 @@ use Da\User\Query\ProfileQuery;
|
|||||||
use Da\User\Query\SocialNetworkAccountQuery;
|
use Da\User\Query\SocialNetworkAccountQuery;
|
||||||
use Da\User\Query\UserQuery;
|
use Da\User\Query\UserQuery;
|
||||||
use Da\User\Service\EmailChangeService;
|
use Da\User\Service\EmailChangeService;
|
||||||
|
use Da\User\Service\TwoFactorQrCodeUriGeneratorService;
|
||||||
use Da\User\Traits\ContainerAwareTrait;
|
use Da\User\Traits\ContainerAwareTrait;
|
||||||
use Da\User\Validator\AjaxRequestModelValidator;
|
use Da\User\Validator\AjaxRequestModelValidator;
|
||||||
|
use Da\User\Validator\TwoFactorCodeValidator;
|
||||||
use Yii;
|
use Yii;
|
||||||
use yii\filters\AccessControl;
|
use yii\filters\AccessControl;
|
||||||
use yii\filters\VerbFilter;
|
use yii\filters\VerbFilter;
|
||||||
use yii\web\Controller;
|
use yii\web\Controller;
|
||||||
use yii\web\ForbiddenHttpException;
|
use yii\web\ForbiddenHttpException;
|
||||||
use yii\web\NotFoundHttpException;
|
use yii\web\NotFoundHttpException;
|
||||||
|
use yii\web\Response;
|
||||||
|
|
||||||
class SettingsController extends Controller
|
class SettingsController extends Controller
|
||||||
{
|
{
|
||||||
@ -81,6 +84,7 @@ class SettingsController extends Controller
|
|||||||
'actions' => [
|
'actions' => [
|
||||||
'disconnect' => ['post'],
|
'disconnect' => ['post'],
|
||||||
'delete' => ['post'],
|
'delete' => ['post'],
|
||||||
|
'two-factor-disable' => ['post']
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'access' => [
|
'access' => [
|
||||||
@ -88,7 +92,16 @@ class SettingsController extends Controller
|
|||||||
'rules' => [
|
'rules' => [
|
||||||
[
|
[
|
||||||
'allow' => true,
|
'allow' => true,
|
||||||
'actions' => ['profile', 'account', 'networks', 'disconnect', 'delete'],
|
'actions' => [
|
||||||
|
'profile',
|
||||||
|
'account',
|
||||||
|
'networks',
|
||||||
|
'disconnect',
|
||||||
|
'delete',
|
||||||
|
'two-factor',
|
||||||
|
'two-factor-enable',
|
||||||
|
'two-factor-disable'
|
||||||
|
],
|
||||||
'roles' => ['@'],
|
'roles' => ['@'],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@ -228,4 +241,70 @@ class SettingsController extends Controller
|
|||||||
|
|
||||||
return $this->goHome();
|
return $this->goHome();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function actionTwoFactor($id)
|
||||||
|
{
|
||||||
|
/** @var User $user */
|
||||||
|
$user = $this->userQuery->whereId($id)->one();
|
||||||
|
|
||||||
|
if (null === $user) {
|
||||||
|
throw new NotFoundHttpException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$uri = $this->make(TwoFactorQrCodeUriGeneratorService::class, [$user])->run();
|
||||||
|
|
||||||
|
return $this->renderAjax('two-factor', ['id' => $id, 'uri' => $uri]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function actionTwoFactorEnable($id)
|
||||||
|
{
|
||||||
|
Yii::$app->response->format = Response::FORMAT_JSON;
|
||||||
|
|
||||||
|
/** @var User $user */
|
||||||
|
$user = $this->userQuery->whereId($id)->one();
|
||||||
|
|
||||||
|
if (null === $user) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => Yii::t('usuario', 'User not found.')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$code = Yii::$app->request->get('code');
|
||||||
|
|
||||||
|
$success = $this
|
||||||
|
->make(TwoFactorCodeValidator::class, [$user, $code, $this->module->twoFactorAuthenticationCycles])
|
||||||
|
->validate();
|
||||||
|
|
||||||
|
$success = $success && $user->updateAttributes(['auth_tf_enabled' => '1']);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => $success,
|
||||||
|
'message' => $success
|
||||||
|
? Yii::t('usuario', 'Two factor successfully enabled.')
|
||||||
|
: Yii::t('usuario', 'Verification failed. Please, enter new code.')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function actionTwoFactorDisable($id)
|
||||||
|
{
|
||||||
|
/** @var User $user */
|
||||||
|
$user = $this->userQuery->whereId($id)->one();
|
||||||
|
|
||||||
|
if (null === $user) {
|
||||||
|
throw new NotFoundHttpException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if($user->updateAttributes(['auth_tf_enabled' => '0']))
|
||||||
|
{
|
||||||
|
Yii::$app
|
||||||
|
->getSession()
|
||||||
|
->setFlash('success', Yii::t('usuario', 'Two-factor authorization has been disabled.'));
|
||||||
|
} else {
|
||||||
|
Yii::$app
|
||||||
|
->getSession()
|
||||||
|
->setFlash('danger', Yii::t('usuario', 'Unable to disable two-factor authorization.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect(['account']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ use Da\User\Helper\SecurityHelper;
|
|||||||
use Da\User\Model\User;
|
use Da\User\Model\User;
|
||||||
use Da\User\Query\UserQuery;
|
use Da\User\Query\UserQuery;
|
||||||
use Da\User\Traits\ModuleAwareTrait;
|
use Da\User\Traits\ModuleAwareTrait;
|
||||||
|
use Da\User\Validator\TwoFactorCodeValidator;
|
||||||
use Yii;
|
use Yii;
|
||||||
use yii\base\Model;
|
use yii\base\Model;
|
||||||
|
|
||||||
@ -30,6 +31,10 @@ class LoginForm extends Model
|
|||||||
* @var string User's password
|
* @var string User's password
|
||||||
*/
|
*/
|
||||||
public $password;
|
public $password;
|
||||||
|
/**
|
||||||
|
* @var string User's two-factor authentication code
|
||||||
|
*/
|
||||||
|
public $twoFactorAuthenticationCode;
|
||||||
/**
|
/**
|
||||||
* @var bool whether to remember User's login
|
* @var bool whether to remember User's login
|
||||||
*/
|
*/
|
||||||
@ -68,6 +73,7 @@ class LoginForm extends Model
|
|||||||
'login' => Yii::t('usuario', 'Login'),
|
'login' => Yii::t('usuario', 'Login'),
|
||||||
'password' => Yii::t('usuario', 'Password'),
|
'password' => Yii::t('usuario', 'Password'),
|
||||||
'rememberMe' => Yii::t('usuario', 'Remember me next time'),
|
'rememberMe' => Yii::t('usuario', 'Remember me next time'),
|
||||||
|
'twoFactorAuthenticationCode' => Yii::t('usuario', 'Two-factor authentication code')
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +84,13 @@ class LoginForm extends Model
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'requiredFields' => [['login', 'password'], 'required'],
|
'requiredFields' => [['login', 'password'], 'required'],
|
||||||
|
'requiredFieldsTwoFactor' => [
|
||||||
|
['login', 'password', 'twoFactorAuthenticationCode'],
|
||||||
|
'required',
|
||||||
|
'on' => '2fa'
|
||||||
|
],
|
||||||
'loginTrim' => ['login', 'trim'],
|
'loginTrim' => ['login', 'trim'],
|
||||||
|
'twoFactorAuthenticationCodeTrim' => ['twoFactorAuthenticationCode', 'trim'],
|
||||||
'passwordValidate' => [
|
'passwordValidate' => [
|
||||||
'password',
|
'password',
|
||||||
function ($attribute) {
|
function ($attribute) {
|
||||||
@ -89,6 +101,20 @@ class LoginForm extends Model
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
'twoFactorAuthenticationCodeValidate' => [
|
||||||
|
'twoFactorAuthenticationCode',
|
||||||
|
function ($attribute) {
|
||||||
|
if ($this->user === null ||
|
||||||
|
!(new TwoFactorCodeValidator(
|
||||||
|
$this->user,
|
||||||
|
$this->twoFactorAuthenticationCode,
|
||||||
|
$this->module->twoFactorAuthenticationCycles
|
||||||
|
))
|
||||||
|
->validate()) {
|
||||||
|
$this->addError($attribute, Yii::t('usuario', 'Invalid two-factor code'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
'confirmationValidate' => [
|
'confirmationValidate' => [
|
||||||
'login',
|
'login',
|
||||||
function ($attribute) {
|
function ($attribute) {
|
||||||
|
|||||||
29
src/User/Migration/m000000_000006_add_two_factor_fields.php
Normal file
29
src/User/Migration/m000000_000006_add_two_factor_fields.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the 2amigos/yii2-usuario project.
|
||||||
|
*
|
||||||
|
* (c) 2amigOS! <http://2amigos.us/>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Da\User\Migration;
|
||||||
|
|
||||||
|
use yii\db\Migration;
|
||||||
|
|
||||||
|
class m000000_000006_add_two_factor_fields extends Migration
|
||||||
|
{
|
||||||
|
public function safeUp()
|
||||||
|
{
|
||||||
|
$this->addColumn('{{%user}}', 'auth_tf_key', $this->string(16));
|
||||||
|
$this->addColumn('{{%user}}', 'auth_tf_enabled', $this->boolean()->defaultValue(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function safeDown()
|
||||||
|
{
|
||||||
|
$this->dropColumn('{{%user}}', 'auth_tf_key');
|
||||||
|
$this->dropColumn('{{%user}}', 'auth_tf_enabled');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -37,6 +37,8 @@ use yii\web\IdentityInterface;
|
|||||||
* @property string $unconfirmed_email
|
* @property string $unconfirmed_email
|
||||||
* @property string $password_hash
|
* @property string $password_hash
|
||||||
* @property string $auth_key
|
* @property string $auth_key
|
||||||
|
* @property string $auth_tf_key
|
||||||
|
* @property int $auth_tf_enabled
|
||||||
* @property int $registration_ip
|
* @property int $registration_ip
|
||||||
* @property int $confirmed_at
|
* @property int $confirmed_at
|
||||||
* @property int $blocked_at
|
* @property int $blocked_at
|
||||||
@ -188,6 +190,11 @@ class User extends ActiveRecord implements IdentityInterface
|
|||||||
'passwordTrim' => ['password', 'trim'],
|
'passwordTrim' => ['password', 'trim'],
|
||||||
'passwordRequired' => ['password', 'required', 'on' => ['register']],
|
'passwordRequired' => ['password', 'required', 'on' => ['register']],
|
||||||
'passwordLength' => ['password', 'string', 'min' => 6, 'max' => 72, 'on' => ['register', 'create']],
|
'passwordLength' => ['password', 'string', 'min' => 6, 'max' => 72, 'on' => ['register', 'create']],
|
||||||
|
|
||||||
|
// two factor auth rules
|
||||||
|
'twoFactorSecretTrim' => ['auth_tf_key', 'trim'],
|
||||||
|
'twoFactorSecretLength' => ['auth_tf_key', 'string', 'max' => 16],
|
||||||
|
'twoFactorEnabledNumber' => ['auth_tf_enabled', 'integer']
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,15 @@ use yii\base\Module as BaseModule;
|
|||||||
*/
|
*/
|
||||||
class Module extends BaseModule
|
class Module extends BaseModule
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var bool whether to enable two factor authentication or not
|
||||||
|
*/
|
||||||
|
public $enableTwoFactorAuthentication = false;
|
||||||
|
/**
|
||||||
|
* @var int cycles of key generation are set on 30 sec. To avoid sync issues, increased validity up to 60 sec.
|
||||||
|
* @see http://2fa-library.readthedocs.io/en/latest/
|
||||||
|
*/
|
||||||
|
public $twoFactorAuthenticationCycles = 1;
|
||||||
/**
|
/**
|
||||||
* @var bool whether to allow registration process or not
|
* @var bool whether to allow registration process or not
|
||||||
*/
|
*/
|
||||||
|
|||||||
46
src/User/Service/TwoFactorQrCodeUriGeneratorService.php
Normal file
46
src/User/Service/TwoFactorQrCodeUriGeneratorService.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Da\User\Service;
|
||||||
|
|
||||||
|
use Da\TwoFA\Manager;
|
||||||
|
use Da\TwoFA\Service\QrCodeDataUriGeneratorService;
|
||||||
|
use Da\TwoFA\Service\TOTPSecretKeyUriGeneratorService;
|
||||||
|
use Da\User\Contracts\ServiceInterface;
|
||||||
|
use Da\User\Model\User;
|
||||||
|
use Yii;
|
||||||
|
|
||||||
|
class TwoFactorQrCodeUriGeneratorService implements ServiceInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var User
|
||||||
|
*/
|
||||||
|
protected $user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TwoFactorQrCodeUriGeneratorService constructor.
|
||||||
|
*
|
||||||
|
* @param User $user
|
||||||
|
*/
|
||||||
|
public function __construct(User $user)
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$user = $this->user;
|
||||||
|
if (!$user->auth_tf_key) {
|
||||||
|
$user->auth_tf_key = (new Manager())->generateSecretKey();
|
||||||
|
$user->updateAttributes(['auth_tf_key']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$totpUri = (new TOTPSecretKeyUriGeneratorService(Yii::$app->name, $user->email, $user->auth_tf_key))->run();
|
||||||
|
$dataUri = (new QrCodeDataUriGeneratorService($totpUri))->run();
|
||||||
|
|
||||||
|
return $dataUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
37
src/User/Validator/TwoFactorCodeValidator.php
Normal file
37
src/User/Validator/TwoFactorCodeValidator.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Da\User\Validator;
|
||||||
|
|
||||||
|
use Da\TwoFA\Manager;
|
||||||
|
use Da\User\Contracts\ValidatorInterface;
|
||||||
|
use Da\User\Model\User;
|
||||||
|
|
||||||
|
class TwoFactorCodeValidator implements ValidatorInterface
|
||||||
|
{
|
||||||
|
protected $user;
|
||||||
|
protected $code;
|
||||||
|
protected $cycles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TwoFactorCodeValidator constructor.
|
||||||
|
*
|
||||||
|
* @param User $user
|
||||||
|
* @param $code
|
||||||
|
* @param int $cycles
|
||||||
|
*/
|
||||||
|
public function __construct(User $user, $code, $cycles = 0)
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
$this->code = $code;
|
||||||
|
$this->cycles = $cycles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool|int
|
||||||
|
*/
|
||||||
|
public function validate()
|
||||||
|
{
|
||||||
|
$manager = new Manager();
|
||||||
|
return $manager->setCycles($this->cycles)->verify($this->code, $this->user->auth_tf_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
70
src/User/resources/views/security/confirm.php
Normal file
70
src/User/resources/views/security/confirm.php
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the 2amigos/yii2-usuario project.
|
||||||
|
*
|
||||||
|
* (c) 2amigOS! <http://2amigos.us/>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Da\User\Widget\ConnectWidget;
|
||||||
|
use yii\helpers\Html;
|
||||||
|
use yii\widgets\ActiveForm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var yii\web\View $this
|
||||||
|
* @var \Da\User\Form\LoginForm $model
|
||||||
|
* @var \Da\User\Module $module
|
||||||
|
*/
|
||||||
|
|
||||||
|
$this->title = Yii::t('usuario', 'Sign in');
|
||||||
|
$this->params['breadcrumbs'][] = $this->title;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?= $this->render('/shared/_alert', ['module' => Yii::$app->getModule('user')]) ?>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 col-md-offset-4 col-sm-6 col-sm-offset-3">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title"><?= Html::encode($this->title) ?></h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<?php $form = ActiveForm::begin(
|
||||||
|
[
|
||||||
|
'id' => $model->formName(),
|
||||||
|
'enableAjaxValidation' => true,
|
||||||
|
'enableClientValidation' => false,
|
||||||
|
'validateOnBlur' => false,
|
||||||
|
'validateOnType' => false,
|
||||||
|
'validateOnChange' => false,
|
||||||
|
]
|
||||||
|
) ?>
|
||||||
|
<?= $form->field(
|
||||||
|
$model,
|
||||||
|
'twoFactorAuthenticationCode',
|
||||||
|
['inputOptions' => ['autofocus' => 'autofocus', 'class' => 'form-control', 'tabindex' => '1']]
|
||||||
|
) ?>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<?= Html::a(
|
||||||
|
Yii::t('usuario', 'Cancel'),
|
||||||
|
['login'],
|
||||||
|
['class' => 'btn btn-default btn-block', 'tabindex' => '3']
|
||||||
|
) ?>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<?= Html::submitButton(
|
||||||
|
Yii::t('usuario', 'Confirm'),
|
||||||
|
['class' => 'btn btn-primary btn-block', 'tabindex' => '3']
|
||||||
|
) ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php ActiveForm::end(); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use yii\helpers\Html;
|
use yii\helpers\Html;
|
||||||
|
use yii\helpers\Url;
|
||||||
use yii\widgets\ActiveForm;
|
use yii\widgets\ActiveForm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,6 +21,9 @@ use yii\widgets\ActiveForm;
|
|||||||
|
|
||||||
$this->title = Yii::t('usuario', 'Account settings');
|
$this->title = Yii::t('usuario', 'Account settings');
|
||||||
$this->params['breadcrumbs'][] = $this->title;
|
$this->params['breadcrumbs'][] = $this->title;
|
||||||
|
|
||||||
|
/** @var \Da\User\Module $module */
|
||||||
|
$module = Yii::$app->getModule('user');
|
||||||
?>
|
?>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
@ -68,7 +72,61 @@ $this->params['breadcrumbs'][] = $this->title;
|
|||||||
<?php ActiveForm::end(); ?>
|
<?php ActiveForm::end(); ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php if ($module->enableTwoFactorAuthentication): ?>
|
||||||
|
<div class="modal fade" id="tfmodal" tabindex="-1" role="dialog" aria-labelledby="tfamodalLabel"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
|
||||||
|
aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="myModalLabel">
|
||||||
|
<?= Yii::t('usuario', 'Two Factor Authentication') ?></h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
...
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||||
|
<?= Yii::t('usuario', 'Close') ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel panel-info">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title"><?= Yii::t('usuario', 'Two-Factor Authentication') ?></h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<p>
|
||||||
|
<?= Yii::t('usuario', 'Two-factor auth protects you against stolen credentials') ?>.
|
||||||
|
</p>
|
||||||
|
<div class="text-right">
|
||||||
|
<?= Html::a(
|
||||||
|
Yii::t('usuario', 'Disable Two-Factor Auth'),
|
||||||
|
['two-factor-disable', 'id' => $model->getUser()->id],
|
||||||
|
[
|
||||||
|
'id' => 'disable_tf_btn',
|
||||||
|
'class' => 'btn btn-warning ' . ($model->getUser()->auth_tf_enabled ? '' : 'hide'),
|
||||||
|
'data-method' => 'post',
|
||||||
|
'data-confirm' => Yii::t('usuario', 'This will disable two-factor auth. Are you sure?'),
|
||||||
|
]
|
||||||
|
) ?>
|
||||||
|
<?= Html::a(
|
||||||
|
Yii::t('usuario', 'Enable Two-factor auth'),
|
||||||
|
'#tfmodal',
|
||||||
|
[
|
||||||
|
'id' => 'enable_tf_btn',
|
||||||
|
'class' => 'btn btn-info ' . ($model->getUser()->auth_tf_enabled ? 'hide' : ''),
|
||||||
|
'data-toggle' => 'modal',
|
||||||
|
'data-target' => '#tfmodal'
|
||||||
|
]
|
||||||
|
) ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
<?php if ($model->module->allowAccountDelete): ?>
|
<?php if ($model->module->allowAccountDelete): ?>
|
||||||
<div class="panel panel-danger">
|
<div class="panel panel-danger">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
@ -80,6 +138,7 @@ $this->params['breadcrumbs'][] = $this->title;
|
|||||||
<?= Yii::t('usuario', 'It will be deleted forever') ?>.
|
<?= Yii::t('usuario', 'It will be deleted forever') ?>.
|
||||||
<?= Yii::t('usuario', 'Please be certain') ?>.
|
<?= Yii::t('usuario', 'Please be certain') ?>.
|
||||||
</p>
|
</p>
|
||||||
|
<div class="text-right">
|
||||||
<?= Html::a(
|
<?= Html::a(
|
||||||
Yii::t('usuario', 'Delete account'),
|
Yii::t('usuario', 'Delete account'),
|
||||||
['delete'],
|
['delete'],
|
||||||
@ -91,6 +150,47 @@ $this->params['breadcrumbs'][] = $this->title;
|
|||||||
) ?>
|
) ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php if ($module->enableTwoFactorAuthentication): ?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// This script should be in fact in a module as an external file
|
||||||
|
// consider overriding this view and include your very own approach
|
||||||
|
$uri = Url::to(['two-factor', 'id' => $model->getUser()->id]);
|
||||||
|
$verify = Url::to(['two-factor-enable', 'id' => $model->getUser()->id]);
|
||||||
|
$js = <<<JS
|
||||||
|
$('#tfmodal')
|
||||||
|
.on('show.bs.modal', function(){
|
||||||
|
if(!$('img#qrCode').length) {
|
||||||
|
$(this).find('.modal-body').load('{$uri}');
|
||||||
|
} else {
|
||||||
|
$('input#tfcode').val('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document)
|
||||||
|
.on('click', '.btn-submit-code', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var btn = $(this);
|
||||||
|
btn.prop('disabled', true);
|
||||||
|
|
||||||
|
$.getJSON('{$verify}', {code: $('#tfcode').val()}, function(data){
|
||||||
|
btn.prop('disabled', false);
|
||||||
|
if(data.success) {
|
||||||
|
$('#enable_tf_btn, #disable_tf_btn').toggleClass('hide');
|
||||||
|
$('#tfmessage').removeClass('alert-danger').addClass('alert-success').find('p').text(data.message);
|
||||||
|
setTimeout(function() { $('#tfmodal').modal('hide'); }, 2000);
|
||||||
|
} else {
|
||||||
|
$('input#tfcode').val('');
|
||||||
|
$('#tfmessage').removeClass('alert-info').addClass('alert-danger').find('p').text(data.message);
|
||||||
|
}
|
||||||
|
}).fail(function(){ btn.prop('disabled', false); });
|
||||||
|
});
|
||||||
|
JS;
|
||||||
|
|
||||||
|
$this->registerJs($js);
|
||||||
|
?>
|
||||||
|
<?php endif; ?>
|
||||||
|
|||||||
40
src/User/resources/views/settings/two-factor.php
Normal file
40
src/User/resources/views/settings/two-factor.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of the 2amigos/yii2-usuario-app project.
|
||||||
|
*
|
||||||
|
* (c) 2amigOS! <http://2amigos.us/>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @var string $id */
|
||||||
|
/** @var string $uri */
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="alert alert-info" id="tfmessage">
|
||||||
|
<p>
|
||||||
|
<?= Yii::t(
|
||||||
|
'usuario',
|
||||||
|
'Scan the QrCode with Google Authenticator App, then insert its temporary code on the box and submit.'
|
||||||
|
) ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-offset-3 col-md-6 text-center">
|
||||||
|
<img id="qrCode" src="<?= $uri ?>"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-offset-3 col-md-6 text-center">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" id="tfcode" placeholder="<?= Yii::t('usuario', 'Two-factor code') ?>"/>
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button type="button" class="btn btn-primary btn-submit-code">
|
||||||
|
<?= Yii::t('usuario', 'Enable') ?>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -8,7 +8,7 @@ return [
|
|||||||
'@Da/User' => dirname(dirname(dirname(__DIR__))) . '/src/User',
|
'@Da/User' => dirname(dirname(dirname(__DIR__))) . '/src/User',
|
||||||
'@tests' => dirname(dirname(__DIR__)),
|
'@tests' => dirname(dirname(__DIR__)),
|
||||||
'@vendor' => VENDOR_DIR,
|
'@vendor' => VENDOR_DIR,
|
||||||
'@bower' => VENDOR_DIR . '/bower',
|
'@bower' => VENDOR_DIR . '/bower-asset',
|
||||||
],
|
],
|
||||||
'bootstrap' => ['Da\User\Bootstrap'],
|
'bootstrap' => ['Da\User\Bootstrap'],
|
||||||
'modules' => [
|
'modules' => [
|
||||||
|
|||||||
Reference in New Issue
Block a user