Add two factor (#632)

This commit is contained in:
Saeed Vaziry
2025-06-27 01:07:33 +02:00
committed by GitHub
parent 73c836bfe7
commit 194143d7ef
12 changed files with 388 additions and 49 deletions

View File

@ -49,6 +49,18 @@ public function store(Request $request): RedirectResponse
RateLimiter::clear($this->throttleKey());
Session::regenerate();
if (user()->two_factor_secret) {
$request->session()->invalidate();
$request->session()->regenerateToken();
$request->session()->put([
'login.id' => user()->id,
'login.remember' => $request->boolean('remember'),
]);
return redirect()->route('two-factor.login');
}
return redirect()->intended(route('servers', absolute: false));
}

View File

@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Models\User;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Routing\Controller;
use Inertia\Inertia;
use Laravel\Fortify\Contracts\FailedTwoFactorLoginResponse;
use Laravel\Fortify\Contracts\TwoFactorLoginResponse;
use Laravel\Fortify\Events\RecoveryCodeReplaced;
use Laravel\Fortify\Events\TwoFactorAuthenticationFailed;
use Laravel\Fortify\Events\ValidTwoFactorAuthenticationCodeProvided;
use Laravel\Fortify\Http\Requests\TwoFactorLoginRequest;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Post;
use Symfony\Component\HttpFoundation\Response;
class TwoFactorAuthenticatedSessionController extends Controller
{
protected StatefulGuard $guard;
public function __construct(StatefulGuard $guard)
{
$this->guard = $guard;
}
#[Get('two-factor', name: 'two-factor.login')]
public function create(TwoFactorLoginRequest $request): \Inertia\Response
{
if (! $request->hasChallengedUser()) {
throw new HttpResponseException(redirect()->route('login'));
}
return Inertia::render('auth/two-factor');
}
#[Post('two-factor', name: 'two-factor.store')]
public function store(TwoFactorLoginRequest $request): TwoFactorLoginResponse|Response
{
/** @var User $user */
$user = $request->challengedUser();
if ($code = $request->validRecoveryCode()) {
$user->replaceRecoveryCode($code);
event(new RecoveryCodeReplaced($user, $code));
} elseif (! $request->hasValidCode()) {
event(new TwoFactorAuthenticationFailed($user));
return app(FailedTwoFactorLoginResponse::class)->toResponse($request);
}
event(new ValidTwoFactorAuthenticationCodeProvided($user));
$this->guard->login($user, $request->remember());
$request->session()->regenerate();
return redirect()->intended(route('servers', absolute: false));
}
}

View File

@ -11,9 +11,12 @@
use Illuminate\Validation\Rules\Password;
use Inertia\Inertia;
use Inertia\Response;
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
use Laravel\Fortify\Actions\EnableTwoFactorAuthentication;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Patch;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
use Spatie\RouteAttributes\Attributes\Put;
@ -65,4 +68,30 @@ public function password(Request $request): RedirectResponse
return to_route('profile');
}
#[Post('/enable-two-factor', name: 'profile.enable-two-factor')]
public function enableTwoFactor(): RedirectResponse
{
$user = user();
app(EnableTwoFactorAuthentication::class)($user);
return back()
->with('success', 'Two factor authentication enabled.')
->with('data', [
'qr_code' => $user->twoFactorQrCodeSvg(),
'qr_code_url' => $user->twoFactorQrCodeUrl(),
'recovery_codes' => $user->recoveryCodes(),
]);
}
#[Post('/disable-two-factor', name: 'profile.disable-two-factor')]
public function disableTwoFactor(): RedirectResponse
{
$user = user();
app(DisableTwoFactorAuthentication::class)($user);
return back()->with('success', 'Two factor authentication disabled.');
}
}

View File

@ -2,8 +2,10 @@
namespace App\Http\Middleware;
use App\Http\Resources\ProjectResource;
use App\Http\Resources\ServerResource;
use App\Http\Resources\SiteResource;
use App\Http\Resources\UserResource;
use App\Models\Server;
use App\Models\Site;
use App\Models\User;
@ -84,11 +86,11 @@ public function share(Request $request): array
'version' => config('app.version'),
'demo' => config('app.demo'),
'quote' => ['message' => trim($message), 'author' => trim($author)],
'auth' => [
'user' => $user,
'projects' => $user?->allProjects()->get(),
'currentProject' => $user?->currentProject,
],
'auth' => $user ? [
'user' => UserResource::make($user->load('projects')),
'projects' => ProjectResource::collection($user->allProjects()->get()),
'currentProject' => ProjectResource::make($user->currentProject),
] : null,
'public_key_text' => __('servers.create.public_key_text', ['public_key' => get_public_key_content()]),
'project_servers' => $servers,
'configs' => [

View File

@ -19,6 +19,7 @@ public function toArray(Request $request): array
'name' => $this->name,
'email' => $this->email,
'role' => $this->role,
'two_factor_enabled' => (bool) $this->two_factor_secret,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'projects' => ProjectResource::collection($this->whenLoaded('projects')),

View File

@ -34,6 +34,9 @@ public function boot(): void
$this->app->bind('plugins', fn (): Plugins => new Plugins);
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
Fortify::twoFactorChallengeView(function () {
return view('app');
});
if (config('app.force_https')) {
URL::forceHttps();