diff --git a/apps/api/src/app/api/auth/github/route.ts b/apps/api/src/app/api/auth/github/route.ts new file mode 100644 index 000000000..d7a21004f --- /dev/null +++ b/apps/api/src/app/api/auth/github/route.ts @@ -0,0 +1,30 @@ +import { redirect } from "next/navigation"; +import { env } from "@/env"; + +/** + * Initiate GitHub OAuth flow for desktop app + * + * GET /api/auth/github?state=xxx + * + * This endpoint redirects to GitHub's OAuth page with the client_id, + * keeping credentials server-side only. + */ +export async function GET(request: Request) { + const url = new URL(request.url); + const state = url.searchParams.get("state"); + + if (!state) { + return Response.json({ error: "Missing state parameter" }, { status: 400 }); + } + + const authUrl = new URL("https://github.com/login/oauth/authorize"); + authUrl.searchParams.set("client_id", env.GH_CLIENT_ID); + authUrl.searchParams.set( + "redirect_uri", + `${env.NEXT_PUBLIC_WEB_URL}/api/auth/desktop/github`, + ); + authUrl.searchParams.set("scope", "user:email"); + authUrl.searchParams.set("state", state); + + redirect(authUrl.toString()); +} diff --git a/apps/api/src/app/api/auth/google/route.ts b/apps/api/src/app/api/auth/google/route.ts new file mode 100644 index 000000000..ca32f36ac --- /dev/null +++ b/apps/api/src/app/api/auth/google/route.ts @@ -0,0 +1,33 @@ +import { redirect } from "next/navigation"; +import { env } from "@/env"; + +/** + * Initiate Google OAuth flow for desktop app + * + * GET /api/auth/google?state=xxx + * + * This endpoint redirects to Google's OAuth page with the client_id, + * keeping credentials server-side only. + */ +export async function GET(request: Request) { + const url = new URL(request.url); + const state = url.searchParams.get("state"); + + if (!state) { + return Response.json({ error: "Missing state parameter" }, { status: 400 }); + } + + const authUrl = new URL("https://accounts.google.com/o/oauth2/v2/auth"); + authUrl.searchParams.set("client_id", env.GOOGLE_CLIENT_ID); + authUrl.searchParams.set( + "redirect_uri", + `${env.NEXT_PUBLIC_WEB_URL}/api/auth/desktop/google`, + ); + authUrl.searchParams.set("response_type", "code"); + authUrl.searchParams.set("scope", "openid email profile"); + authUrl.searchParams.set("state", state); + authUrl.searchParams.set("prompt", "select_account"); + authUrl.searchParams.set("access_type", "online"); + + redirect(authUrl.toString()); +} diff --git a/apps/desktop/electron.vite.config.ts b/apps/desktop/electron.vite.config.ts index ee3ba2b30..0ae322ed5 100644 --- a/apps/desktop/electron.vite.config.ts +++ b/apps/desktop/electron.vite.config.ts @@ -65,11 +65,6 @@ export default defineConfig({ "process.env.NEXT_PUBLIC_WEB_URL": JSON.stringify( process.env.NEXT_PUBLIC_WEB_URL || "https://app.superset.sh", ), - // OAuth client IDs - baked in at build time for main process - "process.env.GOOGLE_CLIENT_ID": JSON.stringify( - process.env.GOOGLE_CLIENT_ID, - ), - "process.env.GH_CLIENT_ID": JSON.stringify(process.env.GH_CLIENT_ID), }, build: { diff --git a/apps/desktop/src/main/env.main.ts b/apps/desktop/src/main/env.main.ts index 6f8bc42dd..bbb8b2c3a 100644 --- a/apps/desktop/src/main/env.main.ts +++ b/apps/desktop/src/main/env.main.ts @@ -16,8 +16,6 @@ export const env = createEnv({ .default("development"), NEXT_PUBLIC_API_URL: z.url().default("https://api.superset.sh"), NEXT_PUBLIC_WEB_URL: z.url().default("https://app.superset.sh"), - GOOGLE_CLIENT_ID: z.string().min(1), - GH_CLIENT_ID: z.string().min(1), }, runtimeEnv: { @@ -27,8 +25,6 @@ export const env = createEnv({ NODE_ENV: process.env.NODE_ENV, NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, NEXT_PUBLIC_WEB_URL: process.env.NEXT_PUBLIC_WEB_URL, - GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, - GH_CLIENT_ID: process.env.GH_CLIENT_ID, }, emptyStringAsUndefined: true, diff --git a/apps/desktop/src/main/lib/auth/auth-service.ts b/apps/desktop/src/main/lib/auth/auth-service.ts index 558fb8e54..8779287dc 100644 --- a/apps/desktop/src/main/lib/auth/auth-service.ts +++ b/apps/desktop/src/main/lib/auth/auth-service.ts @@ -176,7 +176,8 @@ class AuthService extends EventEmitter { /** * Sign in with OAuth provider - * Opens system browser directly to provider's OAuth (bypasses Clerk UI) + * Opens system browser to API endpoint which redirects to provider's OAuth + * This keeps OAuth client_id server-side only */ async signIn( provider: AuthProvider, @@ -186,33 +187,12 @@ class AuthService extends EventEmitter { // Generate state for CSRF protection const state = generateState(); - let authUrl: URL; - - if (provider === "github") { - // Build GitHub OAuth URL - authUrl = new URL("https://github.com/login/oauth/authorize"); - authUrl.searchParams.set("client_id", env.GH_CLIENT_ID); - authUrl.searchParams.set( - "redirect_uri", - `${env.NEXT_PUBLIC_WEB_URL}/api/auth/desktop/github`, - ); - authUrl.searchParams.set("scope", "user:email"); - authUrl.searchParams.set("state", state); - } else { - // Build Google OAuth URL (default) - authUrl = new URL("https://accounts.google.com/o/oauth2/v2/auth"); - authUrl.searchParams.set("client_id", env.GOOGLE_CLIENT_ID); - authUrl.searchParams.set( - "redirect_uri", - `${env.NEXT_PUBLIC_WEB_URL}/api/auth/desktop/google`, - ); - authUrl.searchParams.set("response_type", "code"); - authUrl.searchParams.set("scope", "openid email profile"); - authUrl.searchParams.set("state", state); - // Force account selection every time - authUrl.searchParams.set("prompt", "select_account"); - authUrl.searchParams.set("access_type", "online"); - } + // Build URL to API endpoint that will redirect to OAuth provider + // The API handles adding client_id, keeping it server-side only + const authUrl = new URL( + `${env.NEXT_PUBLIC_API_URL}/api/auth/${provider}`, + ); + authUrl.searchParams.set("state", state); // Open OAuth flow in system browser await shell.openExternal(authUrl.toString());