diff --git a/app/Http/Controllers/PreferencesController.php b/app/Http/Controllers/PreferencesController.php index f27a73797c..9e3ad3ab18 100644 --- a/app/Http/Controllers/PreferencesController.php +++ b/app/Http/Controllers/PreferencesController.php @@ -1,11 +1,15 @@ data; $fiscalYearStart = date('Y') . '-' . $fiscalYearStartStr; $twoFactorAuthEnabled = Preferences::get('twoFactorAuthEnabled', 0)->data; + + $hasTwoFactorAuthSecret = Preferences::get('twoFactorAuthSecret') != null && !empty(Preferences::get('twoFactorAuthSecret')->data); + $showIncomplete = env('SHOW_INCOMPLETE_TRANSLATIONS', false) === true; return view( 'preferences.index', compact( - 'budgetMaximum', 'language', 'accounts', 'frontPageAccounts', 'viewRange', 'customFiscalYear', 'fiscalYearStart', 'twoFactorAuthEnabled', + 'budgetMaximum', 'language', 'accounts', 'frontPageAccounts', 'viewRange', 'customFiscalYear', 'fiscalYearStart', 'twoFactorAuthEnabled', 'hasTwoFactorAuthSecret', 'showIncomplete' ) ); @@ -87,7 +94,14 @@ class PreferencesController extends Controller // two factor auth $twoFactorAuthEnabled = (int)Input::get('twoFactorAuthEnabled'); - Preferences::set('twoFactorAuthEnabled', $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); + } // language: $lang = Input::get('language'); @@ -99,7 +113,44 @@ class PreferencesController extends Controller Session::flash('success', 'Preferences saved!'); 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')); } + /* + * @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')); + } + } diff --git a/app/Http/Requests/TokenFormRequest.php b/app/Http/Requests/TokenFormRequest.php new file mode 100644 index 0000000000..19456f635b --- /dev/null +++ b/app/Http/Requests/TokenFormRequest.php @@ -0,0 +1,39 @@ + 'required', + 'code' => 'required|2faCode:secret', + ]; + + return $rules; + } +} diff --git a/app/Http/routes.php b/app/Http/routes.php index 62e4eaf029..574c333d5b 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -246,6 +246,8 @@ Route::group( */ Route::get('/preferences', ['uses' => 'PreferencesController@index', 'as' => 'preferences']); 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 diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index ed447e81c1..8535ed7fc4 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -16,10 +16,13 @@ use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Rules\Triggers\TriggerInterface; use FireflyIII\User; +use Input; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Validation\Validator; use Log; +use PragmaRX\Google2FA\Contracts\Google2FA; use Symfony\Component\Translation\TranslatorInterface; +use Session; /** * Class FireflyValidator @@ -43,6 +46,29 @@ class FireflyValidator extends Validator 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 $value diff --git a/composer.json b/composer.json index 5e40fb7ba0..8664702215 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "league/csv": "^7.1", "laravelcollective/html": "^5.2", "rmccue/requests": "^1.6" + "pragmarx/google2fa": "^0.7.1" }, "require-dev": { "fzaninotto/faker": "~1.4", diff --git a/config/app.php b/config/app.php index c7255ff5c0..7efa1d2687 100644 --- a/config/app.php +++ b/config/app.php @@ -181,6 +181,7 @@ return [ // Barryvdh\Debugbar\ServiceProvider::class, 'DaveJamesMiller\Breadcrumbs\ServiceProvider', 'TwigBridge\ServiceProvider', + 'PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider', ], @@ -238,7 +239,7 @@ return [ 'ExpandedForm' => 'FireflyIII\Support\Facades\ExpandedForm', 'Entrust' => 'Zizaco\Entrust\EntrustFacade', 'Input' => 'Illuminate\Support\Facades\Input', - + 'Google2FA' => 'PragmaRX\Google2FA\Vendor\Laravel\Facade', ], diff --git a/resources/views/preferences/code.twig b/resources/views/preferences/code.twig new file mode 100644 index 0000000000..fec7b89ebe --- /dev/null +++ b/resources/views/preferences/code.twig @@ -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'}) }} + + +
+
+
+
+

{{ 'pref_code'|_ }}

+
+
+

+ {{ 'pref_code_help'|_ }} +

+
+ +
+ +
+ +
+
+
+

{{ secret }}

+
+
+ + {{ ExpandedForm.text('code', code, {'label' : 'Code'}) }} +
+
+
+ +
+
+
+
+
+ +
+
+
+
+ + + {{ Form.close|raw }} +{% endblock %} \ No newline at end of file diff --git a/resources/views/preferences/index.twig b/resources/views/preferences/index.twig index 78e48f64c0..9e9eae2766 100644 --- a/resources/views/preferences/index.twig +++ b/resources/views/preferences/index.twig @@ -145,19 +145,34 @@

{{ 'pref_two_factor_auth_help'|_ }}

-
-
-
- -
+
+
+
+
+ + {% if twoFactorAuthEnabled == '1' and hasTwoFactorAuthSecret == true %} + + + {% endif %} + +
+ + +
@@ -171,4 +186,4 @@ {{ Form.close|raw }} -{% endblock %} +{% endblock %} \ No newline at end of file