From f7cc65d91fca28e368f985db743bcba7f4de830a Mon Sep 17 00:00:00 2001 From: zhangjun Date: Sat, 11 Jul 2026 23:08:13 +0800 Subject: [PATCH] remove auth --- bun.lock | 10 +- src/main/index.ts | 234 ++------------------------------------- src/main/windows/main.ts | 152 +++++++------------------ src/renderer/App.tsx | 15 +-- 4 files changed, 57 insertions(+), 354 deletions(-) diff --git a/bun.lock b/bun.lock index f338c9b34..7c135f7a6 100644 --- a/bun.lock +++ b/bun.lock @@ -1,11 +1,12 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "21st-desktop", "dependencies": { "@ai-sdk/react": "^3.0.14", - "@anthropic-ai/claude-agent-sdk": "0.2.32", + "@anthropic-ai/claude-agent-sdk": "0.2.45", "@git-diff-view/react": "^0.0.35", "@git-diff-view/shiki": "^0.0.36", "@mcpc-tech/acp-ai-provider": "^0.2.4", @@ -42,7 +43,7 @@ "@xterm/addon-serialize": "^0.14.0", "@xterm/addon-web-links": "^0.12.0", "@xterm/addon-webgl": "^0.19.0", - "@zed-industries/codex-acp": "^0.9.3", + "@zed-industries/codex-acp": "0.9.3", "ai": "^6.0.14", "async-mutex": "^0.5.0", "better-sqlite3": "^12.6.2", @@ -90,6 +91,7 @@ "@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/utils": "^4.0.0", "@electron/rebuild": "^4.0.3", + "@tailwindcss/container-queries": "^0.1.1", "@types/better-sqlite3": "^7.6.13", "@types/diff": "^8.0.0", "@types/node": "^20.17.50", @@ -126,7 +128,7 @@ "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], - "@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.32", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-linuxmusl-arm64": "^0.33.5", "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-8AtsSx/M9jxd0ihS08eqa7VireTEuwQy0i1+6ZJX93LECT6Svlf47dPJiAm7JB+BhVMmwTfQeS6x1akIcCfvbQ=="], + "@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.45", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-linuxmusl-arm64": "^0.33.5", "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-AKH2hKoJNyjLf9ThAttKqbmCjUFg7qs/8+LR/UTVX20fCLn359YH9WrQc6dAiAfi8RYNA+mWwrNYCAq+Sdo5Ag=="], "@apm-js-collab/code-transformer": ["@apm-js-collab/code-transformer@0.8.2", "", {}, "sha512-YRjJjNq5KFSjDUoqu5pFUWrrsvGOxl6c3bu+uMFc9HNNptZ2rNU/TI2nLw4jnhQNtka972Ee2m3uqbvDQtPeCA=="], @@ -648,6 +650,8 @@ "@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="], + "@tailwindcss/container-queries": ["@tailwindcss/container-queries@0.1.1", "", { "peerDependencies": { "tailwindcss": ">=3.2.0" } }, "sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA=="], + "@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="], "@tanstack/query-core": ["@tanstack/query-core@5.90.19", "", {}, "sha512-GLW5sjPVIvH491VV1ufddnfldyVB+teCnpPIvweEfkpRx7CfUmUGhoh9cdcUKBh/KwVxk22aNEDxeTsvmyB/WA=="], diff --git a/src/main/index.ts b/src/main/index.ts index 57af873f0..086c601df 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,16 +1,14 @@ import * as Sentry from "@sentry/electron/main" -import { app, BrowserWindow, dialog, Menu, nativeImage, session } from "electron" +import { app, BrowserWindow, dialog, Menu, nativeImage } from "electron" import { existsSync, readFileSync, readlinkSync, unlinkSync } from "fs" import { createServer } from "http" import { join } from "path" -import { AuthManager, initAuthManager, getAuthManager as getAuthManagerFromModule } from "./auth-manager" + import { identify, initAnalytics, - setSubscriptionPlan, shutdown as shutdownAnalytics, trackAppOpened, - trackAuthCompleted, } from "./lib/analytics" import { checkForUpdates, @@ -28,6 +26,10 @@ import { } from "./lib/cli" import { cleanupGitWatchers } from "./lib/git/watcher" import { cancelAllPendingOAuth, handleMcpOAuthCallback } from "./lib/mcp-auth" +// Re-export auth manager for tRPC routers that may need it +// Note: auth manager is no longer auto-initialized since 21st.dev auth is removed. +// getAuthManager() returns null — routers should handle this gracefully. +export { getAuthManager } from "./auth-manager" import { getAllMcpConfigHandler, hasActiveClaudeSessions, abortAllClaudeSessions } from "./lib/trpc/routers/claude" import { getAllCodexMcpConfigHandler, hasActiveCodexStreams, abortAllCodexStreams } from "./lib/trpc/routers/codex" import { @@ -91,116 +93,15 @@ export function getAppUrl(): string { return process.env.ELECTRON_RENDERER_URL || "https://21st.dev/agents" } -// Auth manager singleton (use the one from auth-manager module) -let authManager: AuthManager - -export function getAuthManager(): AuthManager { - // First try to get from module, fallback to local variable for backwards compat - return getAuthManagerFromModule() || authManager -} - -// Handle auth code from deep link (exported for IPC handlers) -export async function handleAuthCode(code: string): Promise { - console.log("[Auth] Handling auth code:", code.slice(0, 8) + "...") - try { - const authData = await authManager.exchangeCode(code) - console.log("[Auth] Success for user:", authData.user.email) - - // Track successful authentication - trackAuthCompleted(authData.user.id, authData.user.email) - // Fetch and set subscription plan for analytics - try { - const planData = await authManager.fetchUserPlan() - if (planData) { - setSubscriptionPlan(planData.plan) - } - } catch (e) { - console.warn("[Auth] Failed to fetch user plan for analytics:", e) - } - - // Set desktop token cookie using persist:main partition - const ses = session.fromPartition("persist:main") - try { - // First remove any existing cookie to avoid HttpOnly conflict - await ses.cookies.remove(getBaseUrl(), "x-desktop-token") - await ses.cookies.set({ - url: getBaseUrl(), - name: "x-desktop-token", - value: authData.token, - expirationDate: Math.floor( - new Date(authData.expiresAt).getTime() / 1000, - ), - httpOnly: false, - secure: getBaseUrl().startsWith("https"), - sameSite: "lax" as const, - }) - console.log("[Auth] Desktop token cookie set") - } catch (cookieError) { - // Cookie setting is optional - auth data is already saved to disk - console.warn("[Auth] Cookie set failed (non-critical):", cookieError) - } - - // Notify all windows and reload them to show app - const windows = getAllWindows() - for (const win of windows) { - try { - if (win.isDestroyed()) continue - win.webContents.send("auth:success", authData.user) - - // Use stable window ID (main, window-2, etc.) instead of Electron's numeric ID - const stableId = windowManager.getStableId(win) - - if (process.env.ELECTRON_RENDERER_URL) { - // Pass window ID via query param for dev mode - const url = new URL(process.env.ELECTRON_RENDERER_URL) - url.searchParams.set("windowId", stableId) - win.loadURL(url.toString()) - } else { - // Pass window ID via hash for production - win.loadFile(join(__dirname, "../renderer/index.html"), { - hash: `windowId=${stableId}`, - }) - } - } catch (error) { - // Window may have been destroyed during iteration - console.warn("[Auth] Failed to reload window:", error) - } - } - // Focus the first window - windows[0]?.focus() - } catch (error) { - console.error("[Auth] Exchange failed:", error) - // Broadcast auth error to all windows (not just focused) - for (const win of getAllWindows()) { - try { - if (!win.isDestroyed()) { - win.webContents.send("auth:error", (error as Error).message) - } - } catch { - // Window destroyed during iteration - } - } - } -} - -// Handle deep link +// Handle deep link (only MCP OAuth now, no account auth) function handleDeepLink(url: string): void { console.log("[DeepLink] Received:", url) try { const parsed = new URL(url) - // Handle auth callback: twentyfirst-agents://auth?code=xxx - if (parsed.pathname === "/auth" || parsed.host === "auth") { - const code = parsed.searchParams.get("code") - if (code) { - handleAuthCode(code) - return - } - } - // Handle MCP OAuth callback: twentyfirst-agents://mcp-oauth?code=xxx&state=yyy if (parsed.pathname === "/mcp-oauth" || parsed.host === "mcp-oauth") { const code = parsed.searchParams.get("code") @@ -299,87 +200,7 @@ const server = createServer((req, res) => { return } - if (url.pathname === "/auth/callback") { - const code = url.searchParams.get("code") - console.log( - "[Auth Server] Received callback with code:", - code?.slice(0, 8) + "...", - ) - - if (code) { - // Handle the auth code - handleAuthCode(code) - - // Send success response and close the browser tab - res.writeHead(200, { "Content-Type": "text/html" }) - res.end(` - - - - - 1Code - Authentication - - - -
- -

