Skip to content

Commit

Permalink
feat: allow recovery codes when disabling 2FA (#4970)
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonVanacco authored May 4, 2021
1 parent bc8782a commit 1f4c4c4
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 39 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@ Jack Kuo @JackKuo-tw <jackkuo@jackkuo.org>
Russell Ault @RussellAult
Martijn van der Ven @Zegnat <martijn@vanderven.se>
Matthew Fitzgerald @mfitzgerald2 <matt@mfitz.net>
Simon Van Accoleyen @SimonVanacco <simon@vanacco.fr>
30 changes: 1 addition & 29 deletions app/Http/Controllers/Auth/RecoveryLoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

namespace App\Http\Controllers\Auth;

use App\Models\User\User;
use Illuminate\Http\Request;
use App\Events\RecoveryLogin;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RedirectsUsers;
Expand Down Expand Up @@ -51,7 +49,7 @@ public function store(Request $request)
$recovery = $request->input('recovery');

if ($user instanceof \App\Models\User\User &&
$this->recoveryLogin($user, $recovery)) {
$user->recoveryChallenge($recovery)) {
$this->fireLoginEvent($user);
} else {
abort(403);
Expand All @@ -60,32 +58,6 @@ public function store(Request $request)
return redirect($this->redirectPath());
}

/**
* Try login with the recovery code.
*
* @param \App\Models\User\User $user
* @param string $recovery
* @return bool
*/
protected function recoveryLogin(User $user, string $recovery)
{
$recoveryCodes = $user->recoveryCodes()
->where('used', false)
->get();

foreach ($recoveryCodes as $recoveryCode) {
if ($recoveryCode->recovery == $recovery) {
$recoveryCode->forceFill([
'used' => true,
])->save();

return true;
}
}

return false;
}

/**
* Fire the login event.
*
Expand Down
35 changes: 27 additions & 8 deletions app/Http/Controllers/Settings/MultiFAController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Http\Controllers\Settings;

use App\Models\User\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
Expand Down Expand Up @@ -108,23 +109,41 @@ public function deactivateTwoFactor(Request $request)

$user = $request->user();

if ($this->validateTwoFactorLogin($request, $user, $request['one_time_password'])) {
//make secret column blank
$user->google2fa_secret = null;
$user->save();

return response()->json(['success' => true]);
}

return response()->json(['success' => false]);
}

/**
* Validate 2nd factor for user with 2FA code or recovery code.
*
* @param Request $request
* @param User $user
* @param string $oneTimePassword
* @return bool
*/
private function validateTwoFactorLogin(Request $request, User $user, string $oneTimePassword): bool
{
//retrieve secret
$secret = $user->google2fa_secret;

$authenticator = app(Authenticator::class)->boot($request);

if ($authenticator->verifyGoogle2FA($secret, $request['one_time_password'])) {

//make secret column blank
$user->google2fa_secret = null;
$user->save();

// try provided token as a 2FA code, or as a recovery code
if ($authenticator->verifyGoogle2FA($secret, $oneTimePassword)
|| $user->recoveryChallenge($oneTimePassword)) {
$authenticator->logout();

return response()->json(['success' => true]);
return true;
}

return response()->json(['success' => false]);
return false;
}

/**
Expand Down
12 changes: 12 additions & 0 deletions app/Models/User/RecoveryCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Models\User;

use App\Models\ModelBinding as Model;
use Illuminate\Database\Eloquent\Builder;

class RecoveryCode extends Model
{
Expand All @@ -18,4 +19,15 @@ class RecoveryCode extends Model
'user_id',
'recovery',
];

/**
* Scope a query to only include unused code.
*
* @param Builder $query
* @return Builder
*/
public function scopeUnused($query)
{
return $query->where('used', 0);
}
}
22 changes: 22 additions & 0 deletions app/Models/User/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,26 @@ public function preferredLocale()
{
return $this->locale;
}

/**
* Try using a recovery code.
*
* @param string $recovery
* @return bool
*/
public function recoveryChallenge(string $recovery): bool
{
$recoveryCodes = $this->recoveryCodes()->unused()->get();

foreach ($recoveryCodes as $recoveryCode) {
if ($recoveryCode->recovery === $recovery) {
$recoveryCode->used = true;
$recoveryCode->save();

return true;
}
}

return false;
}
}
4 changes: 2 additions & 2 deletions resources/js/components/settings/MfaActivate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@
<form-input
:id="'one_time_password2'"
v-model="one_time_password"
:title="$t('auth.2fa_one_time_password')"
:input-type="'number'"
:title="$t('auth.2fa_one_time_or_recuperation')"
:input-type="'text'"
:width="100"
:required="true"
/>
Expand Down
1 change: 1 addition & 0 deletions resources/lang/en/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
'2fa_wrong_validation' => 'The two factor authentication has failed.',
'2fa_one_time_password' => 'Two factor authentication code',
'2fa_recuperation_code' => 'Enter a two factor recovery code',
'2fa_one_time_or_recuperation' => 'Enter a two factor authentication code or a recovery code',
'2fa_otp_help' => 'Open up your two factor authentication mobile app and copy the code',

'login_to_account' => 'Login to your account',
Expand Down

0 comments on commit 1f4c4c4

Please sign in to comment.