Move 2FA to profile #1153

This commit is contained in:
James Cole 2018-03-09 05:45:22 +01:00
parent ad18b9b81b
commit dff2d716a1
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
9 changed files with 155 additions and 190 deletions

View File

@ -22,11 +22,9 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use FireflyIII\Http\Requests\TokenFormRequest;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Google2FA;
use Illuminate\Http\Request;
use Preferences;
use Session;
@ -54,35 +52,6 @@ class PreferencesController extends Controller
);
}
/**
* @return View
*/
public function code()
{
$domain = $this->getDomain();
$secret = Google2FA::generateSecretKey();
Session::flash('two-factor-secret', $secret);
$image = Google2FA::getQRCodeInline($domain, auth()->user()->email, $secret, 200);
return view('preferences.code', compact('image'));
}
/**
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*
* @throws \Exception
* @throws \Exception
*/
public function deleteCode()
{
Preferences::delete('twoFactorAuthEnabled');
Preferences::delete('twoFactorAuthSecret');
Session::flash('success', strval(trans('firefly.pref_two_factor_auth_disabled')));
Session::flash('info', strval(trans('firefly.pref_two_factor_auth_remove_it')));
return redirect(route('preferences.index'));
}
/**
* @param AccountRepositoryInterface $repository
*
@ -97,12 +66,9 @@ class PreferencesController extends Controller
$language = Preferences::get('language', config('firefly.default_language', 'en_US'))->data;
$listPageSize = Preferences::get('listPageSize', 50)->data;
$customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
$showDeps = Preferences::get('showDepositsFrontpage', false)->data;
$fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
$fiscalYearStart = date('Y') . '-' . $fiscalYearStartStr;
$tjOptionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$is2faEnabled = Preferences::get('twoFactorAuthEnabled', 0)->data; // twoFactorAuthEnabled
$has2faSecret = null !== Preferences::get('twoFactorAuthSecret'); // hasTwoFactorAuthSecret
return view(
'preferences.index',
@ -114,31 +80,11 @@ class PreferencesController extends Controller
'viewRange',
'customFiscalYear',
'listPageSize',
'fiscalYearStart',
'is2faEnabled',
'has2faSecret',
'showDeps'
'fiscalYearStart'
)
);
}
/**
* @param TokenFormRequest $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @SuppressWarnings(PHPMD.UnusedFormalParameter) // it's unused but the class does some validation.
*/
public function postCode(/** @scrutinizer ignore-unused */ TokenFormRequest $request)
{
Preferences::set('twoFactorAuthEnabled', 1);
Preferences::set('twoFactorAuthSecret', Session::get('two-factor-secret'));
Session::flash('success', strval(trans('firefly.saved_preferences')));
Preferences::mark();
return redirect(route('preferences.index'));
}
/**
* @param Request $request
* @param UserRepositoryInterface $repository
@ -169,10 +115,6 @@ class PreferencesController extends Controller
Preferences::set('customFiscalYear', $customFiscalYear);
Preferences::set('fiscalYearStart', $fiscalYearStart);
// show deposits frontpage:
$showDepositsFrontpage = 1 === intval($request->get('showDepositsFrontpage'));
Preferences::set('showDepositsFrontpage', $showDepositsFrontpage);
// save page size:
Preferences::set('listPageSize', 50);
$listPageSize = intval($request->get('listPageSize'));
@ -180,19 +122,6 @@ class PreferencesController extends Controller
Preferences::set('listPageSize', $listPageSize);
}
$twoFactorAuthEnabled = false;
$hasTwoFactorAuthSecret = false;
if (!$repository->hasRole(auth()->user(), 'demo')) {
// two factor auth
$twoFactorAuthEnabled = intval($request->get('twoFactorAuthEnabled'));
$hasTwoFactorAuthSecret = null !== Preferences::get('twoFactorAuthSecret');
// If we already have a secret, just set the two factor auth enabled to 1, and let the user continue with the existing secret.
if ($hasTwoFactorAuthSecret) {
Preferences::set('twoFactorAuthEnabled', $twoFactorAuthEnabled);
}
}
// language:
$lang = $request->get('language');
if (in_array($lang, array_keys(config('firefly.languages')))) {
@ -217,23 +146,6 @@ class PreferencesController extends Controller
Session::flash('success', strval(trans('firefly.saved_preferences')));
Preferences::mark();
// if we don't have a valid secret yet, redirect to the code page.
// AND USER HAS ACTUALLY ENABLED 2FA
if (!$hasTwoFactorAuthSecret && 1 === $twoFactorAuthEnabled) {
return redirect(route('preferences.code'));
}
return redirect(route('preferences.index'));
}
/**
* @return string
*/
private function getDomain(): string
{
$url = url()->to('/');
$parts = parse_url($url);
return $parts['host'];
}
}

