mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-27 20:29:44 +02:00
Revert "feat: integrate Supabase OAuth with OIDC discovery for authentication"
This reverts commit bbe82c124d.
This commit is contained in:
parent
d7b84f87d0
commit
9747c55d0e
10 changed files with 8 additions and 358 deletions
|
|
@ -6,8 +6,6 @@ import {
|
||||||
isConnected,
|
isConnected,
|
||||||
getConnectedProviders,
|
getConnectedProviders,
|
||||||
listProviders,
|
listProviders,
|
||||||
getAuthStatus,
|
|
||||||
logoutRowboat,
|
|
||||||
} from './oauth-handler.js';
|
} from './oauth-handler.js';
|
||||||
import { watcher as watcherCore, workspace } from '@x/core';
|
import { watcher as watcherCore, workspace } from '@x/core';
|
||||||
import { workspace as workspaceShared } from '@x/shared';
|
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;
|
let runsWatcher: (() => void) | null = null;
|
||||||
export async function startRunsWatcher(): Promise<void> {
|
export async function startRunsWatcher(): Promise<void> {
|
||||||
if (runsWatcher) {
|
if (runsWatcher) {
|
||||||
|
|
@ -356,15 +345,6 @@ export function setupIpcHandlers() {
|
||||||
markOnboardingComplete();
|
markOnboardingComplete();
|
||||||
return { success: true };
|
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 integration handlers
|
||||||
'composio:is-configured': async () => {
|
'composio:is-configured': async () => {
|
||||||
return composioHandler.isConfigured();
|
return composioHandler.isConfigured();
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,10 @@ import { IOAuthRepo } from '@x/core/dist/auth/repo.js';
|
||||||
import { IClientRegistrationRepo } from '@x/core/dist/auth/client-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 triggerCalendarSync } from '@x/core/dist/knowledge/sync_calendar.js';
|
||||||
import { triggerSync as triggerFirefliesSync } from '@x/core/dist/knowledge/sync_fireflies.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';
|
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 })
|
// Store active OAuth flows (state -> { codeVerifier, provider, config })
|
||||||
const activeFlows = new Map<string, {
|
const activeFlows = new Map<string, {
|
||||||
codeVerifier: 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
|
* Initiate OAuth flow for a provider
|
||||||
*/
|
*/
|
||||||
|
|
@ -241,18 +180,13 @@ export async function connectProvider(provider: string): Promise<{ success: bool
|
||||||
|
|
||||||
// Exchange code for tokens
|
// Exchange code for tokens
|
||||||
console.log(`[OAuth] Exchanging authorization code for tokens (${provider})...`);
|
console.log(`[OAuth] Exchanging authorization code for tokens (${provider})...`);
|
||||||
const { tokens, sub } = await oauthClient.exchangeCodeForTokens(
|
const tokens = await oauthClient.exchangeCodeForTokens(
|
||||||
flow.config,
|
flow.config,
|
||||||
callbackUrl,
|
callbackUrl,
|
||||||
flow.codeVerifier,
|
flow.codeVerifier,
|
||||||
state
|
state
|
||||||
);
|
);
|
||||||
|
|
||||||
// Persist the subject claim for future userinfo fetches
|
|
||||||
if (sub) {
|
|
||||||
tokens.id_token_sub = sub;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save tokens
|
// Save tokens
|
||||||
console.log(`[OAuth] Token exchange successful for ${provider}`);
|
console.log(`[OAuth] Token exchange successful for ${provider}`);
|
||||||
await oauthRepo.saveTokens(provider, tokens);
|
await oauthRepo.saveTokens(provider, tokens);
|
||||||
|
|
@ -264,18 +198,6 @@ export async function connectProvider(provider: string): Promise<{ success: bool
|
||||||
triggerFirefliesSync();
|
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
|
// Emit success event to renderer
|
||||||
emitOAuthEvent({ provider, success: true });
|
emitOAuthEvent({ provider, success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,6 @@ import { Separator } from "@/components/ui/separator"
|
||||||
import { Toaster } from "@/components/ui/sonner"
|
import { Toaster } from "@/components/ui/sonner"
|
||||||
import { stripKnowledgePrefix, toKnowledgePath, wikiLabel } from '@/lib/wiki-links'
|
import { stripKnowledgePrefix, toKnowledgePath, wikiLabel } from '@/lib/wiki-links'
|
||||||
import { OnboardingModal } from '@/components/onboarding-modal'
|
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 DirEntry = z.infer<typeof workspace.DirEntry>
|
||||||
type RunEventType = z.infer<typeof RunEvent>
|
type RunEventType = z.infer<typeof RunEvent>
|
||||||
|
|
@ -443,24 +441,6 @@ function ChatInputWithMentions({
|
||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
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)
|
// File browser state (for Knowledge section)
|
||||||
const [selectedPath, setSelectedPath] = useState<string | null>(null)
|
const [selectedPath, setSelectedPath] = useState<string | null>(null)
|
||||||
const [fileHistoryBack, setFileHistoryBack] = useState<string[]>([])
|
const [fileHistoryBack, setFileHistoryBack] = useState<string[]>([])
|
||||||
|
|
@ -1712,7 +1692,7 @@ function AppContent({ auth }: { auth: ReturnType<typeof useRowboatAuth> }) {
|
||||||
<SidebarSectionProvider defaultSection="tasks" onSectionChange={handleSectionChange}>
|
<SidebarSectionProvider defaultSection="tasks" onSectionChange={handleSectionChange}>
|
||||||
<div className="flex h-svh w-full">
|
<div className="flex h-svh w-full">
|
||||||
{/* Icon sidebar - always visible, fixed position */}
|
{/* Icon sidebar - always visible, fixed position */}
|
||||||
<SidebarIcon user={auth.user} onLogout={auth.logout} />
|
<SidebarIcon />
|
||||||
|
|
||||||
{/* Spacer for the fixed icon sidebar */}
|
{/* Spacer for the fixed icon sidebar */}
|
||||||
<div className="w-14 shrink-0" />
|
<div className="w-14 shrink-0" />
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -4,7 +4,6 @@ import * as React from "react"
|
||||||
import {
|
import {
|
||||||
Brain,
|
Brain,
|
||||||
HelpCircle,
|
HelpCircle,
|
||||||
LogOut,
|
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
Plug,
|
Plug,
|
||||||
Settings,
|
Settings,
|
||||||
|
|
@ -32,12 +31,7 @@ const navItems: NavItem[] = [
|
||||||
{ id: "knowledge", title: "Knowledge", icon: Brain },
|
{ id: "knowledge", title: "Knowledge", icon: Brain },
|
||||||
]
|
]
|
||||||
|
|
||||||
interface SidebarIconProps {
|
export function SidebarIcon() {
|
||||||
user?: { email: string; name?: string } | null;
|
|
||||||
onLogout?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SidebarIcon({ user, onLogout }: SidebarIconProps = {}) {
|
|
||||||
const { activeSection, setActiveSection } = useSidebarSection()
|
const { activeSection, setActiveSection } = useSidebarSection()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -94,23 +88,6 @@ export function SidebarIcon({ user, onLogout }: SidebarIconProps = {}) {
|
||||||
<HelpCircle className="size-5" />
|
<HelpCircle className="size-5" />
|
||||||
</button>
|
</button>
|
||||||
</HelpPopover>
|
</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>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -159,16 +159,13 @@ export function buildAuthorizationUrl(
|
||||||
state: string;
|
state: string;
|
||||||
}
|
}
|
||||||
): URL {
|
): URL {
|
||||||
const url = client.buildAuthorizationUrl(config, {
|
return client.buildAuthorizationUrl(config, {
|
||||||
redirect_uri: params.redirectUri,
|
redirect_uri: params.redirectUri,
|
||||||
scope: params.scope,
|
scope: params.scope,
|
||||||
code_challenge: params.codeChallenge,
|
code_challenge: params.codeChallenge,
|
||||||
code_challenge_method: 'S256',
|
code_challenge_method: 'S256',
|
||||||
state: params.state,
|
state: params.state,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`[OAuth] Authorization URL: ${url}`);
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -179,7 +176,7 @@ export async function exchangeCodeForTokens(
|
||||||
callbackUrl: URL,
|
callbackUrl: URL,
|
||||||
codeVerifier: string,
|
codeVerifier: string,
|
||||||
expectedState: string
|
expectedState: string
|
||||||
): Promise<{ tokens: OAuthTokens; sub?: string }> {
|
): Promise<OAuthTokens> {
|
||||||
console.log(`[OAuth] Exchanging authorization code for tokens...`);
|
console.log(`[OAuth] Exchanging authorization code for tokens...`);
|
||||||
|
|
||||||
const response = await client.authorizationCodeGrant(config, callbackUrl, {
|
const response = await client.authorizationCodeGrant(config, callbackUrl, {
|
||||||
|
|
@ -187,27 +184,8 @@ export async function exchangeCodeForTokens(
|
||||||
expectedState,
|
expectedState,
|
||||||
});
|
});
|
||||||
|
|
||||||
const claims = response.claims();
|
|
||||||
console.log(`[OAuth] Token exchange successful`);
|
console.log(`[OAuth] Token exchange successful`);
|
||||||
return {
|
return toOAuthTokens(response);
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -77,22 +77,7 @@ const providerConfigs: ProviderConfig = {
|
||||||
'profile',
|
'profile',
|
||||||
'email',
|
'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',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ export const OAuthTokens = z.object({
|
||||||
expires_at: z.number(), // Unix timestamp
|
expires_at: z.number(), // Unix timestamp
|
||||||
token_type: z.literal('Bearer').optional(),
|
token_type: z.literal('Bearer').optional(),
|
||||||
scopes: z.array(z.string()).optional(), // Granted scopes from OAuth response
|
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<typeof OAuthTokens>;
|
export type OAuthTokens = z.infer<typeof OAuthTokens>;
|
||||||
|
|
|
||||||
|
|
@ -244,23 +244,6 @@ const ipcSchemas = {
|
||||||
success: z.literal(true),
|
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 integration channels
|
||||||
'composio:is-configured': {
|
'composio:is-configured': {
|
||||||
req: z.null(),
|
req: z.null(),
|
||||||
|
|
@ -277,12 +260,6 @@ const ipcSchemas = {
|
||||||
error: z.string().optional(),
|
error: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
'auth:logout': {
|
|
||||||
req: z.null(),
|
|
||||||
res: z.object({
|
|
||||||
success: z.boolean(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
'composio:initiate-connection': {
|
'composio:initiate-connection': {
|
||||||
req: z.object({
|
req: z.object({
|
||||||
toolkitSlug: z.string(),
|
toolkitSlug: z.string(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue