mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
fix: honor public callbacks for remote Google OAuth
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
exchangeTokens,
|
||||
requestDeviceCode,
|
||||
pollForToken,
|
||||
resolveBrowserOAuthRedirectUri,
|
||||
} from "@/lib/oauth/providers";
|
||||
import {
|
||||
createProviderConnection,
|
||||
@@ -80,7 +81,9 @@ export async function GET(
|
||||
const { searchParams } = new URL(request.url);
|
||||
|
||||
if (action === "authorize") {
|
||||
const redirectUri = searchParams.get("redirect_uri") || "http://localhost:8080/callback";
|
||||
const requestedRedirectUri =
|
||||
searchParams.get("redirect_uri") || "http://localhost:8080/callback";
|
||||
const redirectUri = resolveBrowserOAuthRedirectUri(provider, requestedRedirectUri);
|
||||
const authData = generateAuthData(provider, redirectUri);
|
||||
if (provider === "qoder" && !authData.authUrl) {
|
||||
return NextResponse.json({
|
||||
|
||||
@@ -10,6 +10,66 @@
|
||||
import { generatePKCE, generateState } from "./utils/pkce";
|
||||
import { PROVIDERS } from "./providers/index";
|
||||
|
||||
const GOOGLE_BROWSER_PROVIDERS = new Set(["antigravity", "gemini-cli"]);
|
||||
|
||||
function normalizeBaseUrl(value) {
|
||||
const trimmed = typeof value === "string" ? value.trim() : "";
|
||||
if (!trimmed) return "";
|
||||
return trimmed.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function hasCustomGoogleOAuthCredentials(providerName, env = process.env) {
|
||||
if (providerName === "antigravity") {
|
||||
return !!env.ANTIGRAVITY_OAUTH_CLIENT_ID?.trim();
|
||||
}
|
||||
|
||||
if (providerName === "gemini-cli") {
|
||||
return !!env.GEMINI_CLI_OAUTH_CLIENT_ID?.trim() || !!env.GEMINI_OAUTH_CLIENT_ID?.trim();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Google providers default to localhost redirects so the embedded public
|
||||
* credentials keep working on out-of-the-box local installs. When operators
|
||||
* provide their own Google OAuth client IDs for a remote deployment, prefer the
|
||||
* public callback URL documented in .env.example / docs/README so the popup can
|
||||
* navigate back to OmniRoute instead of stalling on localhost.
|
||||
*/
|
||||
export function resolveBrowserOAuthRedirectUri(
|
||||
providerName,
|
||||
redirectUri,
|
||||
env = process.env
|
||||
) {
|
||||
if (!GOOGLE_BROWSER_PROVIDERS.has(providerName)) {
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
if (!hasCustomGoogleOAuthCredentials(providerName, env)) {
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
const publicBaseUrl =
|
||||
normalizeBaseUrl(env.NEXT_PUBLIC_BASE_URL) || normalizeBaseUrl(env.OMNIROUTE_PUBLIC_BASE_URL);
|
||||
|
||||
if (!publicBaseUrl) {
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
try {
|
||||
const requested = new URL(redirectUri);
|
||||
const isLocalhostRedirect = /^(localhost|127\.0\.0\.1)$/i.test(requested.hostname);
|
||||
if (!isLocalhostRedirect) {
|
||||
return redirectUri;
|
||||
}
|
||||
} catch {
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
return `${publicBaseUrl}/callback`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get provider handler
|
||||
*/
|
||||
|
||||
@@ -381,9 +381,9 @@ export default function OAuthModal({
|
||||
// - Codex/OpenAI: always port 1455 (registered in OAuth app)
|
||||
// - Windsurf/Devin CLI (remote fallback): use localhost with OmniRoute port + /auth/callback
|
||||
// (on true localhost the callback server handles it; this is only reached on remote)
|
||||
// - Google OAuth providers (antigravity, gemini-cli): always localhost, regardless of
|
||||
// where OmniRoute is hosted — Google only accepts pre-registered localhost URIs with
|
||||
// the built-in credentials. Remote users must configure their own credentials.
|
||||
// - Google OAuth providers (antigravity, gemini-cli): default to localhost so the
|
||||
// bundled credentials keep working. The authorize route upgrades this to the public
|
||||
// callback when custom Google credentials + NEXT_PUBLIC_BASE_URL are configured.
|
||||
// - Other providers on remote: use actual origin (supports PUBLIC_URL env var)
|
||||
// - Localhost: use localhost:port
|
||||
let redirectUri: string;
|
||||
@@ -433,7 +433,7 @@ export default function OAuthModal({
|
||||
);
|
||||
}
|
||||
|
||||
setAuthData({ ...data, redirectUri });
|
||||
setAuthData({ ...data, redirectUri: data.redirectUri || redirectUri });
|
||||
|
||||
// For non-true-localhost (LAN IPs, remote) or manual fallback: use manual input mode (user pastes callback URL)
|
||||
if (!isTrueLocalhost || forceManual) {
|
||||
|
||||
@@ -18,8 +18,10 @@ const providersModule = await import("../../src/lib/oauth/providers/index.ts");
|
||||
const oauthModule = await import("../../src/lib/oauth/constants/oauth.ts");
|
||||
const registryModule = await import("../../open-sse/config/providerRegistry.ts");
|
||||
const antigravityHeadersModule = await import("../../open-sse/services/antigravityHeaders.ts");
|
||||
const oauthHelpersModule = await import("../../src/lib/oauth/providers.ts");
|
||||
|
||||
const PROVIDERS = providersModule.default;
|
||||
const { resolveBrowserOAuthRedirectUri } = oauthHelpersModule;
|
||||
const {
|
||||
ANTIGRAVITY_CONFIG,
|
||||
CLAUDE_CONFIG,
|
||||
@@ -316,6 +318,44 @@ test("browser-based providers expose buildAuthUrl and return provider-specific a
|
||||
assert.equal(clineUrl.origin, "https://api.cline.bot");
|
||||
});
|
||||
|
||||
test("custom Google OAuth credentials switch Antigravity remote callbacks to NEXT_PUBLIC_BASE_URL", () => {
|
||||
const redirectUri = resolveBrowserOAuthRedirectUri(
|
||||
"antigravity",
|
||||
"http://localhost:20128/callback",
|
||||
{
|
||||
NEXT_PUBLIC_BASE_URL: "https://omniroute.example.com/",
|
||||
ANTIGRAVITY_OAUTH_CLIENT_ID: "custom-antigravity.apps.googleusercontent.com",
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(redirectUri, "https://omniroute.example.com/callback");
|
||||
});
|
||||
|
||||
test("custom Google OAuth credentials switch Gemini remote callbacks to OMNIROUTE_PUBLIC_BASE_URL", () => {
|
||||
const redirectUri = resolveBrowserOAuthRedirectUri(
|
||||
"gemini-cli",
|
||||
"http://127.0.0.1:20128/callback",
|
||||
{
|
||||
OMNIROUTE_PUBLIC_BASE_URL: "https://omniroute.example.com",
|
||||
GEMINI_CLI_OAUTH_CLIENT_ID: "custom-gemini.apps.googleusercontent.com",
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(redirectUri, "https://omniroute.example.com/callback");
|
||||
});
|
||||
|
||||
test("Google OAuth callbacks stay on localhost when no custom credentials are configured", () => {
|
||||
const redirectUri = resolveBrowserOAuthRedirectUri(
|
||||
"antigravity",
|
||||
"http://localhost:20128/callback",
|
||||
{
|
||||
NEXT_PUBLIC_BASE_URL: "https://omniroute.example.com",
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(redirectUri, "http://localhost:20128/callback");
|
||||
});
|
||||
|
||||
test("device and import-token providers expose the flow-specific fields expected by their configs", () => {
|
||||
const deviceProviders = ["qwen", "kimi-coding", "github", "kiro", "amazon-q", "kilocode"];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user