Close #15 added two factor authentication

This commit is contained in:
Antonio Ramirez
2017-09-21 17:48:01 +02:00
parent 5ee4c91e03
commit 308b6a0b2c
15 changed files with 1596 additions and 737 deletions

View File

@ -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)

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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()]);

View File

@ -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']);
}
} }

View File

@ -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) {

View 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');
}
}

View File

@ -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']
]; ];
} }

View File

@ -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
*/ */

View 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;
}
}

View 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);
}
}

View 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>

View File

@ -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">&times;</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; ?>

View 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>

View File

@ -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' => [