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/breadcrumbs.php b/app/Http/breadcrumbs.php index f4ff12c12d..6d1ae21e9c 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -378,6 +378,14 @@ Breadcrumbs::register( } ); +Breadcrumbs::register( + 'preferences.code', function (BreadCrumbGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('breadcrumbs.preferences'), route('preferences')); + +} +); + /** * PROFILE */ 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..98a2dd3ee7 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "rcrowe/twigbridge": "~0.9", "league/csv": "^7.1", "laravelcollective/html": "^5.2", - "rmccue/requests": "^1.6" + "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/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index a7a371b9d9..fadcca1707 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -230,6 +230,9 @@ return [ 'pref_two_factor_auth' => '2-step verification', 'pref_two_factor_auth_help' => 'When you enable 2-step verification (also known as two-factor authentication), you add an extra layer of security to your account. You sign in with something you know (your password) and something you have (a verification code). Verification codes are generated by an application on your phone, such as Authy or Google Authenticator.', 'pref_enable_two_factor_auth' => 'Enable 2-step verification', + '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_save_settings' => 'Save settings', // profile: diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 5be1b8d145..86b7ece35c 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -68,4 +68,5 @@ return [ 'string' => 'The :attribute must be a string.', 'url' => 'The :attribute format is invalid.', 'timezone' => 'The :attribute must be a valid zone.', + '2fa_code' => 'The :attribute field is invalid.', ]; diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index 253317581d..5d27a49585 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -228,6 +228,9 @@ return [ 'pref_two_factor_auth' => 'Authenticatie in twee stappen', 'pref_two_factor_auth_help' => 'Als je authenticatie in twee stappen (ook wel twee-factor authenticatie genoemd) inschakelt voeg je een extra beveiligingslaag toe aan je account. Je logt in met iets dat je weet (je wachtwoord) en iets dat je hebt (een verificatiecode). Verificatiecodes worden gegeneerd door apps op je telefoon, zoals Authy en Google Authenticator.', 'pref_enable_two_factor_auth' => 'Authenticatie in twee stappen inschakelen', + 'pref_two_factor_auth_code' => 'Verifieer code', + 'pref_two_factor_auth_code_help' => 'Scan onderstaande QR code met een app op je telefoon zoals Authy of Google Authenticator en vul de code die gegenereerd wordt in.', + 'pref_two_factor_auth_reset_code' => 'Reset verificatiecode', 'pref_save_settings' => 'Instellingen opslaan', // profile: diff --git a/resources/lang/nl_NL/validation.php b/resources/lang/nl_NL/validation.php index f2f03ebd0b..dede00973c 100644 --- a/resources/lang/nl_NL/validation.php +++ b/resources/lang/nl_NL/validation.php @@ -68,4 +68,6 @@ return [ 'string' => 'Het :attribute moet een tekenreeks zijn.', 'url' => ':attribute is geen geldige URL.', 'timezone' => 'Het :attribute moet een geldige zone zijn.', + '2fa_code' => ':attribute is ongeldig.', ]; + diff --git a/resources/views/preferences/code.twig b/resources/views/preferences/code.twig new file mode 100644 index 0000000000..778af11096 --- /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_two_factor_auth_code'|_ }}

+
+
+

+ {{ 'pref_two_factor_auth_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..b029a896c9 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