Skip to content

Commit 0b82eee

Browse files
[6.x] Ability to disable two-factor authentication (#14263)
Co-authored-by: Jason Varga <jason@pixelfear.com>
1 parent ac2b8a7 commit 0b82eee

15 files changed

Lines changed: 311 additions & 30 deletions

File tree

config/users.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,18 @@
181181

182182
'elevated_session_duration' => 15,
183183

184+
/*
185+
|--------------------------------------------------------------------------
186+
| Two-Factor Authentication
187+
|--------------------------------------------------------------------------
188+
|
189+
| Here you may disable two-factor authentication entirely. This can be
190+
| useful on local or staging environments, or when using OAuth.
191+
|
192+
*/
193+
194+
'two_factor_enabled' => env('STATAMIC_TWO_FACTOR_ENABLED', true),
195+
184196
/*
185197
|--------------------------------------------------------------------------
186198
| Enforce Two-Factor Authentication

routes/cp.php

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use Illuminate\Support\Facades\Route;
4+
use Statamic\Facades\TwoFactor;
45
use Statamic\Facades\Utility;
56
use Statamic\Http\Controllers\CP\Addons\AddonsController;
67
use Statamic\Http\Controllers\CP\Addons\AddonSettingsController;
@@ -128,12 +129,14 @@
128129
Route::get('password/reset/{token}', [ResetPasswordController::class, 'showResetForm'])->name('password.reset');
129130
Route::post('password/reset', [ResetPasswordController::class, 'reset'])->name('password.reset.action');
130131

131-
Route::get('two-factor-challenge', [TwoFactorChallengeController::class, 'index'])->name('two-factor-challenge');
132-
Route::post('two-factor-challenge', [TwoFactorChallengeController::class, 'store']);
132+
if (TwoFactor::enabled()) {
133+
Route::get('two-factor-challenge', [TwoFactorChallengeController::class, 'index'])->name('two-factor-challenge');
134+
Route::post('two-factor-challenge', [TwoFactorChallengeController::class, 'store']);
133135

134-
Route::get('two-factor-setup', TwoFactorSetupController::class)
135-
->withoutMiddleware(RedirectIfTwoFactorSetupIncomplete::class)
136-
->name('two-factor-setup');
136+
Route::get('two-factor-setup', TwoFactorSetupController::class)
137+
->withoutMiddleware(RedirectIfTwoFactorSetupIncomplete::class)
138+
->name('two-factor-setup');
139+
}
137140
}
138141

139142
Route::get('logout', [LoginController::class, 'logout'])->name('logout');
@@ -345,14 +348,16 @@
345348
Route::post('users/actions/list', [UserActionController::class, 'bulkActions'])->name('users.actions.bulk');
346349
Route::resource('users', UsersController::class)->except('destroy');
347350
Route::patch('users/{user}/password', [PasswordController::class, 'update'])->name('users.password.update');
348-
Route::withoutMiddleware(RedirectIfTwoFactorSetupIncomplete::class)->middleware(RequireElevatedSession::class)->group(function () {
349-
Route::get('two-factor/enable', [TwoFactorAuthenticationController::class, 'enable'])->name('users.two-factor.enable');
350-
Route::delete('two-factor', [TwoFactorAuthenticationController::class, 'disable'])->name('users.two-factor.disable');
351-
Route::post('two-factor/confirm', [TwoFactorAuthenticationController::class, 'confirm'])->name('users.two-factor.confirm');
352-
Route::get('two-factor/recovery-codes', [TwoFactorRecoveryCodesController::class, 'show'])->name('users.two-factor.recovery-codes.show');
353-
Route::post('two-factor/recovery-codes', [TwoFactorRecoveryCodesController::class, 'store'])->name('users.two-factor.recovery-codes.generate');
354-
Route::get('two-factor/recovery-codes/download', [TwoFactorRecoveryCodesController::class, 'download'])->name('users.two-factor.recovery-codes.download');
355-
});
351+
if (TwoFactor::enabled()) {
352+
Route::withoutMiddleware(RedirectIfTwoFactorSetupIncomplete::class)->middleware(RequireElevatedSession::class)->group(function () {
353+
Route::get('two-factor/enable', [TwoFactorAuthenticationController::class, 'enable'])->name('users.two-factor.enable');
354+
Route::delete('two-factor', [TwoFactorAuthenticationController::class, 'disable'])->name('users.two-factor.disable');
355+
Route::post('two-factor/confirm', [TwoFactorAuthenticationController::class, 'confirm'])->name('users.two-factor.confirm');
356+
Route::get('two-factor/recovery-codes', [TwoFactorRecoveryCodesController::class, 'show'])->name('users.two-factor.recovery-codes.show');
357+
Route::post('two-factor/recovery-codes', [TwoFactorRecoveryCodesController::class, 'store'])->name('users.two-factor.recovery-codes.generate');
358+
Route::get('two-factor/recovery-codes/download', [TwoFactorRecoveryCodesController::class, 'download'])->name('users.two-factor.recovery-codes.download');
359+
});
360+
}
356361
Route::get('account', AccountController::class)->name('account');
357362
Route::resource('user-groups', UserGroupsController::class);
358363
Route::resource('roles', RolesController::class);

routes/web.php

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Illuminate\Support\Facades\Route;
66
use Statamic\Auth\Protect\Protectors\Password\Controller as PasswordProtectController;
77
use Statamic\Facades\OAuth;
8+
use Statamic\Facades\TwoFactor;
89
use Statamic\Http\Controllers\ActivateAccountController;
910
use Statamic\Http\Controllers\ForgotPasswordController;
1011
use Statamic\Http\Controllers\FormController;
@@ -50,17 +51,19 @@
5051
Route::get('password/reset/{token}', [ResetPasswordController::class, 'showResetForm'])->name('password.reset');
5152
Route::post('password/reset', [ResetPasswordController::class, 'reset'])->name('password.reset.action');
5253

53-
Route::get('two-factor-setup', TwoFactorSetupController::class)->name('two-factor-setup');
54-
Route::get('two-factor-challenge', [TwoFactorChallengeController::class, 'index'])->name('two-factor-challenge');
55-
Route::post('two-factor-challenge', [TwoFactorChallengeController::class, 'store']);
56-
57-
Route::withoutMiddleware(RedirectIfTwoFactorSetupIncomplete::class)->group(function () {
58-
Route::get('two-factor/enable', [TwoFactorAuthenticationController::class, 'enable'])->name('users.two-factor.enable');
59-
Route::post('two-factor/confirm', [TwoFactorAuthenticationController::class, 'confirm'])->name('users.two-factor.confirm');
60-
Route::get('two-factor/recovery-codes', [TwoFactorRecoveryCodesController::class, 'show'])->name('users.two-factor.recovery-codes.show');
61-
Route::post('two-factor/recovery-codes', [TwoFactorRecoveryCodesController::class, 'store'])->name('users.two-factor.recovery-codes.generate');
62-
Route::get('two-factor/recovery-codes/download', [TwoFactorRecoveryCodesController::class, 'download'])->name('users.two-factor.recovery-codes.download');
63-
});
54+
if (TwoFactor::enabled()) {
55+
Route::get('two-factor-setup', TwoFactorSetupController::class)->name('two-factor-setup');
56+
Route::get('two-factor-challenge', [TwoFactorChallengeController::class, 'index'])->name('two-factor-challenge');
57+
Route::post('two-factor-challenge', [TwoFactorChallengeController::class, 'store']);
58+
59+
Route::withoutMiddleware(RedirectIfTwoFactorSetupIncomplete::class)->group(function () {
60+
Route::get('two-factor/enable', [TwoFactorAuthenticationController::class, 'enable'])->name('users.two-factor.enable');
61+
Route::post('two-factor/confirm', [TwoFactorAuthenticationController::class, 'confirm'])->name('users.two-factor.confirm');
62+
Route::get('two-factor/recovery-codes', [TwoFactorRecoveryCodesController::class, 'show'])->name('users.two-factor.recovery-codes.show');
63+
Route::post('two-factor/recovery-codes', [TwoFactorRecoveryCodesController::class, 'store'])->name('users.two-factor.recovery-codes.generate');
64+
Route::get('two-factor/recovery-codes/download', [TwoFactorRecoveryCodesController::class, 'download'])->name('users.two-factor.recovery-codes.download');
65+
});
66+
}
6467
});
6568

6669
Route::group(['prefix' => 'auth', 'middleware' => [CPAuthGuard::class]], function () {

src/Auth/TwoFactor/TwoFactor.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Statamic\Auth\TwoFactor;
4+
5+
class TwoFactor
6+
{
7+
public function enabled(): bool
8+
{
9+
return config('statamic.users.two_factor_enabled', true);
10+
}
11+
}

src/Auth/User.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Illuminate\Notifications\Notifiable;
2020
use Illuminate\Support\Facades\Password;
2121
use Statamic\Auth\Passwords\PasswordReset;
22+
use Statamic\Auth\TwoFactor\RecoveryCode;
2223
use Statamic\Contracts\Auth\Role as RoleContract;
2324
use Statamic\Contracts\Auth\TwoFactor\TwoFactorAuthenticationProvider;
2425
use Statamic\Contracts\Auth\User as UserContract;
@@ -40,6 +41,7 @@
4041
use Statamic\Events\UserSaved;
4142
use Statamic\Events\UserSaving;
4243
use Statamic\Facades;
44+
use Statamic\Facades\TwoFactor;
4345
use Statamic\GraphQL\ResolvesValues;
4446
use Statamic\Notifications\ActivateAccount as ActivateAccountNotification;
4547
use Statamic\Notifications\PasswordReset as PasswordResetNotification;
@@ -365,6 +367,10 @@ public function preferredColorMode()
365367

366368
public function isTwoFactorAuthenticationRequired(): bool
367369
{
370+
if (! TwoFactor::enabled()) {
371+
return false;
372+
}
373+
368374
$enforcedRoles = config('statamic.users.two_factor_enforced_roles', []);
369375

370376
if (in_array('*', $enforcedRoles)) {
@@ -410,7 +416,7 @@ public function replaceTwoFactorRecoveryCode(string $code): void
410416
{
411417
$this->set('two_factor_recovery_codes', encrypt(str_replace(
412418
$code,
413-
TwoFactor\RecoveryCode::generate(),
419+
RecoveryCode::generate(),
414420
decrypt($this->two_factor_recovery_codes)
415421
)))->save();
416422

src/Facades/TwoFactor.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Statamic\Facades;
4+
5+
use Illuminate\Support\Facades\Facade;
6+
7+
/**
8+
* @method static bool enabled()
9+
*
10+
* @see \Statamic\Auth\TwoFactor\TwoFactor
11+
*/
12+
class TwoFactor extends Facade
13+
{
14+
protected static function getFacadeAccessor()
15+
{
16+
return \Statamic\Auth\TwoFactor\TwoFactor::class;
17+
}
18+
}

src/Http/Controllers/CP/Auth/LoginController.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Illuminate\Validation\ValidationException;
88
use Inertia\Inertia;
99
use Statamic\Facades\OAuth;
10+
use Statamic\Facades\TwoFactor;
1011
use Statamic\Facades\URL;
1112
use Statamic\Facades\User;
1213
use Statamic\Http\Controllers\Concerns\HandlesLogins;
@@ -75,7 +76,7 @@ public function login(Request $request)
7576

7677
$user = User::fromUser($this->validateCredentials($request));
7778

78-
if ($user->hasEnabledTwoFactorAuthentication()) {
79+
if (TwoFactor::enabled() && $user->hasEnabledTwoFactorAuthentication()) {
7980
return $this->twoFactorChallengeResponse($request, $user);
8081
}
8182

src/Http/Controllers/CP/Users/UsersController.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Statamic\Facades\CP\Toast;
1212
use Statamic\Facades\Scope;
1313
use Statamic\Facades\Search;
14+
use Statamic\Facades\TwoFactor;
1415
use Statamic\Facades\User;
1516
use Statamic\Facades\UserGroup;
1617
use Statamic\Http\Controllers\CP\CpController;
@@ -277,7 +278,7 @@ public function edit(Request $request, $user)
277278
'canEditPassword' => User::fromUser($request->user())->can('editPassword', $user),
278279
'requiresCurrentPassword' => $isCurrentUser = $request->user()->id === $user->id(),
279280
'itemActions' => Action::for($user, ['view' => 'form']),
280-
'twoFactor' => $isCurrentUser ? [
281+
'twoFactor' => $isCurrentUser && TwoFactor::enabled() ? [
281282
'isEnforced' => $user->isTwoFactorAuthenticationRequired(),
282283
'wasSetup' => $user->hasEnabledTwoFactorAuthentication(),
283284
'routes' => [

src/Http/Controllers/User/LoginController.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Http\Exceptions\HttpResponseException;
77
use Illuminate\Http\Request;
88
use Illuminate\Support\Facades\Auth;
9+
use Statamic\Facades\TwoFactor;
910
use Statamic\Facades\URL;
1011
use Statamic\Facades\User;
1112
use Statamic\Http\Controllers\Concerns\HandlesLogins;
@@ -22,7 +23,7 @@ public function login(UserLoginRequest $request)
2223

2324
$user = User::fromUser($this->validateCredentials($request));
2425

25-
if ($user->hasEnabledTwoFactorAuthentication()) {
26+
if (TwoFactor::enabled() && $user->hasEnabledTwoFactorAuthentication()) {
2627
return $this->twoFactorChallengeResponse($request, $user);
2728
}
2829

src/Http/Middleware/RedirectIfTwoFactorSetupIncomplete.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44

55
use Closure;
66
use Illuminate\Http\Request;
7+
use Statamic\Facades\TwoFactor;
78
use Statamic\Facades\User;
89

910
class RedirectIfTwoFactorSetupIncomplete
1011
{
1112
public function handle(Request $request, Closure $next)
1213
{
1314
if (
14-
($user = User::fromUser($request->user()))
15+
TwoFactor::enabled()
16+
&& ($user = User::fromUser($request->user()))
1517
&& $user->isTwoFactorAuthenticationRequired()
1618
&& ! $user->hasEnabledTwoFactorAuthentication()
1719
) {

0 commit comments

Comments
 (0)