From 58362c9c4646718e18f2a39d4e340c37a92c10d2 Mon Sep 17 00:00:00 2001 From: Shubham Naik Date: Wed, 5 Nov 2025 16:35:37 -0800 Subject: [PATCH] chore: proper auth handling (#65) Co-authored-by: Shubham Naik --- src/auth/oauth.ts | 34 ++++++++++++++++++++++++++++++++++ src/cli/App.tsx | 10 +++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/auth/oauth.ts b/src/auth/oauth.ts index 6bfb5c8..daa15b9 100644 --- a/src/auth/oauth.ts +++ b/src/auth/oauth.ts @@ -140,6 +140,7 @@ export async function refreshAccessToken( grant_type: "refresh_token", client_id: OAUTH_CONFIG.clientId, refresh_token: refreshToken, + refresh_token_mode: "new", }), }); @@ -154,6 +155,39 @@ export async function refreshAccessToken( } /** + * Revoke a refresh token (logout) + */ +export async function revokeToken(refreshToken: string): Promise { + try { + const response = await fetch( + `${OAUTH_CONFIG.authBaseUrl}/api/oauth/revoke`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + client_id: OAUTH_CONFIG.clientId, + token: refreshToken, + token_type_hint: "refresh_token", + }), + }, + ); + + // OAuth 2.0 revoke endpoint should return 200 even if token is already invalid + if (!response.ok) { + const error = (await response.json()) as OAuthError; + console.error( + `Warning: Failed to revoke token: ${error.error_description || error.error}`, + ); + // Don't throw - we still want to clear local credentials + } + } catch (error) { + console.error("Warning: Failed to revoke token:", error); + // Don't throw - we still want to clear local credentials + } +} + +/** + * Validate credentials by checking health endpoint * Validate credentials by checking an authenticated endpoint * Uses SDK's agents.list() which requires valid authentication */ diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 95b3969..c745ec0 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -669,7 +669,7 @@ export default function App({ kind: "command", id: cmdId, input: msg, - output: "Clearing credentials...", + output: "Logging out...", phase: "running", }); buffersRef.current.order.push(cmdId); @@ -680,6 +680,14 @@ export default function App({ try { const { settingsManager } = await import("../settings-manager"); const currentSettings = settingsManager.getSettings(); + + // Revoke refresh token on server if we have one + if (currentSettings.refreshToken) { + const { revokeToken } = await import("../auth/oauth"); + await revokeToken(currentSettings.refreshToken); + } + + // Clear local credentials const newEnv = { ...currentSettings.env }; delete newEnv.LETTA_API_KEY; // Note: LETTA_BASE_URL is intentionally NOT deleted from settings