View File

@ -31,9 +31,11 @@ use FireflyIII\Http\Middleware\IsSandStormUser;
use FireflyIII\Http\Requests\DeleteAccountFormRequest;
use FireflyIII\Http\Requests\EmailFormRequest;
use FireflyIII\Http\Requests\ProfileFormRequest;
use FireflyIII\Http\Requests\TokenFormRequest;
use FireflyIII\Models\Preference;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Google2FA;
use Hash;
use Illuminate\Contracts\Auth\Guard;
use Log;
@ -92,6 +94,50 @@ class ProfileController extends Controller
return view('profile.change-password', compact('title', 'subTitle', 'subTitleIcon'));
}
/**
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function deleteCode()
{
Preferences::delete('twoFactorAuthEnabled');
Preferences::delete('twoFactorAuthSecret');
Session::flash('success', strval(trans('firefly.pref_two_factor_auth_disabled')));
Session::flash('info', strval(trans('firefly.pref_two_factor_auth_remove_it')));
return redirect(route('profile.index'));
}
/**
* View that generates a 2FA code for the user.
* @return View
*/
public function code()
{
$domain = $this->getDomain();
$secret = Google2FA::generateSecretKey();
Session::flash('two-factor-secret', $secret);
$image = Google2FA::getQRCodeInline($domain, auth()->user()->email, $secret, 200);
return view('profile.code', compact('image'));
}
/**
* @param TokenFormRequest $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @SuppressWarnings(PHPMD.UnusedFormalParameter) // it's unused but the class does some validation.
*/
public function postCode(TokenFormRequest $request)
{
Preferences::set('twoFactorAuthEnabled', 1);
Preferences::set('twoFactorAuthSecret', Session::get('two-factor-secret'));
Session::flash('success', strval(trans('firefly.saved_preferences')));
Preferences::mark();
return redirect(route('profile.index'));
}
/**
* @param UserRepositoryInterface $repository
* @param string $token
@ -139,13 +185,37 @@ class ProfileController extends Controller
return view('profile.delete-account', compact('title', 'subTitle', 'subTitleIcon'));
}
/**
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function enable2FA(UserRepositoryInterface $repository)
{
if ($repository->hasRole(auth()->user(), 'demo')) {
return redirect(route('profile.index'));
}
$hasTwoFactorAuthSecret = (null !== Preferences::get('twoFactorAuthSecret'));
// if we don't have a valid secret yet, redirect to the code page to get one.
if (!$hasTwoFactorAuthSecret) {
return redirect(route('profile.code'));
}
// If FF3 already has a secret, just set the two factor auth enabled to 1,
// and let the user continue with the existing secret.
Preferences::set('twoFactorAuthEnabled', 1);
return redirect(route('profile.index'));
}
/**
* @return View
*/
public function index()
{
$subTitle = auth()->user()->email;
$userId = auth()->user()->id;
$subTitle = auth()->user()->email;
$userId = auth()->user()->id;
$enabled2FA = intval(Preferences::get('twoFactorAuthEnabled', 0)->data) === 1;
// get access token or create one.
$accessToken = Preferences::get('access_token', null);
@ -154,7 +224,7 @@ class ProfileController extends Controller
$accessToken = Preferences::set('access_token', $token);
}
return view('profile.index', compact('subTitle', 'userId', 'accessToken'));
return view('profile.index', compact('subTitle', 'userId', 'accessToken', 'enabled2FA'));
}
/**
@ -332,4 +402,15 @@ class ProfileController extends Controller
return true;
}
/**
* @return string
*/
private function getDomain(): string
{
$url = url()->to('/');
$parts = parse_url($url);
return $parts['host'];
}
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Support;
use Cache;
use Exception;
use FireflyIII\Models\Preference;
use FireflyIII\User;
use Illuminate\Support\Collection;
@ -50,8 +51,6 @@ class Preferences
* @param $name
*
* @return bool
*
* @throws \Exception
*/
public function delete(string $name): bool
{
@ -59,7 +58,11 @@ class Preferences
if (Cache::has($fullName)) {
Cache::forget($fullName);
}
Preference::where('user_id', auth()->user()->id)->where('name', $name)->delete();
try {
Preference::where('user_id', auth()->user()->id)->where('name', $name)->delete();
} catch (Exception $e) {
// don't care.
}
return true;
}

