diff --git a/src/Http/Controllers/Auth/Concerns/RedirectsToIntendedUrl.php b/src/Http/Controllers/Auth/Concerns/RedirectsToIntendedUrl.php new file mode 100644 index 000000000..d74242d4f --- /dev/null +++ b/src/Http/Controllers/Auth/Concerns/RedirectsToIntendedUrl.php @@ -0,0 +1,39 @@ +to($url) + : redirect()->intended(route('code16.sharp.home')); + + if (request()->inertia() && ! $this->isSharpUrl($redirect->getTargetUrl())) { + return Inertia::location($redirect->getTargetUrl()); + } + + return $redirect; + } + + private function isSharpUrl(string $url): bool + { + try { + return Route::getRoutes()->match(Request::create($url))->named('code16.sharp.*'); + } catch (NotFoundHttpException|MethodNotAllowedHttpException) { + return false; + } + } +} diff --git a/src/Http/Controllers/Auth/ImpersonateController.php b/src/Http/Controllers/Auth/ImpersonateController.php index 62711228b..f5058cdf9 100644 --- a/src/Http/Controllers/Auth/ImpersonateController.php +++ b/src/Http/Controllers/Auth/ImpersonateController.php @@ -3,14 +3,18 @@ namespace Code16\Sharp\Http\Controllers\Auth; use Code16\Sharp\Auth\Impersonate\SharpImpersonationHandler; +use Code16\Sharp\Http\Controllers\Auth\Concerns\RedirectsToIntendedUrl; use Code16\Sharp\Http\Controllers\Auth\Requests\ImpersonateRequest; use Illuminate\Http\RedirectResponse; use Illuminate\Routing\Controller; use Inertia\Inertia; use Inertia\Response; +use Symfony\Component\HttpFoundation\Response as SymfonyResponse; class ImpersonateController extends Controller { + use RedirectsToIntendedUrl; + public function create(?SharpImpersonationHandler $impersonationHandler): RedirectResponse|Response { return Inertia::render('Auth/Impersonate', [ @@ -20,13 +24,13 @@ public function create(?SharpImpersonationHandler $impersonationHandler): Redire ]); } - public function store(ImpersonateRequest $request): RedirectResponse + public function store(ImpersonateRequest $request): SymfonyResponse { auth(sharp()->config()->get('auth.guard')) ->loginUsingId($request->input('user_id')); $request->session()->regenerate(); - return redirect()->intended(route('code16.sharp.home')); + return $this->redirectToIntendedUrl(); } } diff --git a/src/Http/Controllers/Auth/LoginController.php b/src/Http/Controllers/Auth/LoginController.php index 6f13f92c4..57836305f 100644 --- a/src/Http/Controllers/Auth/LoginController.php +++ b/src/Http/Controllers/Auth/LoginController.php @@ -4,6 +4,7 @@ use Code16\Sharp\Auth\Passkeys\PasskeyManager; use Code16\Sharp\Exceptions\Auth\SharpAuthenticationNeeds2faException; +use Code16\Sharp\Http\Controllers\Auth\Concerns\RedirectsToIntendedUrl; use Code16\Sharp\Http\Controllers\Auth\Requests\LoginRequest; use Illuminate\Contracts\View\View; use Illuminate\Http\RedirectResponse; @@ -13,9 +14,12 @@ use Illuminate\Support\Facades\Route; use Inertia\Inertia; use Inertia\Response; +use Symfony\Component\HttpFoundation\Response as SymfonyResponse; class LoginController extends Controller { + use RedirectsToIntendedUrl; + public function create(PasskeyManager $passkeys): RedirectResponse|Response { if ($loginPageUrl = sharp()->config()->get('auth.login_page_url')) { @@ -41,7 +45,7 @@ public function create(PasskeyManager $passkeys): RedirectResponse|Response ]); } - public function store(LoginRequest $request, PasskeyManager $passkeys): RedirectResponse + public function store(LoginRequest $request, PasskeyManager $passkeys): SymfonyResponse { try { $request->authenticate(); @@ -61,7 +65,7 @@ public function store(LoginRequest $request, PasskeyManager $passkeys): Redirect return redirect()->route('code16.sharp.passkeys.create', ['prompt' => true]); } - return redirect()->intended(route('code16.sharp.home')); + return $this->redirectToIntendedUrl(); } public function destroy(Request $request): RedirectResponse diff --git a/src/Http/Controllers/Auth/LoginMultiFactorController.php b/src/Http/Controllers/Auth/LoginMultiFactorController.php index 683cf02a2..49bee8031 100644 --- a/src/Http/Controllers/Auth/LoginMultiFactorController.php +++ b/src/Http/Controllers/Auth/LoginMultiFactorController.php @@ -5,15 +5,19 @@ use Code16\Sharp\Auth\Passkeys\PasskeyManager; use Code16\Sharp\Auth\TwoFactor\MultiFactorManager; use Code16\Sharp\Enums\MultiFactorMethod; +use Code16\Sharp\Http\Controllers\Auth\Concerns\RedirectsToIntendedUrl; use Code16\Sharp\Http\Controllers\Auth\Requests\Login2faRequest; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Inertia\Inertia; use Inertia\Response; +use Symfony\Component\HttpFoundation\Response as SymfonyResponse; class LoginMultiFactorController extends Controller { + use RedirectsToIntendedUrl; + public function create(Request $request, MultiFactorManager $multiFactor, PasskeyManager $passkeys): RedirectResponse|Response { $user = $multiFactor->pendingUser(); @@ -34,11 +38,11 @@ public function create(Request $request, MultiFactorManager $multiFactor, Passke return redirect()->route('code16.sharp.login'); } - public function store(Login2faRequest $request, MultiFactorManager $twoFactor): RedirectResponse + public function store(Login2faRequest $request, MultiFactorManager $twoFactor): SymfonyResponse { $request->authenticate($twoFactor->currentHandler()); $request->session()->regenerate(); - return redirect()->intended(route('code16.sharp.home')); + return $this->redirectToIntendedUrl(); } } diff --git a/src/Http/Controllers/Auth/Passkeys/PasskeyAuthenticatedController.php b/src/Http/Controllers/Auth/Passkeys/PasskeyAuthenticatedController.php index 768ffa4ff..fab7afc5e 100644 --- a/src/Http/Controllers/Auth/Passkeys/PasskeyAuthenticatedController.php +++ b/src/Http/Controllers/Auth/Passkeys/PasskeyAuthenticatedController.php @@ -3,12 +3,15 @@ namespace Code16\Sharp\Http\Controllers\Auth\Passkeys; use Code16\Sharp\Auth\TwoFactor\MultiFactorManager; +use Code16\Sharp\Http\Controllers\Auth\Concerns\RedirectsToIntendedUrl; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Illuminate\Support\Facades\Auth; class PasskeyAuthenticatedController extends Controller { + use RedirectsToIntendedUrl; + public function __invoke(Request $request, MultiFactorManager $multiFactor) { if ($multiFactor->isUsingPasskeyMethod()) { @@ -29,6 +32,6 @@ public function __invoke(Request $request, MultiFactorManager $multiFactor) $multiFactor->currentHandler()->forgetCode(); } - return redirect(request()->input('intended_url', route('code16.sharp.home'))); + return $this->redirectToIntendedUrl($request->input('intended_url', route('code16.sharp.home'))); } } diff --git a/src/Http/Controllers/Auth/Passkeys/PasskeyRegisteredController.php b/src/Http/Controllers/Auth/Passkeys/PasskeyRegisteredController.php index 9847387b3..3685aa779 100644 --- a/src/Http/Controllers/Auth/Passkeys/PasskeyRegisteredController.php +++ b/src/Http/Controllers/Auth/Passkeys/PasskeyRegisteredController.php @@ -3,12 +3,15 @@ namespace Code16\Sharp\Http\Controllers\Auth\Passkeys; use Code16\Sharp\Auth\TwoFactor\MultiFactorManager; +use Code16\Sharp\Http\Controllers\Auth\Concerns\RedirectsToIntendedUrl; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Illuminate\Support\Facades\Auth; class PasskeyRegisteredController extends Controller { + use RedirectsToIntendedUrl; + public function __invoke(Request $request, MultiFactorManager $multiFactor) { if (! session()->has('registered_passkey')) { @@ -24,6 +27,6 @@ public function __invoke(Request $request, MultiFactorManager $multiFactor) $multiFactor->currentHandler()->forgetCode(); } - return redirect()->intended(route('code16.sharp.home')); + return $this->redirectToIntendedUrl(); } } diff --git a/tests/Http/Auth/AuthenticationTest.php b/tests/Http/Auth/AuthenticationTest.php index 7dc202104..9b34b29d6 100644 --- a/tests/Http/Auth/AuthenticationTest.php +++ b/tests/Http/Auth/AuthenticationTest.php @@ -48,6 +48,35 @@ function setTestAuthGuard(): void expect(auth('sharp')->check())->toBeTrue(); }); +it('redirects Inertia login to the intended Sharp url', function () { + setTestAuthGuard(); + + $this + ->withSession(['url.intended' => route('code16.sharp.list', 'person')]) + ->withHeader('X-Inertia', 'true') + ->post(route('code16.sharp.login.post'), [ + 'login' => 'test@example.org', + 'password' => 'password', + ]) + ->assertRedirect(route('code16.sharp.list', 'person')); +}); + +it('forces a full page visit on Inertia login when the intended url is not a Sharp page', function () { + setTestAuthGuard(); + + $this + ->withSession(['url.intended' => url('/tools/glyphs')]) + ->withHeader('X-Inertia', 'true') + ->post(route('code16.sharp.login.post'), [ + 'login' => 'test@example.org', + 'password' => 'password', + ]) + ->assertStatus(409) + ->assertHeader('X-Inertia-Location', url('/tools/glyphs')); + + expect(auth('sharp')->check())->toBeTrue(); +}); + it('does not allow to login with invalid payload', function () { $this->post(route('code16.sharp.login.post')) ->assertSessionHasErrors(['login', 'password']);