Revert "feat: integrate Supabase OAuth with OIDC discovery for authentication"

This reverts commit bbe82c124d.
This commit is contained in:
Ramnique Singh 2026-02-03 18:40:15 +05:30
parent d7b84f87d0
commit 9747c55d0e
10 changed files with 8 additions and 358 deletions

View file

@ -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<void> {
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();

View file

@ -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<string, {
codeVerifier: string;
@ -131,64 +128,6 @@ async function getProviderConfiguration(provider: string): Promise<Configuration
}
}
/**
* Get authentication status for the rowboat provider
*/
export async function getAuthStatus(): Promise<{ isAuthenticated: boolean; user: { email: string; name?: string } | null }> {
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) {

View file

@ -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<typeof workspace.DirEntry>
type RunEventType = z.infer<typeof RunEvent>
@ -443,24 +441,6 @@ function ChatInputWithMentions({
}
function App() {
const auth = useRowboatAuth()
if (auth.isLoading) {
return (
<div className="flex h-svh w-full items-center justify-center bg-background">
<LoaderIcon className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
)
}
if (!auth.isAuthenticated) {
return <LoginScreen isLoggingIn={auth.isLoggingIn} error={auth.error} login={auth.login} />
}
return <AppContent auth={auth} />
}
function AppContent({ auth }: { auth: ReturnType<typeof useRowboatAuth> }) {
// File browser state (for Knowledge section)
const [selectedPath, setSelectedPath] = useState<string | null>(null)
const [fileHistoryBack, setFileHistoryBack] = useState<string[]>([])
@ -1712,7 +1692,7 @@ function AppContent({ auth }: { auth: ReturnType<typeof useRowboatAuth> }) {
<SidebarSectionProvider defaultSection="tasks" onSectionChange={handleSectionChange}>
<div className="flex h-svh w-full">
{/* Icon sidebar - always visible, fixed position */}
<SidebarIcon user={auth.user} onLogout={auth.logout} />
<SidebarIcon />
{/* Spacer for the fixed icon sidebar */}
<div className="w-14 shrink-0" />

View file

@ -1,51 +0,0 @@
import { Button } from './ui/button';
import { LoaderIcon } from 'lucide-react';
interface LoginScreenProps {
isLoggingIn: boolean;
error: string | null;
login: () => Promise<void>;
}
export function LoginScreen({ isLoggingIn, error, login }: LoginScreenProps) {
return (
<div className="flex h-svh w-full items-center justify-center bg-background">
<div className="flex flex-col items-center gap-6 max-w-sm text-center px-4">
<div className="text-4xl font-semibold tracking-tight text-foreground/80">
Rowboat
</div>
<p className="text-sm text-muted-foreground">
Sign in to your Rowboat account to continue.
</p>
{error && (
<div className="w-full rounded-md bg-destructive/10 border border-destructive/20 px-4 py-3 text-sm text-destructive">
{error}
</div>
)}
<Button
onClick={login}
disabled={isLoggingIn}
className="w-full"
size="lg"
>
{isLoggingIn ? (
<>
<LoaderIcon className="h-4 w-4 animate-spin mr-2" />
Waiting for browser...
</>
) : (
'Sign in to Rowboat'
)}
</Button>
{isLoggingIn && (
<p className="text-xs text-muted-foreground">
Complete sign-in in your browser, then return here.
</p>
)}
</div>
</div>
);
}

View file

@ -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 = {}) {
<HelpCircle className="size-5" />
</button>
</HelpPopover>
{/* Sign out */}
{onLogout && (
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={onLogout}
className="flex h-10 w-10 items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground transition-colors"
>
<LogOut className="size-5" />
</button>
</TooltipTrigger>
<TooltipContent side="right" sideOffset={8}>
{user?.email ? `Sign out (${user.email})` : 'Sign out'}
</TooltipContent>
</Tooltip>
)}
</nav>
</div>
)

View file

@ -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<void>;
logout: () => Promise<void>;
}
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<string | null>(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,
};
}