Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
/storage/*.key
/storage/pail
/vendor
.DS_Store
.env
.env.backup
.env.production
Expand Down
14 changes: 13 additions & 1 deletion app/Http/Controllers/Auth/AuthenticatedSessionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use Inertia\Response;
use Laravel\Fortify\Features;

class AuthenticatedSessionController extends Controller
{
Expand All @@ -29,7 +30,18 @@ public function create(Request $request): Response
*/
public function store(LoginRequest $request): RedirectResponse
{
$request->authenticate();
$user = $request->validateCredentials();

if (Features::enabled(Features::twoFactorAuthentication()) && $user->hasEnabledTwoFactorAuthentication()) {
$request->session()->put([
'login.id' => $user->getKey(),
'login.remember' => $request->boolean('remember'),
]);

return redirect()->route('two-factor.login');
}

Auth::login($user, $request->boolean('remember'));

$request->session()->regenerate();

Expand Down
41 changes: 0 additions & 41 deletions app/Http/Controllers/Auth/ConfirmablePasswordController.php

This file was deleted.

71 changes: 71 additions & 0 deletions app/Http/Controllers/Concerns/ConfirmsTwoFactorAuthentication.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

namespace App\Http\Controllers\Concerns;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
use Laravel\Fortify\Features;

trait ConfirmsTwoFactorAuthentication
{
/**
* Validate the two-factor authentication state for the request.
*/
protected function validateTwoFactorAuthenticationState(Request $request): void
{
if (! Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm')) {
return;
}

$currentTime = time();

// Notate totally disabled state in session...
if ($this->twoFactorAuthenticationDisabled($request)) {
$request->session()->put('two_factor_empty_at', $currentTime);
}

// If was previously totally disabled this session but is now confirming, notate time...
if ($this->hasJustBegunConfirmingTwoFactorAuthentication($request)) {
$request->session()->put('two_factor_confirming_at', $currentTime);
}

// If the profile is reloaded and is not confirmed but was previously in confirming state, disable...
if ($this->neverFinishedConfirmingTwoFactorAuthentication($request, $currentTime)) {
app(DisableTwoFactorAuthentication::class)(Auth::user());

$request->session()->put('two_factor_empty_at', $currentTime);
$request->session()->remove('two_factor_confirming_at');
}
}

/**
* Determine if two-factor authentication is totally disabled.
*/
protected function twoFactorAuthenticationDisabled(Request $request): bool
{
return is_null($request->user()->two_factor_secret) &&
is_null($request->user()->two_factor_confirmed_at);
}

/**
* Determine if two-factor authentication is just now being confirmed within the last request cycle.
*/
protected function hasJustBegunConfirmingTwoFactorAuthentication(Request $request): bool
{
return ! is_null($request->user()->two_factor_secret) &&
is_null($request->user()->two_factor_confirmed_at) &&
$request->session()->has('two_factor_empty_at') &&
is_null($request->session()->get('two_factor_confirming_at'));
}

/**
* Determine if two-factor authentication was never totally confirmed once confirmation started.
*/
protected function neverFinishedConfirmingTwoFactorAuthentication(Request $request, int $currentTime): bool
{
return ! array_key_exists('code', $request->session()->getOldInput()) &&
is_null($request->user()->two_factor_confirmed_at) &&
$request->session()->get('two_factor_confirming_at', 0) != $currentTime;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace App\Http\Controllers\Settings;

use App\Http\Controllers\Concerns\ConfirmsTwoFactorAuthentication;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response as HttpResponse;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
use Inertia\Inertia;
use Inertia\Response;
use Laravel\Fortify\Features;

class TwoFactorAuthenticationController extends Controller implements HasMiddleware
{
use ConfirmsTwoFactorAuthentication;

/**
* Get the middleware that should be assigned to the controller.
*/
public static function middleware(): array
{
return Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword')
? [new Middleware('password.confirm', only: ['show'])]
: [];
}

/**
* Show the user's two-factor authentication settings page.
*/
public function show(Request $request): Response
{
abort_if(
! Features::enabled(Features::twoFactorAuthentication()),
HttpResponse::HTTP_FORBIDDEN,
'Two factor authentication is disabled.'
);

$this->validateTwoFactorAuthenticationState($request);

return Inertia::render('settings/two-factor', [
'requiresConfirmation' => Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm'),
'twoFactorEnabled' => $request->user()->hasEnabledTwoFactorAuthentication(),
]);
}
}
14 changes: 10 additions & 4 deletions app/Http/Requests/Auth/LoginRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Http\Requests\Auth;

use App\Models\User;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
Expand Down Expand Up @@ -32,15 +33,18 @@ public function rules(): array
}

/**
* Attempt to authenticate the request's credentials.
* Validate the request's credentials and return the user without logging them in.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function authenticate(): void
public function validateCredentials(): User
{
$this->ensureIsNotRateLimited();

if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
/** @var User $user */
$user = Auth::getProvider()->retrieveByCredentials($this->only('email', 'password'));

if (! $user || ! Auth::getProvider()->validateCredentials($user, $this->only('password'))) {
RateLimiter::hit($this->throttleKey());

throw ValidationException::withMessages([
Expand All @@ -49,6 +53,8 @@ public function authenticate(): void
}

RateLimiter::clear($this->throttleKey());

return $user;
}

/**
Expand All @@ -75,7 +81,7 @@ public function ensureIsNotRateLimited(): void
}

/**
* Get the rate limiting throttle key for the request.
* Get the rate-limiting throttle key for the request.
*/
public function throttleKey(): string
{
Expand Down
3 changes: 2 additions & 1 deletion app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;

class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
use HasFactory, Notifiable, TwoFactorAuthenticatable;

/**
* The attributes that are mass assignable.
Expand Down
39 changes: 39 additions & 0 deletions app/Providers/FortifyServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace App\Providers;

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Inertia\Inertia;
use Laravel\Fortify\Fortify;

class FortifyServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Fortify::twoFactorChallengeView(function () {
return Inertia::render('auth/two-factor-challenge');
});

Fortify::confirmPasswordView(function () {
return Inertia::render('auth/confirm-password');
});

RateLimiter::for('two-factor', function (Request $request) {
return Limit::perMinute(5)->by($request->session()->get('login.id'));
});
}
}
1 change: 1 addition & 0 deletions bootstrap/providers.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

return [
App\Providers\AppServiceProvider::class,
App\Providers\FortifyServiceProvider::class,
];
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"require": {
"php": "^8.2",
"inertiajs/inertia-laravel": "^2.0",
"laravel/fortify": "^1.29",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1",
"laravel/wayfinder": "^0.1.9"
Expand Down
Loading