diff --git a/apps/x/apps/main/src/ipc.ts b/apps/x/apps/main/src/ipc.ts index 42690bda..53b2f913 100644 --- a/apps/x/apps/main/src/ipc.ts +++ b/apps/x/apps/main/src/ipc.ts @@ -6,8 +6,6 @@ import { isConnected, getConnectedProviders, listProviders, - getAuthStatus, - logoutRowboat, } from './oauth-handler.js'; import { watcher as watcherCore, workspace } from '@x/core'; import { workspace as workspaceShared } from '@x/shared'; @@ -223,15 +221,6 @@ export function emitOAuthEvent(event: { provider: string; success: boolean; erro } } -export function emitAuthEvent(event: { isAuthenticated: boolean; user: { email: string; name?: string } | null }): void { - const windows = BrowserWindow.getAllWindows(); - for (const win of windows) { - if (!win.isDestroyed() && win.webContents) { - win.webContents.send('auth:didAuthenticate', event); - } - } -} - let runsWatcher: (() => void) | null = null; export async function startRunsWatcher(): Promise { if (runsWatcher) { @@ -356,15 +345,6 @@ export function setupIpcHandlers() { markOnboardingComplete(); return { success: true }; }, - 'auth:getStatus': async () => { - return await getAuthStatus(); - }, - 'auth:login': async () => { - return await connectProvider('rowboat'); - }, - 'auth:logout': async () => { - return await logoutRowboat(); - }, // Composio integration handlers 'composio:is-configured': async () => { return composioHandler.isConfigured(); diff --git a/apps/x/apps/main/src/oauth-handler.ts b/apps/x/apps/main/src/oauth-handler.ts index da4b7e62..58ab0809 100644 --- a/apps/x/apps/main/src/oauth-handler.ts +++ b/apps/x/apps/main/src/oauth-handler.ts @@ -9,13 +9,10 @@ import { IOAuthRepo } from '@x/core/dist/auth/repo.js'; import { IClientRegistrationRepo } from '@x/core/dist/auth/client-repo.js'; import { triggerSync as triggerCalendarSync } from '@x/core/dist/knowledge/sync_calendar.js'; import { triggerSync as triggerFirefliesSync } from '@x/core/dist/knowledge/sync_fireflies.js'; -import { emitOAuthEvent, emitAuthEvent } from './ipc.js'; +import { emitOAuthEvent } from './ipc.js'; const REDIRECT_URI = 'http://localhost:8080/oauth/callback'; -// Cached user info for the rowboat provider -let cachedRowboatUser: { email: string; name?: string } | null = null; - // Store active OAuth flows (state -> { codeVerifier, provider, config }) const activeFlows = new Map { - try { - const oauthRepo = getOAuthRepo(); - const connected = await oauthRepo.isConnected('rowboat'); - if (!connected) { - cachedRowboatUser = null; - return { isAuthenticated: false, user: null }; - } - - // If we have cached user info, return it - if (cachedRowboatUser) { - return { isAuthenticated: true, user: cachedRowboatUser }; - } - - // Get stored tokens to check for id_token_sub - const storedTokens = await oauthRepo.getTokens('rowboat'); - if (!storedTokens?.id_token_sub) { - // Legacy tokens without sub claim — require re-login - console.log('[OAuth] No id_token_sub in stored tokens, requiring re-login'); - cachedRowboatUser = null; - return { isAuthenticated: false, user: null }; - } - - // Try to get access token (will refresh if needed) - const accessToken = await getAccessToken('rowboat'); - if (!accessToken) { - cachedRowboatUser = null; - return { isAuthenticated: false, user: null }; - } - - // Fetch user info via OIDC discovery - try { - const config = await getProviderConfiguration('rowboat'); - cachedRowboatUser = await oauthClient.fetchUserInfo(config, accessToken, storedTokens.id_token_sub); - } catch (error) { - console.error('[OAuth] Failed to fetch user info via OIDC:', error); - cachedRowboatUser = null; - return { isAuthenticated: false, user: null }; - } - - return { isAuthenticated: true, user: cachedRowboatUser }; - } catch (error) { - console.error('[OAuth] Auth status check failed:', error); - return { isAuthenticated: false, user: null }; - } -} - -/** - * Logout from rowboat (clear tokens and cached user) - */ -export async function logoutRowboat(): Promise<{ success: boolean }> { - cachedRowboatUser = null; - return disconnectProvider('rowboat'); -} - /** * Initiate OAuth flow for a provider */ @@ -241,18 +180,13 @@ export async function connectProvider(provider: string): Promise<{ success: bool // Exchange code for tokens console.log(`[OAuth] Exchanging authorization code for tokens (${provider})...`); - const { tokens, sub } = await oauthClient.exchangeCodeForTokens( + const tokens = await oauthClient.exchangeCodeForTokens( flow.config, callbackUrl, flow.codeVerifier, state ); - // Persist the subject claim for future userinfo fetches - if (sub) { - tokens.id_token_sub = sub; - } - // Save tokens console.log(`[OAuth] Token exchange successful for ${provider}`); await oauthRepo.saveTokens(provider, tokens); @@ -264,18 +198,6 @@ export async function connectProvider(provider: string): Promise<{ success: bool triggerFirefliesSync(); } - // For rowboat provider, fetch user info and emit auth event - if (provider === 'rowboat' && sub) { - try { - const userInfo = await oauthClient.fetchUserInfo(flow.config, tokens.access_token, sub); - cachedRowboatUser = userInfo; - emitAuthEvent({ isAuthenticated: true, user: userInfo }); - } catch (error) { - console.error('[OAuth] Failed to fetch user info via OIDC:', error); - emitAuthEvent({ isAuthenticated: true, user: null }); - } - } - // Emit success event to renderer emitOAuthEvent({ provider, success: true }); } catch (error) { diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index f45bd486..25ab8031 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -50,8 +50,6 @@ import { Separator } from "@/components/ui/separator" import { Toaster } from "@/components/ui/sonner" import { stripKnowledgePrefix, toKnowledgePath, wikiLabel } from '@/lib/wiki-links' import { OnboardingModal } from '@/components/onboarding-modal' -import { useRowboatAuth } from '@/hooks/useRowboatAuth' -import { LoginScreen } from '@/components/login-screen' type DirEntry = z.infer type RunEventType = z.infer @@ -443,24 +441,6 @@ function ChatInputWithMentions({ } function App() { - const auth = useRowboatAuth() - - if (auth.isLoading) { - return ( -
- -
- ) - } - - if (!auth.isAuthenticated) { - return - } - - return -} - -function AppContent({ auth }: { auth: ReturnType }) { // File browser state (for Knowledge section) const [selectedPath, setSelectedPath] = useState(null) const [fileHistoryBack, setFileHistoryBack] = useState([]) @@ -1712,7 +1692,7 @@ function AppContent({ auth }: { auth: ReturnType }) {
{/* Icon sidebar - always visible, fixed position */} - + {/* Spacer for the fixed icon sidebar */}
diff --git a/apps/x/apps/renderer/src/components/login-screen.tsx b/apps/x/apps/renderer/src/components/login-screen.tsx deleted file mode 100644 index acf69609..00000000 --- a/apps/x/apps/renderer/src/components/login-screen.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Button } from './ui/button'; -import { LoaderIcon } from 'lucide-react'; - -interface LoginScreenProps { - isLoggingIn: boolean; - error: string | null; - login: () => Promise; -} - -export function LoginScreen({ isLoggingIn, error, login }: LoginScreenProps) { - return ( -
-
-
- Rowboat -
-

- Sign in to your Rowboat account to continue. -

- - {error && ( -
- {error} -
- )} - - - - {isLoggingIn && ( -

- Complete sign-in in your browser, then return here. -

- )} -
-
- ); -} diff --git a/apps/x/apps/renderer/src/components/sidebar-icon.tsx b/apps/x/apps/renderer/src/components/sidebar-icon.tsx index e45e1093..54aa0622 100644 --- a/apps/x/apps/renderer/src/components/sidebar-icon.tsx +++ b/apps/x/apps/renderer/src/components/sidebar-icon.tsx @@ -4,7 +4,6 @@ import * as React from "react" import { Brain, HelpCircle, - LogOut, MessageSquare, Plug, Settings, @@ -32,12 +31,7 @@ const navItems: NavItem[] = [ { id: "knowledge", title: "Knowledge", icon: Brain }, ] -interface SidebarIconProps { - user?: { email: string; name?: string } | null; - onLogout?: () => void; -} - -export function SidebarIcon({ user, onLogout }: SidebarIconProps = {}) { +export function SidebarIcon() { const { activeSection, setActiveSection } = useSidebarSection() return ( @@ -94,23 +88,6 @@ export function SidebarIcon({ user, onLogout }: SidebarIconProps = {}) { - - {/* Sign out */} - {onLogout && ( - - - - - - {user?.email ? `Sign out (${user.email})` : 'Sign out'} - - - )}
) diff --git a/apps/x/apps/renderer/src/hooks/useRowboatAuth.ts b/apps/x/apps/renderer/src/hooks/useRowboatAuth.ts deleted file mode 100644 index caa9c969..00000000 --- a/apps/x/apps/renderer/src/hooks/useRowboatAuth.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { useState, useEffect, useCallback } from 'react'; - -interface RowboatAuthState { - isAuthenticated: boolean; - isLoading: boolean; - isLoggingIn: boolean; - user: { email: string; name?: string } | null; - error: string | null; - login: () => Promise; - logout: () => Promise; -} - -export function useRowboatAuth(): RowboatAuthState { - const [isAuthenticated, setIsAuthenticated] = useState(false); - const [isLoading, setIsLoading] = useState(true); - const [isLoggingIn, setIsLoggingIn] = useState(false); - const [user, setUser] = useState<{ email: string; name?: string } | null>(null); - const [error, setError] = useState(null); - - // Check auth status on mount - useEffect(() => { - async function checkStatus() { - try { - const result = await window.ipc.invoke('auth:getStatus', null); - setIsAuthenticated(result.isAuthenticated); - setUser(result.user); - } catch (err) { - console.error('Failed to check auth status:', err); - setIsAuthenticated(false); - setUser(null); - } finally { - setIsLoading(false); - } - } - checkStatus(); - }, []); - - // Listen for auth events - useEffect(() => { - const cleanup = window.ipc.on('auth:didAuthenticate', (event) => { - setIsAuthenticated(event.isAuthenticated); - setUser(event.user); - setIsLoggingIn(false); - setError(null); - }); - return cleanup; - }, []); - - // Also listen for oauth:didConnect for the rowboat provider (handles errors) - useEffect(() => { - const cleanup = window.ipc.on('oauth:didConnect', (event) => { - if (event.provider !== 'rowboat') return; - if (!event.success) { - setIsLoggingIn(false); - setError(event.error || 'Login failed'); - } - }); - return cleanup; - }, []); - - const login = useCallback(async () => { - try { - setIsLoggingIn(true); - setError(null); - const result = await window.ipc.invoke('auth:login', null); - if (!result.success) { - setIsLoggingIn(false); - setError(result.error || 'Failed to start login'); - } - // If success, the OAuth flow has started - wait for auth:didAuthenticate event - } catch (err) { - console.error('Login failed:', err); - setIsLoggingIn(false); - setError('Failed to start login'); - } - }, []); - - const logout = useCallback(async () => { - try { - await window.ipc.invoke('auth:logout', null); - setIsAuthenticated(false); - setUser(null); - } catch (err) { - console.error('Logout failed:', err); - } - }, []); - - return { - isAuthenticated, - isLoading, - isLoggingIn, - user, - error, - login, - logout, - }; -} diff --git a/apps/x/packages/core/src/auth/oauth-client.ts b/apps/x/packages/core/src/auth/oauth-client.ts index 719c8af1..613cee2e 100644 --- a/apps/x/packages/core/src/auth/oauth-client.ts +++ b/apps/x/packages/core/src/auth/oauth-client.ts @@ -159,16 +159,13 @@ export function buildAuthorizationUrl( state: string; } ): URL { - const url = client.buildAuthorizationUrl(config, { + return client.buildAuthorizationUrl(config, { redirect_uri: params.redirectUri, scope: params.scope, code_challenge: params.codeChallenge, code_challenge_method: 'S256', state: params.state, }); - - console.log(`[OAuth] Authorization URL: ${url}`); - return url; } /** @@ -179,7 +176,7 @@ export async function exchangeCodeForTokens( callbackUrl: URL, codeVerifier: string, expectedState: string -): Promise<{ tokens: OAuthTokens; sub?: string }> { +): Promise { console.log(`[OAuth] Exchanging authorization code for tokens...`); const response = await client.authorizationCodeGrant(config, callbackUrl, { @@ -187,27 +184,8 @@ export async function exchangeCodeForTokens( expectedState, }); - const claims = response.claims(); console.log(`[OAuth] Token exchange successful`); - return { - tokens: toOAuthTokens(response), - sub: claims?.sub, - }; -} - -/** - * Fetch user info from the OIDC userinfo endpoint (discovered via issuer metadata) - */ -export async function fetchUserInfo( - config: client.Configuration, - accessToken: string, - expectedSubject: string -): Promise<{ email: string; name?: string }> { - const userInfo = await client.fetchUserInfo(config, accessToken, expectedSubject); - return { - email: userInfo.email ?? '', - name: userInfo.name, - }; + return toOAuthTokens(response); } /** diff --git a/apps/x/packages/core/src/auth/providers.ts b/apps/x/packages/core/src/auth/providers.ts index 9be05314..edda5447 100644 --- a/apps/x/packages/core/src/auth/providers.ts +++ b/apps/x/packages/core/src/auth/providers.ts @@ -77,22 +77,7 @@ const providerConfigs: ProviderConfig = { 'profile', 'email', ] - }, - rowboat: { - discovery: { - mode: 'issuer', - issuer: 'https://yhafoahozylbdyyyqjep.supabase.co/auth/v1', - }, - client: { - mode: 'static', - clientId: '0b8a99ec-b5b2-4ddf-8e14-69a3a1675114', - }, - scopes: [ - 'openid', - 'email', - 'profile', - ], - }, + } }; /** diff --git a/apps/x/packages/core/src/auth/types.ts b/apps/x/packages/core/src/auth/types.ts index bd4bb319..249d63b4 100644 --- a/apps/x/packages/core/src/auth/types.ts +++ b/apps/x/packages/core/src/auth/types.ts @@ -9,7 +9,6 @@ export const OAuthTokens = z.object({ expires_at: z.number(), // Unix timestamp token_type: z.literal('Bearer').optional(), scopes: z.array(z.string()).optional(), // Granted scopes from OAuth response - id_token_sub: z.string().optional(), // Subject claim from ID token }); export type OAuthTokens = z.infer; diff --git a/apps/x/packages/shared/src/ipc.ts b/apps/x/packages/shared/src/ipc.ts index 4c83ef56..79746516 100644 --- a/apps/x/packages/shared/src/ipc.ts +++ b/apps/x/packages/shared/src/ipc.ts @@ -244,23 +244,6 @@ const ipcSchemas = { success: z.literal(true), }), }, - 'auth:getStatus': { - req: z.null(), - res: z.object({ - isAuthenticated: z.boolean(), - user: z.object({ - email: z.string(), - name: z.string().optional(), - }).nullable(), - }), - }, - 'auth:login': { - req: z.null(), - res: z.object({ - success: z.boolean(), - error: z.string().optional(), - }), - }, // Composio integration channels 'composio:is-configured': { req: z.null(), @@ -277,12 +260,6 @@ const ipcSchemas = { error: z.string().optional(), }), }, - 'auth:logout': { - req: z.null(), - res: z.object({ - success: z.boolean(), - }), - }, 'composio:initiate-connection': { req: z.object({ toolkitSlug: z.string(),