authorizedApps(); return view('dashboard', compact('user', 'services')); } /** * Launch a service app using SSO. */ public function launchSSO($id) { $user = Auth::user(); $app = \App\Models\App::findOrFail($id); // Check if user is authorized to access this app $authorizedApps = $user->authorizedApps(); if (!$authorizedApps->contains('id', $app->id)) { return redirect()->route('dashboard')->with('error', 'Access denied. You do not have permission to access this service.'); } // If user is logged in via Microsoft, route all service launches (including Keka) // through the Microsoft SSO flow to ensure their browser has an active Microsoft session. if ($user->microsoft_id) { // Save target app URL in session so callback knows where to redirect session(['sso_target_url' => $app->url]); try { // Determine domain hint based on user's email domain to skip account type prompt $domainHint = (str_ends_with($user->email, '.onmicrosoft.com') || (str_contains($user->email, '@') && !str_ends_with($user->email, 'outlook.com') && !str_ends_with($user->email, 'hotmail.com') && !str_ends_with($user->email, 'live.com'))) ? 'organizations' : 'consumers'; // Redirect to Microsoft OAuth authorize page with login_hint, domain_hint, prompt=none and custom callback return Socialite::driver('microsoft') ->redirectUrl(route('sso.callback')) ->with([ 'login_hint' => $user->email, 'domain_hint' => $domainHint, 'prompt' => 'none' ]) ->redirect(); } catch (\Exception $e) { \Illuminate\Support\Facades\Log::error('Microsoft SSO launch failed', [ 'message' => $e->getMessage() ]); // Fallback: direct redirect return redirect($app->url); } } // For non-Microsoft or non-Microsoft-linked users, redirect directly return redirect($app->url); } /** * Handle the callback for Microsoft SSO. */ public function handleSSOCallback(Request $request) { // If Microsoft returns an error (user logged out or session expired) if ($request->has('error')) { Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return redirect()->route('login')->with('error', 'You have been signed out because your Microsoft session is no longer active.'); } $user = Auth::user(); try { $microsoftUser = Socialite::driver('microsoft') ->redirectUrl(route('sso.callback')) ->user(); if ($microsoftUser && $user) { // Update tokens in db $user->update([ 'microsoft_token' => $microsoftUser->token, 'microsoft_refresh_token' => $microsoftUser->refreshToken ?? $user->microsoft_refresh_token, ]); // Update verification timestamp session(['ms_session_last_checked' => now()]); } } catch (\Exception $e) { \Illuminate\Support\Facades\Log::error('Microsoft SSO callback failed', [ 'message' => $e->getMessage() ]); // Treat token exchange failure as an expired session Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return redirect()->route('login')->with('error', 'Your Microsoft session has expired. Please log in again.'); } $targetUrl = session('sso_target_url', 'https://outlook.office.com/mail/'); session()->forget('sso_target_url'); // If launching Microsoft Teams, redirect to the deep link launcher page if (str_contains($targetUrl, 'teams.microsoft.com')) { return redirect()->route('sso.launch-app', [ 'name' => 'Microsoft Teams', 'url' => $targetUrl, 'protocol' => 'msteams://' ]); } return redirect($targetUrl); } /** * Handle silent session verification callback from Microsoft. */ public function handleSessionCheckCallback(Request $request) { // If Microsoft returns an error (user logged out or session expired) if ($request->has('error')) { Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return redirect()->route('login')->with('error', 'You have been signed out because you signed out of your Microsoft account.'); } try { $microsoftUser = Socialite::driver('microsoft') ->redirectUrl(route('sso.callback-check')) ->user(); $user = Auth::user(); if ($microsoftUser && $user) { // Update active tokens in DB $user->update([ 'microsoft_token' => $microsoftUser->token, 'microsoft_refresh_token' => $microsoftUser->refreshToken ?? $user->microsoft_refresh_token, ]); // Update verification timestamp session(['ms_session_last_checked' => now()]); } } catch (\Exception $e) { \Illuminate\Support\Facades\Log::error('Microsoft session check callback failed', [ 'message' => $e->getMessage() ]); // Treat token exchange failure as an expired session Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return redirect()->route('login')->with('error', 'Your Microsoft session has expired. Please log in again.'); } // Redirect back to the page they were trying to access $origin = session('sso_check_origin', route('dashboard')); session()->forget('sso_check_origin'); return redirect($origin); } /** * Render the desktop application launcher page with web fallback. */ public function launchApp(Request $request) { $name = $request->query('name', 'Application'); $url = $request->query('url'); $protocol = $request->query('protocol'); if (!$url || !$protocol) { return redirect()->route('dashboard'); } return view('sso.launch-app', compact('name', 'url', 'protocol')); } }