Authentication successful

-

You can close this tab

-
- - -`) - } else { - res.writeHead(400, { "Content-Type": "text/plain" }) - res.end("Missing code parameter") - } - } else if (url.pathname === "/callback") { + if (url.pathname === "/callback") { // Handle MCP OAuth callback const code = url.searchParams.get("code") const state = url.searchParams.get("state") @@ -890,47 +711,12 @@ if (gotTheLock) { // Build initial menu buildMenu() - // Initialize auth manager (uses singleton from auth-manager module) - authManager = initAuthManager(!!process.env.ELECTRON_RENDERER_URL) - console.log("[App] Auth manager initialized") - - // Initialize analytics after auth manager so we can identify user + // Initialize analytics initAnalytics() - // If user already authenticated from previous session, identify them - if (authManager.isAuthenticated()) { - const user = authManager.getUser() - if (user) { - identify(user.id, { email: user.email }) - console.log("[Analytics] User identified from saved session:", user.id) - } - } - - // Track app opened (now with correct user ID if authenticated) + // Track app opened trackAppOpened() - // Set up callback to update cookie when token is refreshed - authManager.setOnTokenRefresh(async (authData) => { - console.log("[Auth] Token refreshed, updating cookie...") - const ses = session.fromPartition("persist:main") - try { - await ses.cookies.set({ - url: getBaseUrl(), - name: "x-desktop-token", - value: authData.token, - expirationDate: Math.floor( - new Date(authData.expiresAt).getTime() / 1000, - ), - httpOnly: false, - secure: getBaseUrl().startsWith("https"), - sameSite: "lax" as const, - }) - console.log("[Auth] Desktop token cookie updated after refresh") - } catch (err) { - console.error("[Auth] Failed to update cookie:", err) - } - }) - // Initialize database try { initDatabase() diff --git a/src/main/windows/main.ts b/src/main/windows/main.ts index 15dcdd137..ddca09776 100644 --- a/src/main/windows/main.ts +++ b/src/main/windows/main.ts @@ -14,7 +14,7 @@ import { join } from "path" import { readFileSync, existsSync, writeFileSync, mkdirSync } from "fs" import { createIPCHandler } from "trpc-electron/main" import { createAppRouter } from "../lib/trpc/routers" -import { getAuthManager, handleAuthCode, getBaseUrl } from "../index" +import { getAuthManager, getBaseUrl } from "../index" import { registerGitWatcherIPC } from "../lib/git/watcher" import { hasActiveClaudeSessions, abortAllClaudeSessions } from "../lib/trpc/routers/claude" import { hasActiveCodexStreams, abortAllCodexStreams } from "../lib/trpc/routers/codex" @@ -348,17 +348,20 @@ function registerIpcHandlers(): void { ipcMain.handle("auth:get-user", (event) => { if (!validateSender(event)) return null - return getAuthManager().getUser() + const am = getAuthManager() + return am?.getUser() ?? null }) ipcMain.handle("auth:is-authenticated", (event) => { if (!validateSender(event)) return false - return getAuthManager().isAuthenticated() + const am = getAuthManager() + return am?.isAuthenticated() ?? false }) ipcMain.handle("auth:logout", async (event) => { if (!validateSender(event)) return - getAuthManager().logout() + const am = getAuthManager() + am?.logout() // Clear cookie from persist:main partition const ses = session.fromPartition("persist:main") try { @@ -367,43 +370,25 @@ function registerIpcHandlers(): void { } catch (err) { console.error("[Auth] Failed to clear cookie:", err) } - // Show login page in all windows - for (const win of windowManager.getAll()) { - showLoginPageInWindow(win) - } }) ipcMain.handle("auth:start-flow", (event) => { - if (!validateSender(event)) return - const win = getWindowFromEvent(event) - getAuthManager().startAuthFlow(win) + // No-op — 21st.dev auth removed }) + // auth:submit-code — no-op (21st.dev auth removed) ipcMain.handle("auth:submit-code", async (event, code: string) => { if (!validateSender(event)) return - if (!code || typeof code !== "string") { - getWindowFromEvent(event)?.webContents.send( - "auth:error", - "Invalid authorization code", - ) - return - } - await handleAuthCode(code) }) ipcMain.handle("auth:update-user", async (event, updates: { name?: string }) => { - if (!validateSender(event)) return null - try { - return await getAuthManager().updateUser(updates) - } catch (error) { - console.error("[Auth] Failed to update user:", error) - throw error - } + // No-op — 21st.dev auth removed + return null }) ipcMain.handle("auth:get-token", async (event) => { - if (!validateSender(event)) return null - return getAuthManager().getValidToken() + // No-op — 21st.dev auth removed + return null }) // Signed fetch - proxies requests through main process (no CORS) @@ -421,19 +406,12 @@ function registerIpcHandlers(): void { } console.log("[SignedFetch] Sender validated OK") - const token = await getAuthManager().getValidToken() - console.log("[SignedFetch] Token:", token ? "present" : "missing", "URL:", url) - if (!token) { - return { ok: false, status: 401, data: null, error: "Not authenticated" } - } - try { const response = await fetch(url, { method: options?.method || "GET", body: options?.body, headers: { ...options?.headers, - "X-Desktop-Token": token, "Content-Type": "application/json", }, }) @@ -475,18 +453,12 @@ function registerIpcHandlers(): void { return { ok: false, status: 403, error: "Unauthorized sender" } } - const token = await getAuthManager().getValidToken() - if (!token) { - return { ok: false, status: 401, error: "Not authenticated" } - } - try { const response = await fetch(url, { method: options?.method || "POST", body: options?.body, headers: { ...options?.headers, - "X-Desktop-Token": token, "Content-Type": "application/json", }, }) @@ -541,32 +513,7 @@ function registerIpcHandlers(): void { registerThemeScannerIPC() } -/** - * Show login page in a specific window - */ -function showLoginPageInWindow(window: BrowserWindow): void { - console.log("[Main] Showing login page in window", window.id) - - // In dev mode, login.html is in src/renderer, not out/renderer - if (process.env.ELECTRON_RENDERER_URL) { - // Dev mode: load from source directory - const loginPath = join(app.getAppPath(), "src/renderer/login.html") - console.log("[Main] Loading login from:", loginPath) - window.loadFile(loginPath) - } else { - // Production: load from built output - window.loadFile(join(__dirname, "../renderer/login.html")) - } -} -/** - * Show login page in the focused window (or first window) - */ -export function showLoginPage(): void { - const win = windowManager.getFocused() || windowManager.getAll()[0] - if (!win) return - showLoginPageInWindow(win) -} // Singleton IPC handler (prevents duplicate handlers on macOS window recreation) let ipcHandler: ReturnType | null = null @@ -780,57 +727,36 @@ export function createWindow(options?: { chatId?: string; subChatId?: string }): // windowManager handles cleanup via 'closed' event listener }) - // Load the renderer - check auth first + // Load the main app directly (no auth gate) const devServerUrl = process.env.ELECTRON_RENDERER_URL - const authManager = getAuthManager() - - console.log("[Main] ========== AUTH CHECK ==========") - console.log("[Main] AuthManager exists:", !!authManager) - const isAuth = authManager.isAuthenticated() - console.log("[Main] isAuthenticated():", isAuth) - const user = authManager.getUser() - console.log("[Main] getUser():", user ? user.email : "null") - console.log("[Main] ================================") - - if (isAuth) { - console.log("[Main] ✓ User authenticated, loading app") - // Get stable window ID from manager (assigned during register) - // "main" for first window, "window-2", "window-3", etc. for additional windows - const windowId = windowManager.getStableId(window) - - // Build URL params including optional chatId/subChatId - const buildParams = (params: URLSearchParams) => { - params.set("windowId", windowId) - if (options?.chatId) params.set("chatId", options.chatId) - if (options?.subChatId) params.set("subChatId", options.subChatId) - } - if (devServerUrl) { - // Pass params via query for dev mode - const url = new URL(devServerUrl) - buildParams(url.searchParams) - window.loadURL(url.toString()) - // Only open devtools for first window in development - if (!app.isPackaged && windowId === "main") { - window.webContents.openDevTools() - } - } else { - // Pass params via hash for production (file:// URLs) - const hashParams = new URLSearchParams() - buildParams(hashParams) - window.loadFile(join(__dirname, "../renderer/index.html"), { - hash: hashParams.toString(), - }) + // Get stable window ID from manager (assigned during register) + // "main" for first window, "window-2", "window-3", etc. for additional windows + const windowId = windowManager.getStableId(window) + + // Build URL params including optional chatId/subChatId + const buildParams = (params: URLSearchParams) => { + params.set("windowId", windowId) + if (options?.chatId) params.set("chatId", options.chatId) + if (options?.subChatId) params.set("subChatId", options.subChatId) + } + + if (devServerUrl) { + // Pass params via query for dev mode + const url = new URL(devServerUrl) + buildParams(url.searchParams) + window.loadURL(url.toString()) + // Only open devtools for first window in development + if (!app.isPackaged && windowId === "main") { + window.webContents.openDevTools() } } else { - console.log("[Main] ✗ Not authenticated, showing login page") - // In dev mode, login.html is in src/renderer - if (devServerUrl) { - const loginPath = join(app.getAppPath(), "src/renderer/login.html") - window.loadFile(loginPath) - } else { - window.loadFile(join(__dirname, "../renderer/login.html")) - } + // Pass params via hash for production (file:// URLs) + const hashParams = new URLSearchParams() + buildParams(hashParams) + window.loadFile(join(__dirname, "../renderer/index.html"), { + hash: hashParams.toString(), + }) } // Log page load - traffic light visibility is managed by the renderer diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 72fa8d406..33de350ba 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -15,7 +15,7 @@ import { CodexOnboardingPage, SelectRepoPage, } from "./features/onboarding" -import { identify, initAnalytics, shutdown } from "./lib/analytics" +import { initAnalytics, shutdown } from "./lib/analytics" import { anthropicOnboardingCompletedAtom, apiKeyOnboardingCompletedAtom, @@ -182,19 +182,6 @@ export function App() { } syncOptOutStatus() - // Identify user if already authenticated - const identifyUser = async () => { - try { - const user = await window.desktopApi?.getUser() - if (user?.id) { - identify(user.id, { email: user.email, name: user.name }) - } - } catch (error) { - console.warn("[Analytics] Failed to identify user:", error) - } - } - identifyUser() - // Cleanup on unmount return () => { shutdown()