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') %}
+
{{ trans('firefly.two_factor_welcome', {user: user.email}) }}
+{{ 'two_factor_enter_code'|_ }}
+ + + + + + + {{ 'two_factor_forgot'|_ }} +