From 905edeb4a249cf71a9fccd7815f14fbadc20c884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Tue, 21 Apr 2026 10:53:05 +0100 Subject: [PATCH] fix: route new PWA guests to signup (#313) ## Summary - redirect guest access to protected routes based on returning-user cookie - send new PWA guests to registration and returning guests to login - persist returning-user cookie after register, login, and 2FA login - keep explicit login links working via `force=1` ## Testing - vendor/bin/pint --dirty --format agent - php artisan test --compact tests/Feature/Auth/AuthenticationTest.php tests/Feature/DashboardTest.php tests/Feature/Auth/RegistrationTest.php --- app/Http/Responses/LoginResponse.php | 5 +- app/Http/Responses/RegisterResponse.php | 5 ++ app/Http/Responses/TwoFactorLoginResponse.php | 5 +- app/Services/AuthEntryPointService.php | 58 +++++++++++++++++++ bootstrap/app.php | 3 + resources/js/components/partials/header.tsx | 8 ++- resources/js/pages/auth/register.tsx | 7 ++- tests/Feature/AccountControllerTest.php | 10 ++-- tests/Feature/Auth/AuthenticationTest.php | 21 ++++++- .../Feature/Auth/PasswordConfirmationTest.php | 6 +- tests/Feature/Auth/RegistrationTest.php | 8 ++- tests/Feature/CashflowPageTest.php | 8 ++- tests/Feature/DashboardTest.php | 40 ++++++++++++- .../OpenBankingFeatureFlagTest.php | 8 ++- tests/Feature/Settings/CategoryTest.php | 8 ++- .../Feature/Settings/ChartColorSchemeTest.php | 6 +- .../NetWorthChartLoanPreferenceTest.php | 6 +- .../NetWorthChartRealEstatePreferenceTest.php | 6 +- tests/Feature/Settings/TimezoneTest.php | 6 +- tests/Feature/SubscriptionTest.php | 11 ++-- tests/Feature/TransactionTest.php | 10 +++- 21 files changed, 212 insertions(+), 33 deletions(-) create mode 100644 app/Services/AuthEntryPointService.php diff --git a/app/Http/Responses/LoginResponse.php b/app/Http/Responses/LoginResponse.php index 87982eca..ded8f861 100644 --- a/app/Http/Responses/LoginResponse.php +++ b/app/Http/Responses/LoginResponse.php @@ -2,6 +2,7 @@ namespace App\Http\Responses; +use App\Services\AuthEntryPointService; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract; @@ -9,13 +10,15 @@ use Laravel\Fortify\Fortify; class LoginResponse implements LoginResponseContract { + public function __construct(private readonly AuthEntryPointService $authEntryPointService) {} + /** * Create an HTTP response that represents the object. */ public function toResponse($request): JsonResponse|RedirectResponse { - // Flash a session variable to indicate the user just logged in session()->flash('show_encryption_prompt', true); + $this->authEntryPointService->queueReturningUserCookie(); return $request->wantsJson() ? response()->json(['two_factor' => false]) diff --git a/app/Http/Responses/RegisterResponse.php b/app/Http/Responses/RegisterResponse.php index 7314fb55..f0533182 100644 --- a/app/Http/Responses/RegisterResponse.php +++ b/app/Http/Responses/RegisterResponse.php @@ -2,14 +2,19 @@ namespace App\Http\Responses; +use App\Services\AuthEntryPointService; use Illuminate\Http\JsonResponse; use Laravel\Fortify\Contracts\RegisterResponse as RegisterResponseContract; use Symfony\Component\HttpFoundation\Response; class RegisterResponse implements RegisterResponseContract { + public function __construct(private readonly AuthEntryPointService $authEntryPointService) {} + public function toResponse($request): Response { + $this->authEntryPointService->queueReturningUserCookie(); + if ($request->wantsJson()) { return new JsonResponse('', 201); } diff --git a/app/Http/Responses/TwoFactorLoginResponse.php b/app/Http/Responses/TwoFactorLoginResponse.php index 1d62c4c4..6cf31aee 100644 --- a/app/Http/Responses/TwoFactorLoginResponse.php +++ b/app/Http/Responses/TwoFactorLoginResponse.php @@ -2,6 +2,7 @@ namespace App\Http\Responses; +use App\Services\AuthEntryPointService; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Laravel\Fortify\Contracts\TwoFactorLoginResponse as TwoFactorLoginResponseContract; @@ -9,13 +10,15 @@ use Laravel\Fortify\Fortify; class TwoFactorLoginResponse implements TwoFactorLoginResponseContract { + public function __construct(private readonly AuthEntryPointService $authEntryPointService) {} + /** * Create an HTTP response that represents the object. */ public function toResponse($request): JsonResponse|RedirectResponse { - // Flash a session variable to indicate the user just logged in session()->flash('show_encryption_prompt', true); + $this->authEntryPointService->queueReturningUserCookie(); return $request->wantsJson() ? new JsonResponse('', 204) diff --git a/app/Services/AuthEntryPointService.php b/app/Services/AuthEntryPointService.php new file mode 100644 index 00000000..4265058f --- /dev/null +++ b/app/Services/AuthEntryPointService.php @@ -0,0 +1,58 @@ +hasAuthenticatedBefore($request) + || ! Features::enabled(Features::registration()) + || $this->landingAuthOverrideService->authButtonsHidden($request) + ) { + return route('login'); + } + + return route('register'); + } + + public function queueReturningUserCookie(): void + { + Cookie::queue($this->makeReturningUserCookie()); + } + + private function hasAuthenticatedBefore(Request $request): bool + { + return filter_var($request->cookie(self::COOKIE_NAME), FILTER_VALIDATE_BOOL); + } + + private function makeReturningUserCookie(): HttpFoundationCookie + { + return Cookie::make( + self::COOKIE_NAME, + '1', + self::COOKIE_MINUTES, + '/', + config('session.domain'), + config('session.secure'), + true, + false, + config('session.same_site', 'lax'), + ); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 41a83c47..58d435d1 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -6,6 +6,7 @@ use App\Http\Middleware\EnsureUserIsSubscribed; use App\Http\Middleware\HandleAppearance; use App\Http\Middleware\HandleInertiaRequests; use App\Http\Middleware\SetLocale; +use App\Services\AuthEntryPointService; use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; @@ -21,6 +22,8 @@ return Application::configure(basePath: dirname(__DIR__)) health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { + $middleware->redirectGuestsTo(fn (Request $request) => app(AuthEntryPointService::class)->guestRedirectRoute($request)); + $middleware->encryptCookies(except: ['appearance', 'sidebar_state', 'chart-color-scheme']); $middleware->trustProxies( diff --git a/resources/js/components/partials/header.tsx b/resources/js/components/partials/header.tsx index cd8e013a..3bd7f3bf 100644 --- a/resources/js/components/partials/header.tsx +++ b/resources/js/components/partials/header.tsx @@ -1,5 +1,5 @@ import { cn } from '@/lib/utils'; -import { dashboard } from '@/routes'; +import { dashboard, login } from '@/routes'; import { type SharedData } from '@/types'; import { __ } from '@/utils/i18n'; import { Link, usePage } from '@inertiajs/react'; @@ -119,7 +119,7 @@ export default function Header({ ) : ( <> - +