View File

@ -446,8 +446,7 @@ return [
'pref_two_factor_auth_code' => 'Verify code',
'pref_two_factor_auth_code_help' => 'Scan the QR code with an application on your phone such as Authy or Google Authenticator and enter the generated code.',
'pref_two_factor_auth_reset_code' => 'Reset verification code',
'pref_two_factor_auth_remove_code' => 'Remove verification code',
'pref_two_factor_auth_remove_will_disable' => '(this will also disable two-factor authentication)',
'pref_two_factor_auth_disable_2fa' => 'Disable 2FA',
'pref_save_settings' => 'Save settings',
'saved_preferences' => 'Preferences saved!',
'preferences_general' => 'General',

View File

@ -15,7 +15,6 @@
<ul class="nav nav-tabs">
<li class="active"><a href="#general" data-toggle="tab" aria-expanded="true">{{ 'preferences_general'|_ }}</a></li>
<li class=""><a href="#frontpage" data-toggle="tab" aria-expanded="false">{{ 'preferences_frontpage'|_ }}</a></li>
<li class=""><a href="#security" data-toggle="tab" aria-expanded="false">{{ 'preferences_security'|_ }}</a></li>
<li class=""><a href="#layout" data-toggle="tab" aria-expanded="false">{{ 'preferences_layout'|_ }}</a></li>
</ul>
<div class="tab-content">
@ -116,70 +115,6 @@
</div>
</div>
{# frontpage settings column b #}
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
{# show deposit chart #}
<div class="preferences-box">
<h3>{{ 'pref_home_show_deposits'|_ }}</h3>
<p class="text-info">{{ 'pref_home_show_deposits_info'|_ }}</p>
<div class="form-group">
<div class="col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox" name="showDepositsFrontpage[]" value="{{ showDeps }}"
{% if showDeps %}
checked
{% endif %}
> {{ 'pref_home_do_show_deposits'|_ }}
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="security">
{# security settings here #}
<div class="row">
{# security settings column A #}
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
{# 2fa #}
<div class="preferences-box">
<h3>{{ 'pref_two_factor_auth'|_ }}</h3>
<p class="text-info">{{ 'pref_two_factor_auth_help'|_ }}</p>
<div class="form-group">
<div class="col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox" name="twoFactorAuthEnabled" value="1"
{% if is2faEnabled == '1' %} checked {% endif %}> {{ 'pref_enable_two_factor_auth'|_ }}
</label>
</div>
</div>
{% if is2faEnabled == 1 and has2faSecret == true %}
<div class="col-sm-10">
<div class="checkbox">
<label>
<a href="{{ route('preferences.code') }}">{{ 'pref_two_factor_auth_reset_code'|_ }}</a>
</label>
</div>
<div class="checkbox">
<label>
<a href="{{ route('preferences.delete-code') }}">{{ 'pref_two_factor_auth_remove_code'|_ }}</a>
{{ 'pref_two_factor_auth_remove_will_disable'|_ }}
</label>
</div>
</div>
{% endif %}
</div>
</div>
</div>
{# security settings column B #}
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
</div>
</div>

View File

@ -5,10 +5,10 @@
{% endblock %}
{% block content %}
<form method="POST" action="{{ route('preferences.code.store') }}" accept-charset="UTF-8" class="form-horizontal" id="preferences_code">
<form method="POST" action="{{ route('profile.code.store') }}" accept-charset="UTF-8" class="form-horizontal" id="preferences_code">
<input name="_token" type="hidden" value="{{ csrf_token() }}">
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-6">
<div class="col-lg-8 col-lg-offset-2 col-md-12 col-sm-12 col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'pref_two_factor_auth_code'|_ }}</h3>
@ -19,22 +19,21 @@
</p>
<div class="form group">
<div class="col-sm-8 col-md-offset-4">
<img src="{{ image }}" alt="" title=""/>
<br/><br/>
<img src="{{ image }}" alt="" title=""
style="border:1px #ddd solid;"/>
</div>
</div>
{{ ExpandedForm.text('code', code) }}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="form-group">
<div class="col-sm-12">
<div class="col-lg-8 col-lg-offset-2 col-md-12 col-sm-12 col-xs-12">
<div class="box">
<div class="box-body">
{{ ExpandedForm.text('code', code) }}
</div>
<div class="box-footer">
<button type="submit" class="btn btn-success btn-lg">{{ 'pref_save_settings'|_ }}</button>
</div>
</div>
@ -43,12 +42,12 @@
</form>
{% endblock %}
{% block scripts %}
<script type="text/javascript">
$(function () {
"use strict";
<script type="text/javascript">
$(function () {
"use strict";
// Focus first visible form element.
$("form#preferences_code input:enabled:visible:first").first().select();
});
</script>
// Focus first visible form element.
$("form#preferences_code input:enabled:visible:first").first().select();
});
</script>
{% endblock %}

View File

@ -11,7 +11,7 @@
<div class="row">
<div class="col-lg-6 col-lg-offset-3 col-md-6 col-sm-12">
<div class="col-lg-8 col-lg-offset-2 col-md-12 col-sm-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">{{ 'options'|_ }}</h3>
@ -30,7 +30,7 @@
</div>
</div>
<div class="row">
<div class="col-lg-6 col-lg-offset-3 col-md-6 col-sm-12">
<div class="col-lg-8 col-lg-offset-2 col-md-12 col-sm-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">{{ 'command_line_token'|_ }}</h3>
@ -53,18 +53,49 @@
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-lg-offset-3 col-md-6 col-sm-12">
<div class="col-lg-8 col-lg-offset-2 col-md-12 col-sm-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">{{ 'pref_two_factor_auth'|_ }}</h3>
</div>
<div class="box-body">
<p class="text-info">{{ 'pref_two_factor_auth_help'|_ }}</p>
{% if enabled2FA == true %}
<div class="btn-group">
<a class="btn btn-info" href="{{ route('profile.code') }}">
<i class="fa fa-recycle"></i>
{{ 'pref_two_factor_auth_reset_code'|_ }}</a>
<a class="btn btn-danger" href="{{ route('profile.delete-code') }}">
<i class="fa fa-trash"></i>
{{ 'pref_two_factor_auth_disable_2fa'|_ }}</a>
</div>
{% else %}
<p>
<form action="{{ route('profile.enable2FA') }}" method="post">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-info"><i class="fa fa-lock"></i> {{ 'pref_enable_two_factor_auth'|_ }}</button>
</form>
</p>
{% endif %}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-12 col-sm-12">
<passport-clients></passport-clients>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-lg-offset-3 col-md-6 col-sm-12">
<div class="col-lg-8 col-lg-offset-2 col-md-12 col-sm-12">
<passport-authorized-clients></passport-authorized-clients>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-lg-offset-3 col-md-6 col-sm-12">
<div class="col-lg-8 col-lg-offset-2 col-md-12 col-sm-12">
<passport-personal-access-tokens></passport-personal-access-tokens>
</div>
</div>

View File

@ -602,10 +602,10 @@ Breadcrumbs::register(
);
Breadcrumbs::register(
'preferences.code',
'profile.code',
function (BreadCrumbsGenerator $breadcrumbs) {
$breadcrumbs->parent('home');
$breadcrumbs->push(trans('breadcrumbs.preferences'), route('preferences.index'));
$breadcrumbs->push(trans('breadcrumbs.profile'), route('profile.index'));
}
);

View File

@ -566,10 +566,8 @@ Route::group(
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'preferences', 'as' => 'preferences.'], function () {
Route::get('', ['uses' => 'PreferencesController@index', 'as' => 'index']);
Route::get('/code', ['uses' => 'PreferencesController@code', 'as' => 'code']);
Route::get('/delete-code', ['uses' => 'PreferencesController@deleteCode', 'as' => 'delete-code']);
Route::post('', ['uses' => 'PreferencesController@postIndex', 'as' => 'update']);
Route::post('/code', ['uses' => 'PreferencesController@postCode', 'as' => 'code.store']);
}
);
@ -589,6 +587,13 @@ Route::group(
Route::post('change-password', ['uses' => 'ProfileController@postChangePassword', 'as' => 'change-password.post']);
Route::post('change-email', ['uses' => 'ProfileController@postChangeEmail', 'as' => 'change-email.post']);
Route::post('regenerate', ['uses' => 'ProfileController@regenerate', 'as' => 'regenerate']);
// new 2FA routes
Route::post('enable2FA', ['uses' => 'ProfileController@enable2FA', 'as' => 'enable2FA']);
Route::get('2fa/code', ['uses' => 'ProfileController@code', 'as' => 'code']);
Route::post('2fa/code', ['uses' => 'ProfileController@postCode', 'as' => 'code.store']);
Route::get('/delete-code', ['uses' => 'ProfileController@deleteCode', 'as' => 'delete-code']);
}
);