Keep the app subdomain visible during Ladill SSO login and logout.
Deploy Ladill Events / deploy (push) Successful in 39s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-09 12:15:02 +00:00
co-authored by Cursor
parent 7f7c08292f
commit a3b0c2f9cf
5 changed files with 183 additions and 23 deletions
@@ -11,7 +11,9 @@ use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use Illuminate\View\View;
/**
* "Sign in with Ladill" Authorization Code + PKCE against auth.ladill.com.
@@ -19,7 +21,7 @@ use Illuminate\Support\Str;
*/
class SsoLoginController extends Controller
{
public function connect(Request $request): RedirectResponse
public function connect(Request $request): RedirectResponse|View
{
$intended = (string) $request->query('redirect', route('events.dashboard'));
@@ -58,10 +60,26 @@ class SsoLoginController extends Controller
$query['prompt'] = 'none';
}
return redirect()->away(rtrim((string) config('services.ladill_sso.issuer'), '/').'/oauth/authorize?'.http_build_query($query));
$authorizeUrl = rtrim((string) config('services.ladill_sso.issuer'), '/').'/oauth/authorize?'.http_build_query($query);
if ($request->boolean('interactive') && ! $request->boolean('fallback')) {
$request->session()->put('sso.popup', true);
return view('auth.sso-signing-in', [
'authorizeUrl' => $authorizeUrl,
'intended' => $intended,
'fallbackUrl' => route('sso.connect', [
'redirect' => $intended,
'interactive' => 1,
'fallback' => 1,
]),
]);
}
return redirect()->away($authorizeUrl);
}
public function callback(Request $request): RedirectResponse
public function callback(Request $request): RedirectResponse|View
{
$intended = (string) $request->session()->get('sso.intended', route('events.dashboard'));
@@ -74,18 +92,12 @@ class SsoLoginController extends Controller
]);
}
return redirect()->route('sso.connect', [
'redirect' => $intended,
'interactive' => 1,
]);
return $this->finishCallback($request, $intended, (string) $request->query('error_description', $request->query('error')));
}
if (! $request->filled('code')
|| $request->query('state') !== $request->session()->pull('sso.state')) {
return redirect()->route('sso.connect', [
'redirect' => $intended,
'interactive' => 1,
]);
return $this->finishCallback($request, $intended, 'invalid_state');
}
$issuer = rtrim((string) config('services.ladill_sso.issuer'), '/');
@@ -99,18 +111,12 @@ class SsoLoginController extends Controller
'code_verifier' => (string) $request->session()->pull('sso.verifier'),
]);
if ($tokenRes->failed()) {
return redirect()->route('sso.connect', [
'redirect' => $intended,
'interactive' => 1,
]);
return $this->finishCallback($request, $intended, 'token_exchange_failed');
}
$user = $this->loginFromTokenResponse($request, $tokenRes);
if (! $user) {
return redirect()->route('sso.connect', [
'redirect' => $intended,
'interactive' => 1,
]);
return $this->finishCallback($request, $intended, 'userinfo_failed');
}
QrTeamMember::linkPendingInvitesFor($user);
@@ -118,7 +124,7 @@ class SsoLoginController extends Controller
Auth::login($user, remember: true);
$request->session()->regenerate();
return $this->safeRedirect($intended, route('events.dashboard'));
return $this->finishCallback($request, $intended);
}
public function logout(Request $request): RedirectResponse
@@ -127,10 +133,24 @@ class SsoLoginController extends Controller
$request->session()->invalidate();
$request->session()->regenerateToken();
$home = 'https://'.config('app.platform_domain');
$endSession = 'https://'.config('app.auth_domain').'/logout/sso?redirect='.urlencode($home);
return redirect()->route('sso.logout-bridge', [
'return' => $this->defaultSignedOutUrl(),
]);
}
return redirect()->away($endSession);
public function logoutBridge(Request $request): View
{
$return = $this->safeReturnUrl((string) $request->query('return', ''));
$hubUrl = 'https://'.config('app.auth_domain').'/logout/sso/hub?'.http_build_query([
'embedded' => 1,
'return' => $return,
]);
return view('auth.sso-logout-bridge', [
'hubUrl' => $hubUrl,
'return' => $return,
'authOrigin' => 'https://'.config('app.auth_domain'),
]);
}
public function frontchannelLogout(Request $request): Response|RedirectResponse
@@ -228,6 +248,50 @@ class SsoLoginController extends Controller
return strtolower((string) $user->email) !== $mailbox;
}
private function finishCallback(Request $request, string $intended, ?string $error = null): RedirectResponse|View
{
if ($request->session()->pull('sso.popup')) {
return view('auth.sso-popup-done', [
'intended' => $intended,
'error' => $error,
'appOrigin' => rtrim((string) config('app.url'), '/'),
]);
}
if ($error) {
return redirect()->route('sso.connect', [
'redirect' => $intended,
'interactive' => 1,
]);
}
return $this->safeRedirect($intended, route('events.dashboard'));
}
private function defaultSignedOutUrl(): string
{
foreach (array_keys(Route::getRoutes()->getRoutesByName()) as $name) {
if (str_ends_with($name, '.signed-out')) {
return route($name);
}
}
return 'https://'.config('app.platform_domain');
}
private function safeReturnUrl(string $url): string
{
$host = parse_url($url, PHP_URL_HOST);
$root = (string) config('app.platform_domain', 'ladill.com');
if (is_string($host) && str_starts_with($url, 'https://')
&& ($host === $root || str_ends_with($host, '.'.$root))) {
return $url;
}
return $this->defaultSignedOutUrl();
}
private function safeRedirect(string $url, string $fallback): RedirectResponse
{
$host = parse_url($url, PHP_URL_HOST);