Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions apps/api/src/app/api/auth/github/route.ts
Original file line number Diff line number Diff line change
@@ -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());
}
33 changes: 33 additions & 0 deletions apps/api/src/app/api/auth/google/route.ts
Original file line number Diff line number Diff line change
@@ -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());
}
5 changes: 0 additions & 5 deletions apps/desktop/electron.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
4 changes: 0 additions & 4 deletions apps/desktop/src/main/env.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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,

Expand Down
36 changes: 8 additions & 28 deletions apps/desktop/src/main/lib/auth/auth-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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());
Expand Down
Loading