mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-24 06:43:23 +00:00
Added 2fa code validation
This commit is contained in:
@@ -1,11 +1,15 @@
|
|||||||
<?php namespace FireflyIII\Http\Controllers;
|
<?php namespace FireflyIII\Http\Controllers;
|
||||||
|
|
||||||
|
use Auth;
|
||||||
use Config;
|
use Config;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI;
|
||||||
|
use FireflyIII\Http\Requests\TokenFormRequest;
|
||||||
use Input;
|
use Input;
|
||||||
use Preferences;
|
use Preferences;
|
||||||
|
use Response;
|
||||||
use Session;
|
use Session;
|
||||||
use View;
|
use View;
|
||||||
|
use PragmaRX\Google2FA\Contracts\Google2FA;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class PreferencesController
|
* Class PreferencesController
|
||||||
@@ -43,12 +47,15 @@ class PreferencesController extends Controller
|
|||||||
$fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
|
$fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
|
||||||
$fiscalYearStart = date('Y') . '-' . $fiscalYearStartStr;
|
$fiscalYearStart = date('Y') . '-' . $fiscalYearStartStr;
|
||||||
$twoFactorAuthEnabled = Preferences::get('twoFactorAuthEnabled', 0)->data;
|
$twoFactorAuthEnabled = Preferences::get('twoFactorAuthEnabled', 0)->data;
|
||||||
|
|
||||||
|
$hasTwoFactorAuthSecret = Preferences::get('twoFactorAuthSecret') != null && !empty(Preferences::get('twoFactorAuthSecret')->data);
|
||||||
|
|
||||||
$showIncomplete = env('SHOW_INCOMPLETE_TRANSLATIONS', false) === true;
|
$showIncomplete = env('SHOW_INCOMPLETE_TRANSLATIONS', false) === true;
|
||||||
|
|
||||||
return view(
|
return view(
|
||||||
'preferences.index',
|
'preferences.index',
|
||||||
compact(
|
compact(
|
||||||
'budgetMaximum', 'language', 'accounts', 'frontPageAccounts', 'viewRange', 'customFiscalYear', 'fiscalYearStart', 'twoFactorAuthEnabled',
|
'budgetMaximum', 'language', 'accounts', 'frontPageAccounts', 'viewRange', 'customFiscalYear', 'fiscalYearStart', 'twoFactorAuthEnabled', 'hasTwoFactorAuthSecret',
|
||||||
'showIncomplete'
|
'showIncomplete'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -87,7 +94,14 @@ class PreferencesController extends Controller
|
|||||||
|
|
||||||
// two factor auth
|
// two factor auth
|
||||||
$twoFactorAuthEnabled = (int)Input::get('twoFactorAuthEnabled');
|
$twoFactorAuthEnabled = (int)Input::get('twoFactorAuthEnabled');
|
||||||
|
|
||||||
|
$hasTwoFactorAuthSecret = Preferences::get('twoFactorAuthSecret') != null && !empty(Preferences::get('twoFactorAuthSecret')->data);
|
||||||
|
|
||||||
|
// 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);
|
Preferences::set('twoFactorAuthEnabled', $twoFactorAuthEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
// language:
|
// language:
|
||||||
$lang = Input::get('language');
|
$lang = Input::get('language');
|
||||||
@@ -99,7 +113,44 @@ class PreferencesController extends Controller
|
|||||||
Session::flash('success', 'Preferences saved!');
|
Session::flash('success', 'Preferences saved!');
|
||||||
Preferences::mark();
|
Preferences::mark();
|
||||||
|
|
||||||
|
// if we don't have a valid secret yet, redirect to the code page.
|
||||||
|
if(!$hasTwoFactorAuthSecret)
|
||||||
|
{
|
||||||
|
return redirect(route('preferences.code'));
|
||||||
|
}
|
||||||
|
|
||||||
return redirect(route('preferences'));
|
return redirect(route('preferences'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param TokenFormRequest $request
|
||||||
|
*
|
||||||
|
* @return $this|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function postCode(TokenFormRequest $request)
|
||||||
|
{
|
||||||
|
Preferences::set('twoFactorAuthEnabled', 1);
|
||||||
|
Preferences::set('twoFactorAuthSecret', $request->input('secret'));
|
||||||
|
|
||||||
|
Session::flash('success', 'Preferences saved!');
|
||||||
|
Preferences::mark();
|
||||||
|
|
||||||
|
return redirect(route('preferences'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param Google2FA $google2fa
|
||||||
|
*
|
||||||
|
* @return $this|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function code(Google2FA $google2fa)
|
||||||
|
{
|
||||||
|
$secret = $google2fa->generateSecretKey(16, Auth::user()->id);
|
||||||
|
|
||||||
|
$image = $google2fa->getQRCodeInline("FireflyIII", null, $secret, 150);
|
||||||
|
|
||||||
|
|
||||||
|
return view('preferences.code', compact('secret', 'image'));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
39
app/Http/Requests/TokenFormRequest.php
Normal file
39
app/Http/Requests/TokenFormRequest.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace FireflyIII\Http\Requests;
|
||||||
|
|
||||||
|
use Auth;
|
||||||
|
use Input;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class TokenFormRequest
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Http\Requests
|
||||||
|
*/
|
||||||
|
class TokenFormRequest extends Request
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
// Only allow logged in users
|
||||||
|
return Auth::check();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
'secret' => 'required',
|
||||||
|
'code' => 'required|2faCode:secret',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $rules;
|
||||||
|
}
|
||||||
|
}
|
@@ -246,6 +246,8 @@ Route::group(
|
|||||||
*/
|
*/
|
||||||
Route::get('/preferences', ['uses' => 'PreferencesController@index', 'as' => 'preferences']);
|
Route::get('/preferences', ['uses' => 'PreferencesController@index', 'as' => 'preferences']);
|
||||||
Route::post('/preferences', ['uses' => 'PreferencesController@postIndex']);
|
Route::post('/preferences', ['uses' => 'PreferencesController@postIndex']);
|
||||||
|
Route::get('/preferences/code', ['uses' => 'PreferencesController@code', 'as' => 'preferences.code']);
|
||||||
|
Route::post('/preferences/code', ['uses' => 'PreferencesController@postCode']);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Profile Controller
|
* Profile Controller
|
||||||
|
@@ -16,10 +16,13 @@ use FireflyIII\Models\TransactionType;
|
|||||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
use FireflyIII\Rules\Triggers\TriggerInterface;
|
use FireflyIII\Rules\Triggers\TriggerInterface;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
|
use Input;
|
||||||
use Illuminate\Contracts\Encryption\DecryptException;
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
use Illuminate\Validation\Validator;
|
use Illuminate\Validation\Validator;
|
||||||
use Log;
|
use Log;
|
||||||
|
use PragmaRX\Google2FA\Contracts\Google2FA;
|
||||||
use Symfony\Component\Translation\TranslatorInterface;
|
use Symfony\Component\Translation\TranslatorInterface;
|
||||||
|
use Session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class FireflyValidator
|
* Class FireflyValidator
|
||||||
@@ -43,6 +46,29 @@ class FireflyValidator extends Validator
|
|||||||
parent::__construct($translator, $data, $rules, $messages, $customAttributes);
|
parent::__construct($translator, $data, $rules, $messages, $customAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $attribute
|
||||||
|
* @param $value
|
||||||
|
* @param $parameters
|
||||||
|
*
|
||||||
|
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validate2faCode($attribute, $value, $parameters): bool
|
||||||
|
{
|
||||||
|
if (!is_string($value) || is_null($value) || strlen($value) <> 6) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the secret from our hidden form field.
|
||||||
|
$secret = Input::get($parameters[0]);
|
||||||
|
|
||||||
|
$google2fa = app('PragmaRX\Google2FA\Google2FA');
|
||||||
|
|
||||||
|
return $google2fa->verifyKey($secret, $value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $attribute
|
* @param $attribute
|
||||||
* @param $value
|
* @param $value
|
||||||
|
@@ -23,6 +23,7 @@
|
|||||||
"league/csv": "^7.1",
|
"league/csv": "^7.1",
|
||||||
"laravelcollective/html": "^5.2",
|
"laravelcollective/html": "^5.2",
|
||||||
"rmccue/requests": "^1.6"
|
"rmccue/requests": "^1.6"
|
||||||
|
"pragmarx/google2fa": "^0.7.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fzaninotto/faker": "~1.4",
|
"fzaninotto/faker": "~1.4",
|
||||||
|
@@ -181,6 +181,7 @@ return [
|
|||||||
// Barryvdh\Debugbar\ServiceProvider::class,
|
// Barryvdh\Debugbar\ServiceProvider::class,
|
||||||
'DaveJamesMiller\Breadcrumbs\ServiceProvider',
|
'DaveJamesMiller\Breadcrumbs\ServiceProvider',
|
||||||
'TwigBridge\ServiceProvider',
|
'TwigBridge\ServiceProvider',
|
||||||
|
'PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider',
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -238,7 +239,7 @@ return [
|
|||||||
'ExpandedForm' => 'FireflyIII\Support\Facades\ExpandedForm',
|
'ExpandedForm' => 'FireflyIII\Support\Facades\ExpandedForm',
|
||||||
'Entrust' => 'Zizaco\Entrust\EntrustFacade',
|
'Entrust' => 'Zizaco\Entrust\EntrustFacade',
|
||||||
'Input' => 'Illuminate\Support\Facades\Input',
|
'Input' => 'Illuminate\Support\Facades\Input',
|
||||||
|
'Google2FA' => 'PragmaRX\Google2FA\Vendor\Laravel\Facade',
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
|
52
resources/views/preferences/code.twig
Normal file
52
resources/views/preferences/code.twig
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{% extends "./layout/default.twig" %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{{ Form.open({'class' : 'form-horizontal','id' : 'preferences.code'}) }}
|
||||||
|
<input type="hidden" name="secret" value="{{ secret }}"/>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 col-md-6 col-sm-6">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">{{ 'pref_code'|_ }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body">
|
||||||
|
<p class="text-info">
|
||||||
|
{{ 'pref_code_help'|_ }}
|
||||||
|
</p>
|
||||||
|
<div class="form group">
|
||||||
|
|
||||||
|
<div class="col-sm-8 col-md-offset-4">
|
||||||
|
<img src="{{ image }}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="form group">
|
||||||
|
<div class="col-sm-8 col-md-offset-4">
|
||||||
|
<p>{{ secret }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ ExpandedForm.text('code', code, {'label' : '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">
|
||||||
|
<button type="submit" class="btn btn-success btn-lg">{{ 'pref_save_settings'|_ }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- form close -->
|
||||||
|
{{ Form.close|raw }}
|
||||||
|
{% endblock %}
|
@@ -154,10 +154,25 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if twoFactorAuthEnabled == '1' and hasTwoFactorAuthSecret == true %}
|
||||||
|
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<a href="{{ URL.to('/preferences/code') }}">{{ 'pref_two_factor_auth_reset_secret'|_ }}</a>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||||
|
Reference in New Issue
Block a user