diff --git a/app/Exceptions/Http/TwoFactorAuthRequiredException.php b/app/Exceptions/Http/TwoFactorAuthRequiredException.php new file mode 100644 index 000000000..fb9c09ebc --- /dev/null +++ b/app/Exceptions/Http/TwoFactorAuthRequiredException.php @@ -0,0 +1,18 @@ +user(); + + // Auth and profile endpoints should always be available + if (!$user || $request->routeIs('*auth.*')) { + return $next($request); + } + + $level = (int) config('panel.auth.2fa_required'); + + $has2fa = $user->hasEmailAuthentication() || filled($user->getAppAuthenticationSecret()); + if ($level === self::LEVEL_NONE || $has2fa) { + // If this setting is not configured, or the user is already using 2FA then we can just send them right through, nothing else needs to be checked. + return $next($request); + } + + if ($level === self::LEVEL_ADMIN && !$user->isAdmin()) { + // If the level is set as admin and the user is not an admin, pass them through as well. + return $next($request); + } + + // For API calls return an exception which gets rendered nicely in the API response... + if ($request->isJson() || Str::startsWith($request->path(), '/api')) { + throw new TwoFactorAuthRequiredException(); + } + + // ... otherwise display banner and redirect to profile + AlertBanner::make('2fa_must_be_enabled') + ->body(trans('auth.2fa_must_be_enabled')) + ->warning() + ->send(); + + return redirect(EditProfile::getUrl(['tab' => '2fa::data::tab'], panel: 'app')); + } +} diff --git a/app/Providers/Filament/PanelProvider.php b/app/Providers/Filament/PanelProvider.php index cdb0dde42..92ec261d7 100644 --- a/app/Providers/Filament/PanelProvider.php +++ b/app/Providers/Filament/PanelProvider.php @@ -6,7 +6,7 @@ use App\Enums\CustomizationKey; use App\Filament\Pages\Auth\EditProfile; use App\Filament\Pages\Auth\Login; use App\Http\Middleware\LanguageMiddleware; -use App\Models\User; +use App\Http\Middleware\RequireTwoFactorAuthentication; use Filament\Actions\Action; use Filament\Auth\MultiFactor\App\AppAuthentication; use Filament\Auth\MultiFactor\Email\EmailAuthentication; @@ -56,29 +56,6 @@ abstract class PanelProvider extends BasePanelProvider AppAuthentication::make()->recoverable(), EmailAuthentication::make(), ]) - ->requiresMultiFactorAuthentication(function () { - $user = user(); // TODO: get user, see https://github.com/filamentphp/filament/discussions/17695 - if ($user) { - $level = (int) config('panel.auth.2fa_required'); - - // Not required - if ($level === 0) { - return false; - } - - // Only admins - if ($level === 1) { - return $user->isAdmin(); - } - - // All users - if ($level === 2) { - return true; - } - } - - return false; - }) ->middleware([ EncryptCookies::class, AddQueuedCookiesToResponse::class, @@ -93,6 +70,7 @@ abstract class PanelProvider extends BasePanelProvider ]) ->authMiddleware([ Authenticate::class, + RequireTwoFactorAuthentication::class, ]); } }