diff --git a/app/Http/Controllers/Auth/SsoLoginController.php b/app/Http/Controllers/Auth/SsoLoginController.php index 1b658ed..283d450 100644 --- a/app/Http/Controllers/Auth/SsoLoginController.php +++ b/app/Http/Controllers/Auth/SsoLoginController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use App\Models\QrTeamMember; use App\Models\User; +use Illuminate\Http\Client\Response as HttpResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; @@ -26,6 +27,10 @@ class SsoLoginController extends Controller return $this->safeRedirect($intended, route('qr.dashboard')); } + if ($this->attemptSilentRefresh($request, $intended)) { + return $this->safeRedirect($intended, route('qr.dashboard')); + } + $verifier = Str::random(64); $state = Str::random(40); $request->session()->put('sso.verifier', $verifier); @@ -44,6 +49,11 @@ class SsoLoginController extends Controller 'code_challenge_method' => 'S256', ]; + $loginHint = (string) $request->session()->get('sso.login_hint', ''); + if ($loginHint !== '') { + $query['login_hint'] = $loginHint; + } + if (! $request->boolean('interactive')) { $query['prompt'] = 'none'; } @@ -95,23 +105,14 @@ class SsoLoginController extends Controller ]); } - $claims = Http::withToken((string) $tokenRes->json('access_token'))->acceptJson()->get($issuer.'/oauth/userinfo'); - if ($claims->failed() || ! $claims->json('sub')) { + $user = $this->loginFromTokenResponse($request, $tokenRes); + if (! $user) { return redirect()->route('sso.connect', [ 'redirect' => $intended, 'interactive' => 1, ]); } - $user = User::updateOrCreate( - ['public_id' => (string) $claims->json('sub')], - [ - 'name' => $claims->json('name'), - 'email' => $claims->json('email') ?: (string) $claims->json('sub').'@users.ladill.com', - 'avatar_url' => $claims->json('picture'), - ], - ); - QrTeamMember::linkPendingInvitesFor($user); Auth::login($user, remember: true); @@ -150,6 +151,68 @@ class SsoLoginController extends Controller return response('', 204); } + private function attemptSilentRefresh(Request $request, string $intended): bool + { + $refreshToken = (string) $request->session()->get('sso.refresh_token', ''); + if ($refreshToken === '') { + return false; + } + + $issuer = rtrim((string) config('services.ladill_sso.issuer'), '/'); + $tokenRes = Http::asForm()->post($issuer.'/oauth/token', [ + 'grant_type' => 'refresh_token', + 'refresh_token' => $refreshToken, + 'client_id' => (string) config('services.ladill_sso.client_id'), + 'client_secret' => (string) config('services.ladill_sso.client_secret'), + 'scope' => 'openid profile email', + ]); + + if ($tokenRes->failed()) { + $request->session()->forget('sso.refresh_token'); + + return false; + } + + $user = $this->loginFromTokenResponse($request, $tokenRes); + + if (! $user) { + return false; + } + + Auth::login($user, remember: true); + $request->session()->put('sso.intended', $intended); + + return true; + } + + private function loginFromTokenResponse(Request $request, HttpResponse $tokenRes): ?User + { + $refreshToken = (string) $tokenRes->json('refresh_token', ''); + if ($refreshToken !== '') { + $request->session()->put('sso.refresh_token', $refreshToken); + } + + $issuer = rtrim((string) config('services.ladill_sso.issuer'), '/'); + $claims = Http::withToken((string) $tokenRes->json('access_token'))->acceptJson()->get($issuer.'/oauth/userinfo'); + if ($claims->failed() || ! $claims->json('sub')) { + return null; + } + + $email = (string) ($claims->json('email') ?: ''); + if ($email !== '') { + $request->session()->put('sso.login_hint', $email); + } + + return User::updateOrCreate( + ['public_id' => (string) $claims->json('sub')], + [ + 'name' => $claims->json('name'), + 'email' => $email !== '' ? $email : (string) $claims->json('sub').'@users.ladill.com', + 'avatar_url' => $claims->json('picture'), + ], + ); + } + private function shouldLogoutForMailbox(Request $request): bool { $mailbox = strtolower(trim((string) $request->query('mailbox', ''))); diff --git a/config/ladill_launcher.php b/config/ladill_launcher.php index cac9c15..38c9c0f 100644 --- a/config/ladill_launcher.php +++ b/config/ladill_launcher.php @@ -30,7 +30,7 @@ return [ ['name' => 'Domains', 'url' => 'https://domains.'.$root, 'icon' => 'domains.svg'], ['name' => 'Servers', 'url' => 'https://servers.'.$root, 'icon' => 'servers.svg'], ['name' => 'Hosting', 'url' => 'https://hosting.'.$root, 'icon' => 'hosting.svg'], - ['name' => 'QR Plus', 'url' => 'https://qrplus.'.$root, 'icon' => 'qrplus.svg'], - ['name' => 'Events', 'url' => 'https://events.'.$root, 'icon' => 'events.svg'], + ['name' => 'QR Plus', 'url' => 'https://qrplus.'.$root.'/sso/connect?redirect='.urlencode('https://qrplus.'.$root.'/dashboard'), 'icon' => 'qrplus.svg'], + ['name' => 'Events', 'url' => 'https://events.'.$root.'/sso/connect?redirect='.urlencode('https://events.'.$root.'/dashboard'), 'icon' => 'events.svg'], ], ]; diff --git a/resources/views/partials/sso-keepalive.blade.php b/resources/views/partials/sso-keepalive.blade.php index ecd5d4a..bc14950 100644 --- a/resources/views/partials/sso-keepalive.blade.php +++ b/resources/views/partials/sso-keepalive.blade.php @@ -1,4 +1,13 @@ @if (auth()->check()) - {{-- Same-site iframe keeps the shared auth.ladill.com session warm while using this app. --}} - + @php $authPing = 'https://'.config('app.auth_domain').'/sso/ping'; @endphp + {{-- Same-site pings keep the shared auth.ladill.com session warm while using this app. --}} + + @endif