From ea52a5202262e3b6e0d44fc70c70307c2c51ba21 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Aug 2019 07:00:47 +0200 Subject: [PATCH] New MFA submit routine with history. --- .../Controllers/Auth/TwoFactorController.php | 96 +++++++++++++++++++ resources/lang/en_US/firefly.php | 29 +++--- resources/views/v1/auth/mfa.twig | 41 +++++++- routes/web.php | 16 ++-- 4 files changed, 160 insertions(+), 22 deletions(-) diff --git a/app/Http/Controllers/Auth/TwoFactorController.php b/app/Http/Controllers/Auth/TwoFactorController.php index 8509d96c5e..67934843ae 100644 --- a/app/Http/Controllers/Auth/TwoFactorController.php +++ b/app/Http/Controllers/Auth/TwoFactorController.php @@ -29,12 +29,85 @@ use FireflyIII\User; use Illuminate\Cookie\CookieJar; use Illuminate\Http\Request; use Log; +use PragmaRX\Google2FALaravel\Support\Authenticator; +use Preferences; /** * Class TwoFactorController. */ class TwoFactorController extends Controller { + /** + * @param Request $request + */ + public function submitMFA(Request $request) + { + /** @var array $mfaHistory */ + $mfaHistory = Preferences::get('mfa_history', [])->data; + $mfaCode = $request->get('one_time_password'); + + // is in history? then refuse to use it. + if ($this->inMFAHistory($mfaCode, $mfaHistory)) { + $this->filterMFAHistory(); + session()->flash('error', trans('firefly.wrong_mfa_code')); + + return redirect(route('home')); + } + + /** @var Authenticator $authenticator */ + $authenticator = app(Authenticator::class)->boot($request); + + if ($authenticator->isAuthenticated()) { + // save MFA in preferences + $this->addToMFAHistory($mfaCode); + + // otp auth success! + return redirect(route('home')); + } + session()->flash('error', trans('firefly.wrong_mfa_code')); + + return redirect(route('home')); + } + + /** + * @param string $mfaCode + */ + private function addToMFAHistory(string $mfaCode): void + { + /** @var array $mfaHistory */ + $mfaHistory = Preferences::get('mfa_history', [])->data; + $entry = [ + 'time' => time(), + 'code' => $mfaCode, + ]; + $mfaHistory[] = $entry; + + Preferences::set('mfa_history', $mfaHistory); + $this->filterMFAHistory(); + } + + /** + * Remove old entries from the preferences array. + */ + private function filterMFAHistory(): void + { + /** @var array $mfaHistory */ + $mfaHistory = Preferences::get('mfa_history', [])->data; + $newHistory = []; + $now = time(); + foreach ($mfaHistory as $entry) { + $time = $entry['time']; + $code = $entry['code']; + if ($now - $time <= 300) { + $newHistory[] = [ + 'time' => $time, + 'code' => $code, + ]; + } + } + Preferences::set('mfa_history', $newHistory); + } + /** * Show 2FA screen. * @@ -117,4 +190,27 @@ class TwoFactorController extends Controller return redirect(route('home'))->withCookie($cookie); } + + /** + * Each MFA history has a timestamp and a code, saving the MFA entries for 5 minutes. So if the + * submitted MFA code has been submitted in the last 5 minutes, it won't work despite being valid. + * + * @param string $mfaCode + * @param array $mfaHistory + * + * @return bool + */ + private function inMFAHistory(string $mfaCode, array $mfaHistory): bool + { + $now = time(); + foreach ($mfaHistory as $entry) { + $time = $entry['time']; + $code = $entry['code']; + if ($code === $mfaCode && $now - $time <= 300) { + return true; + } + } + + return false; + } } diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 26e45502e6..1caf9d9862 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -84,20 +84,20 @@ return [ 'no_help_could_be_found' => 'No help text could be found.', 'no_help_title' => 'Apologies, an error occurred.', 'two_factor_welcome' => 'Hello!', - 'two_factor_enter_code' => 'To continue, please enter your two factor authentication code. Your application can generate it for you.', - 'two_factor_code_here' => 'Enter code here', - 'two_factor_title' => 'Two factor authentication', - 'authenticate' => 'Authenticate', - 'two_factor_forgot_title' => 'Lost two factor authentication', - 'two_factor_forgot' => 'I forgot my two-factor thing.', - 'two_factor_lost_header' => 'Lost your two factor authentication?', - 'two_factor_lost_intro' => 'Unfortunately, this is not something you can reset from the web interface. You have two choices.', - 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions.', - 'two_factor_lost_fix_owner' => 'Otherwise, email the site owner, :site_owner and ask them to reset your two factor authentication.', - 'warning_much_data' => ':days days of data may take a while to load.', - 'registered' => 'You have registered successfully!', - 'Default asset account' => 'Default asset account', - 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', + 'two_factor_enter_code' => 'To continue, please enter your two factor authentication code. Your application can generate it for you.', + 'two_factor_code_here' => 'Enter code here', + 'two_factor_title' => 'Two factor authentication', + 'authenticate' => 'Authenticate', + 'two_factor_forgot_title' => 'Lost two factor authentication', + 'two_factor_forgot' => 'I forgot my two-factor thing.', + 'two_factor_lost_header' => 'Lost your two factor authentication?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions, or run docker logs <container_id> to see the instructions (refresh this page).', + 'two_factor_lost_fix_owner' => 'Otherwise, email the site owner, :site_owner and ask them to reset your two factor authentication.', + 'warning_much_data' => ':days days of data may take a while to load.', + 'registered' => 'You have registered successfully!', + 'Default asset account' => 'Default asset account', + 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', 'Savings account' => 'Savings account', 'Credit card' => 'Credit card', 'source_accounts' => 'Source account(s)', @@ -489,6 +489,7 @@ return [ '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', '2fa_already_enabled' => '2-step verification is already enabled.', + 'wrong_mfa_code' => 'This MFA code is not valid.', 'pref_save_settings' => 'Save settings', 'saved_preferences' => 'Preferences saved!', 'preferences_general' => 'General', diff --git a/resources/views/v1/auth/mfa.twig b/resources/views/v1/auth/mfa.twig index 9acea61fb6..29eab9d37a 100644 --- a/resources/views/v1/auth/mfa.twig +++ b/resources/views/v1/auth/mfa.twig @@ -1 +1,40 @@ -here be mfa \ No newline at end of file +{% extends "./layout/guest" %} + +{% block content %} + {% if session_has('error') %} + + {% endif %} + +
+ + + +
+ + +
+ +
+ +
+
+ +
+
+
+ + + + + {{ 'two_factor_forgot'|_ }} +
+ + + + +{% endblock %} \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 37f85cd626..a24e4fdb31 100644 --- a/routes/web.php +++ b/routes/web.php @@ -45,6 +45,7 @@ Route::group( Route::get('login', 'Auth\LoginController@showLoginForm')->name('login'); Route::post('login', 'Auth\LoginController@login'); + // Registration Routes... Route::get('register', ['uses' => 'Auth\RegisterController@showRegistrationForm', 'as' => 'register']); Route::post('register', 'Auth\RegisterController@register'); @@ -75,16 +76,17 @@ Route::group( } ); -/** - * For the two factor routes, the user must be logged in, but NOT 2FA. Account confirmation does not matter here. - * - */ + +///** +// * For the two factor routes, the user must be logged in, but NOT 2FA. Account confirmation does not matter here. +// * +// */ Route::group( ['middleware' => 'user-logged-in-no-2fa', 'prefix' => 'two-factor', 'as' => 'two-factor.', 'namespace' => 'FireflyIII\Http\Controllers\Auth'], function () { - Route::get('', ['uses' => 'TwoFactorController@index', 'as' => 'index']); + Route::post('submit', ['uses' => 'TwoFactorController@submitMFA', 'as' => 'submit']); Route::get('lost', ['uses' => 'TwoFactorController@lostTwoFactor', 'as' => 'lost']); - Route::post('', ['uses' => 'TwoFactorController@postIndex', 'as' => 'post']); - + // Route::post('', ['uses' => 'TwoFactorController@postIndex', 'as' => 'post']); + // } );