Skip to content
Open
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
7ef8d7e
feat: implement two-factor authentication functionality and UI enhanc…
pushpak1300 Aug 18, 2025
263cdef
refactor: extract two-factor authentication logic to composable for b…
pushpak1300 Aug 18, 2025
94b4248
refactor: move two-factor authentication logic from composable to pag…
pushpak1300 Aug 19, 2025
c284c85
refactor: simplify two-factor authentication logic with form componen…
pushpak1300 Aug 19, 2025
62c4b52
feat: add PinInput component and update TwoFactor UI with reka-ui 2.4…
pushpak1300 Aug 19, 2025
0fb05f8
refactor: replace reka-ui PinInput with custom implementation and opt…
pushpak1300 Aug 19, 2025
ae01a15
refactor: revert unneeded changes
pushpak1300 Aug 19, 2025
ee18d0f
Merge branch 'main' into feat/two-factor-auth
pushpak1300 Aug 19, 2025
ea4d231
refactor: simplify two-factor authentication
pushpak1300 Aug 19, 2025
b1b183d
feat: update two-factor authentication to delay login until confirmation
pushpak1300 Aug 19, 2025
7f78d7e
wip
pushpak1300 Aug 19, 2025
ebda2c6
wip
pushpak1300 Aug 19, 2025
cae3c55
refactor: centralize two-factor authentication logic, improve UI stat…
pushpak1300 Aug 19, 2025
14f1165
refactor: streamline TwoFactor components, simplify form handling, an…
pushpak1300 Aug 19, 2025
1a24366
refactor: centralize clipboard and two-factor authentication logic, e…
pushpak1300 Aug 19, 2025
8bc54c9
refactor: enhance TwoFactor UI components with reusable Card layout, …
pushpak1300 Aug 19, 2025
da99c25
fix: use `hasEnabledTwoFactorAuthentication` for improved logic clari…
pushpak1300 Aug 19, 2025
f93dc53
refactor: streamline TwoFactor UI logic, improve recovery mode toggle…
pushpak1300 Aug 19, 2025
f608bc4
refactor: simplify TwoFactor test structure, replace manual assertion…
pushpak1300 Aug 19, 2025
b6610fc
chore: add newline to end of TwoFactorAuthenticationTest file
pushpak1300 Aug 19, 2025
8ba762c
refactor: enhance TwoFactor UI with conditional enable button logic, …
pushpak1300 Aug 20, 2025
c4d5d06
refactor: enhance TwoFactor UI with conditional enable button logic, …
pushpak1300 Aug 20, 2025
bd1d90c
refactor: inline two-factor authentication composable logic into TwoF…
pushpak1300 Aug 20, 2025
0fe2452
refactor: remove unused `ref` and `focus` logic from Input component …
pushpak1300 Aug 20, 2025
a6d28f4
refactor: enhance two-factor authentication flow with middleware chec…
pushpak1300 Aug 20, 2025
fecc5d4
refactor: simplify TwoFactor component with async/await, refine recov…
pushpak1300 Aug 20, 2025
ef2931c
refactor: reset setup data on modal close, improve form handling, and…
pushpak1300 Aug 20, 2025
a162314
refactor: add reusable reset methods for reactive states, simplify Tw…
pushpak1300 Aug 20, 2025
bc8440a
fix: formatting issue
pushpak1300 Aug 20, 2025
d0a80eb
fix: conditionally apply password confirmation middleware
pushpak1300 Aug 20, 2025
e4b915f
chore: update lock file
pushpak1300 Aug 20, 2025
2e55156
fix: make component reactive
pushpak1300 Aug 20, 2025
9b81898
wip
pushpak1300 Aug 21, 2025
08b9471
refactor: split the component
pushpak1300 Aug 21, 2025
021db0d
refactor: use composable
pushpak1300 Aug 21, 2025
9317296
refactor: remove redundant type definition
pushpak1300 Aug 21, 2025
9782812
refactor: remove custom useClipboard composable
pushpak1300 Aug 21, 2025
c0ae075
refactor: simplify recovery codes visibility logic
pushpak1300 Aug 21, 2025
b1ddcb5
Merge branch 'main' into feat/two-factor-auth
pushpak1300 Aug 25, 2025
6ff5d5d
fix: merge issues
pushpak1300 Aug 25, 2025
b083e76
refactor: update two-factor authentication forms to use route helpers
pushpak1300 Aug 25, 2025
b2ef754
refactor: test
pushpak1300 Aug 25, 2025
31e6061
refactor: improve two-factor authentication tests and enhance readabi…
pushpak1300 Aug 25, 2025
cc8f893
refactor: update two-factor authentication tests for consistency and …
pushpak1300 Aug 25, 2025
7b4e4e4
wip
pushpak1300 Aug 25, 2025
e2e382d
Move conditional middleware from route to controller
pushpak1300 Aug 25, 2025
f2888c1
Fix tests to be consistent based on config
pushpak1300 Aug 25, 2025
f8ca148
Remove comments
pushpak1300 Aug 25, 2025
b85a0d7
refactor: simplify toggleRecoveryCodesVisibility function signature
pushpak1300 Aug 25, 2025
7309dde
remove duplicate type defination
pushpak1300 Aug 25, 2025
52b681e
fix type definition
pushpak1300 Aug 25, 2025
b314f93
More Fixes
pushpak1300 Aug 25, 2025
bfcb743
fix types
pushpak1300 Aug 25, 2025
afa80b5
fix wayfinder routes
pushpak1300 Aug 25, 2025
e4bff4d
refactor ui to use defineModal and remove unnecessary things
pushpak1300 Aug 26, 2025
c849ded
remove unneeded async attribute
pushpak1300 Aug 26, 2025
d073337
remove redundant attribute action
pushpak1300 Aug 26, 2025
00db807
fix: finish state
pushpak1300 Aug 26, 2025
f991021
Merge branch 'main' into feat/two-factor-auth
pushpak1300 Aug 26, 2025
5fdfd1d
fix: merge conflicts
pushpak1300 Aug 26, 2025
3f3d9c7
state cleanup
pushpak1300 Aug 26, 2025
ae76fd8
Merge branch 'main' into feat/two-factor-auth
pushpak1300 Aug 28, 2025
27da01f
fix typo
pushpak1300 Aug 28, 2025
0b3afa0
use arrow functions
pushpak1300 Aug 28, 2025
aeffd88
Move trait to form-request
pushpak1300 Aug 28, 2025
2b20426
Formatting
pushpak1300 Aug 28, 2025
17f4ec7
various formatting tweaks
taylorotwell Aug 28, 2025
7c3811c
Update TwoFactorSetupModal.vue
taylorotwell Aug 28, 2025
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 @@ -7,6 +7,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,33 @@
<?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 Inertia\Inertia;
use Inertia\Response;
use Laravel\Fortify\Features;

class TwoFactorAuthenticationController extends Controller
{
use ConfirmsTwoFactorAuthentication;

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

$this->validateTwoFactorAuthenticationState($request);

return Inertia::render('settings/TwoFactor', [
'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 @@ -33,15 +34,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 @@ -50,6 +54,8 @@ public function authenticate(): void
}

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

return $user;
}

/**
Expand All @@ -76,7 +82,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/TwoFactorChallenge');
});

Fortify::confirmPasswordView(function () {
return Inertia::render('auth/ConfirmPassword');
});

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.28",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1",
"tightenco/ziggy": "^2.4"
Expand Down
Loading