diff --git a/app/Http/Middleware/CheckForcePasswordReset.php b/app/Http/Middleware/CheckForcePasswordReset.php
index 78b1f896c..c857cb836 100644
--- a/app/Http/Middleware/CheckForcePasswordReset.php
+++ b/app/Http/Middleware/CheckForcePasswordReset.php
@@ -25,7 +25,7 @@ public function handle(Request $request, Closure $next): Response
}
$force_password_reset = auth()->user()->force_password_reset;
if ($force_password_reset) {
- if ($request->routeIs('auth.force-password-reset') || $request->path() === 'force-password-reset' || $request->path() === 'livewire/update' || $request->path() === 'logout') {
+ if ($request->routeIs('auth.force-password-reset') || $request->path() === 'force-password-reset' || $request->path() === 'two-factor-challenge' || $request->path() === 'livewire/update' || $request->path() === 'logout') {
return $next($request);
}
diff --git a/bootstrap/helpers/subscriptions.php b/bootstrap/helpers/subscriptions.php
index 4b84fb7f6..709af854a 100644
--- a/bootstrap/helpers/subscriptions.php
+++ b/bootstrap/helpers/subscriptions.php
@@ -77,6 +77,7 @@ function allowedPathsForUnsubscribedAccounts()
'login',
'logout',
'force-password-reset',
+ 'two-factor-challenge',
'livewire/update',
'admin',
];
@@ -95,6 +96,7 @@ function allowedPathsForInvalidAccounts()
'logout',
'verify',
'force-password-reset',
+ 'two-factor-challenge',
'livewire/update',
];
}
diff --git a/resources/views/errors/419.blade.php b/resources/views/errors/419.blade.php
index e7cd3fc45..8569f4e22 100644
--- a/resources/views/errors/419.blade.php
+++ b/resources/views/errors/419.blade.php
@@ -3,15 +3,11 @@
419
This page is definitely old, not like you!
-
Sorry, we couldn't find the page you're looking
- for.
+
Your session has expired. Please log in again to continue.
-
- Go back
-
-
- Dashboard
+
+ Back to Login
Contact
support
diff --git a/tests/Feature/TwoFactorChallengeAccessTest.php b/tests/Feature/TwoFactorChallengeAccessTest.php
new file mode 100644
index 000000000..2bd58d197
--- /dev/null
+++ b/tests/Feature/TwoFactorChallengeAccessTest.php
@@ -0,0 +1,65 @@
+user = User::factory()->create();
+ $this->team = Team::factory()->personal()->create();
+ $this->team->members()->attach($this->user->id, ['role' => 'owner']);
+ session(['currentTeam' => $this->team]);
+});
+
+it('allows unauthenticated access to two-factor-challenge page', function () {
+ $response = $this->get('/two-factor-challenge');
+
+ // Fortify returns a redirect to /login if there's no login.id in session,
+ // but the important thing is it does NOT return a 419 or 500
+ expect($response->status())->toBeIn([200, 302]);
+});
+
+it('includes two-factor-challenge in allowed paths for unsubscribed accounts', function () {
+ $paths = allowedPathsForUnsubscribedAccounts();
+
+ expect($paths)->toContain('two-factor-challenge');
+});
+
+it('includes two-factor-challenge in allowed paths for invalid accounts', function () {
+ $paths = allowedPathsForInvalidAccounts();
+
+ expect($paths)->toContain('two-factor-challenge');
+});
+
+it('includes two-factor-challenge in allowed paths for boarding accounts', function () {
+ $paths = allowedPathsForBoardingAccounts();
+
+ expect($paths)->toContain('two-factor-challenge');
+});
+
+it('does not redirect authenticated user with force_password_reset from two-factor-challenge', function () {
+ $this->user->update(['force_password_reset' => true]);
+
+ $response = $this->actingAs($this->user)->get('/two-factor-challenge');
+
+ // Should NOT redirect to force-password-reset page
+ if ($response->isRedirect()) {
+ expect($response->headers->get('Location'))->not->toContain('force-password-reset');
+ }
+});
+
+it('renders 419 error page with login link instead of previous url', function () {
+ $response = $this->get('/two-factor-challenge', [
+ 'X-CSRF-TOKEN' => 'invalid-token',
+ ]);
+
+ // The 419 page should exist and contain a link to /login
+ $view = view('errors.419')->render();
+
+ expect($view)->toContain('/login');
+ expect($view)->toContain('Back to Login');
+ expect($view)->toContain('This page is definitely old, not like you!');
+ expect($view)->not->toContain('url()->previous()');
+});