diff --git a/app/Http/Controllers/KekaOutlookController.php b/app/Http/Controllers/KekaOutlookController.php index 2a299fb..d542684 100644 --- a/app/Http/Controllers/KekaOutlookController.php +++ b/app/Http/Controllers/KekaOutlookController.php @@ -10,16 +10,19 @@ final class KekaOutlookController extends Controller { - public function __construct(private KekaOutlookService $kekaOutlook) {} + public function __construct(private readonly KekaOutlookService $kekaOutlook) {} /** * GET /keka/connect * - * If the user already has a stored refresh token, we don't show - * Microsoft's login screen at all — just confirm/refresh silently - * and send them straight back to Keka. + * FIRST TIME: + * → Your Microsoft OAuth (stores token in oauth_tokens) + * → callback stores token → redirect to sentientgeeks.keka.com + * (Keka's SSO kicks in automatically since Microsoft session exists) * - * If not connected yet, redirect to Microsoft login (one-time). + * ALREADY CONNECTED: + * → Silent token refresh if near expiry + * → Straight to sentientgeeks.keka.com */ public function connect(Request $request): RedirectResponse { @@ -27,75 +30,64 @@ public function connect(Request $request): RedirectResponse if ($this->kekaOutlook->isConnected($user)) { try { - // Ensures token is fresh; throws if refresh token is invalid/revoked. $this->kekaOutlook->getValidToken($user); - - return redirect()->away($this->kekaWebsiteUrl()); - } catch (\RuntimeException $e) { - // Refresh failed -> fall through to re-login below. + } catch (\RuntimeException) { + // Token fully expired/revoked → re-authenticate + return redirect()->away($this->kekaOutlook->getAuthUrl()); } + + // Already connected and token valid → go to Keka + return redirect()->away((string) config('microsoft.keka_dashboard_url')); } + // Not connected yet → your Microsoft OAuth first return redirect()->away($this->kekaOutlook->getAuthUrl()); } /** * GET /keka/callback * - * Microsoft redirects here after login with ?code=... or ?error=... + * Microsoft posts auth code here → store token → redirect to Keka. + * Keka's own SSO will auto-login since Microsoft session is now active. */ public function callback(Request $request): RedirectResponse { if ($request->filled('error')) { - return redirect() - ->route('dashboard') - ->with('error', 'Microsoft login was cancelled or failed: '.$request->get('error_description', $request->get('error'))); + return redirect()->route('dashboard') + ->with('error', 'Microsoft login failed: ' . $request->get('error_description', $request->get('error'))); } if (! $this->kekaOutlook->isValidState($request->get('state'))) { - return redirect() - ->route('dashboard') - ->with('error', 'Invalid login session. Please try connecting again.'); + return redirect()->route('dashboard') + ->with('error', 'Invalid session state. Please try connecting again.'); } $code = $request->get('code'); if (! $code) { - return redirect() - ->route('dashboard') - ->with('error', 'No authorization code received from Microsoft.'); + return redirect()->route('dashboard') + ->with('error', 'No authorization code received.'); } $result = $this->kekaOutlook->handleCallback($request->user(), $code); if (! $result['success']) { - return redirect() - ->route('dashboard') - ->with('error', 'Failed to connect: '.($result['message'] ?? 'Unknown error')); + return redirect()->route('dashboard') + ->with('error', 'Connection failed: ' . ($result['message'] ?? 'Unknown error')); } - return redirect()->away($this->kekaWebsiteUrl()); + // Token stored ✓ → Keka SSO will auto-login via active Microsoft session + return redirect()->away((string) config('microsoft.keka_dashboard_url')); } /** * POST /keka/disconnect - * - * Wipes stored tokens. User must go through Microsoft login again next time. */ public function disconnect(Request $request): RedirectResponse { $this->kekaOutlook->disconnect($request->user()); - return redirect() - ->route('dashboard') + return redirect()->route('dashboard') ->with('success', 'Keka has been disconnected.'); } - - /** - * The Keka website URL the user lands on after a successful (silent) connection. - */ - private function kekaWebsiteUrl(): string - { - return (string) config('microsoft.keka_website_url', 'https://app.keka.com'); - } } \ No newline at end of file diff --git a/config/microsoft.php b/config/microsoft.php index ec3be99..4a78e2d 100644 --- a/config/microsoft.php +++ b/config/microsoft.php @@ -1,16 +1,26 @@ - env('MICROSOFT_GRAPH_CLIENT_ID'), 'client_secret' => env('MICROSOFT_GRAPH_CLIENT_SECRET'), 'tenant_id' => env('MICROSOFT_GRAPH_TENANT_ID'), + + // Your app's redirect URI for Keka OAuth flow 'keka_redirect_uri' => env('KEKA_MS_REDIRECT_URI', 'http://localhost:8000/keka/callback'), - 'keka_scopes' => ['openid', - 'profile', - 'email', - 'offline_access', - 'User.Read', - 'Mail.Read', - 'Mail.ReadWrite', - 'Mail.Send'] + + // After first-time login: Keka's own Microsoft SSO URL + // After already connected: Keka dashboard + 'keka_login_url' => env('KEKA_LOGIN_URL', 'https://sentientgeeks.keka.com/'), + 'keka_dashboard_url'=> env('KEKA_DASHBOARD_URL', 'https://sentientgeeks.keka.com/'), + + 'keka_scopes' => [ + 'openid', + 'profile', + 'email', + 'offline_access', + 'User.Read', + 'Mail.Read', + 'Mail.ReadWrite', + 'Mail.Send', + ], ]; \ No newline at end of file diff --git a/database/migrations/2026_06_12_125836_add_keka_connected_to_users_table.php b/database/migrations/2026_06_12_125836_add_keka_connected_to_users_table.php new file mode 100644 index 0000000..0f50901 --- /dev/null +++ b/database/migrations/2026_06_12_125836_add_keka_connected_to_users_table.php @@ -0,0 +1,22 @@ +boolean('keka_connected')->default(false); + }); + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('keka_connected'); + }); + } +}; \ No newline at end of file