Skip to content

fix: use public callbacks for remote Google OAuth with custom creds - #2787

Merged
diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.5from
akarray:fix/remote-google-oauth-public-callback
May 27, 2026
Merged

diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.5from
akarray:fix/remote-google-oauth-public-callback

Conversation

@akarray

@akarray akarray commented May 27, 2026

Copy link
Copy Markdown
Contributor

Summary

This fixes the remaining remote Google OAuth callback regression for antigravity and gemini-cli when operators configure their own Google OAuth web app credentials.

The docs and UI already say that remote deployments can use NEXT_PUBLIC_BASE_URL / custom credentials for a fully remote callback flow, but the current implementation still hard-codes http://localhost:<port>/callback in the modal and then ignores the redirect URI returned by /api/oauth/[provider]/authorize.

In practice that leaves remote users stuck after Google consent because the popup never returns to the deployed OmniRoute host.

Related: #957

Root cause

  • OAuthModal always chooses a localhost callback for Google providers.
  • /api/oauth/[provider]/authorize can already return a redirectUri, but the modal overwrites it with the client-side localhost value.
  • The docs / .env.example / warning copy describe a supported custom-credentials remote flow that the runtime code never actually completes.

What changed

  • add resolveBrowserOAuthRedirectUri() in src/lib/oauth/providers.ts
    • keep localhost/manual-copy behavior for bundled Google credentials
    • switch localhost requests to NEXT_PUBLIC_BASE_URL or OMNIROUTE_PUBLIC_BASE_URL when custom Google OAuth credentials are configured
  • use that helper in /api/oauth/[provider]/authorize
  • make OAuthModal respect the redirectUri returned by the authorize endpoint
  • add regression coverage for:
    • Antigravity + NEXT_PUBLIC_BASE_URL
    • Gemini + OMNIROUTE_PUBLIC_BASE_URL
    • localhost fallback when no custom Google credentials are present

Compatibility / behavior after this PR

  • Default / bundled Google credentials: unchanged, still use the localhost/manual callback flow
  • Custom Google credentials on remote deployments: now use the public /callback URL as documented
  • Non-Google providers: unchanged

Validation

  • changed files are diagnostics-clean
  • built successfully with docker build --target runner-base -t local/omniroute:v3.8.5-temp .
  • manually validated on a remote deployment with custom Antigravity OAuth credentials and a public base URL; the callback now returns to the OmniRoute host instead of stalling on localhost

Why this targets release/v3.8.5

This regression was reproduced and fixed against release/v3.8.5, so the PR is intentionally based on that branch to keep the diff minimal and directly applicable to the version users are running.

@kilo-code-bot

kilo-code-bot Bot commented May 27, 2026

Copy link
Copy Markdown

Kilo Code Review could not run — your account is out of credits.

Add credits or switch to a free model to enable reviews on this change.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds server-side logic to normalize Google OAuth redirect URIs for remote deployments when custom Google credentials are configured, and updates the UI to prefer the server-provided redirect URI.

Changes:

  • Introduce resolveBrowserOAuthRedirectUri to switch Google OAuth redirects from localhost to a configured public base URL when custom credentials are present.
  • Use the new resolver in the /api/oauth/[provider]/authorize route and prefer returned redirectUri in OAuthModal.
  • Add unit tests covering redirect switching behavior for Antigravity and Gemini.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.

File Description
tests/unit/oauth-providers-config.test.ts Adds unit tests for the new redirect URI resolution behavior.
src/shared/components/OAuthModal.tsx Updates redirect URI handling and comments to align with new server-side behavior.
src/lib/oauth/providers.ts Adds redirect URI resolver for Google browser OAuth providers and related helpers.
src/app/api/oauth/[provider]/[action]/route.ts Applies redirect resolver during authorization to return the correct redirect URI to clients.

Comment on lines +53 to +70
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`;

const GOOGLE_BROWSER_PROVIDERS = new Set(["antigravity", "gemini-cli"]);

function normalizeBaseUrl(value) {
return trimmed.replace(/\/+$/, "");
}

function hasCustomGoogleOAuthCredentials(providerName, env = process.env) {
Comment on lines +40 to +44
export function resolveBrowserOAuthRedirectUri(
providerName,
redirectUri,
env = process.env
) {

try {
const requested = new URL(redirectUri);
const isLocalhostRedirect = /^(localhost|127\.0\.0\.1)$/i.test(requested.hostname);
Comment on lines +384 to +386
// - 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.
* 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(

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces dynamic resolution of browser OAuth redirect URIs for Google providers (such as Antigravity and Gemini) via a new helper function resolveBrowserOAuthRedirectUri. When custom Google credentials and a public base URL are configured, localhost redirects are upgraded to the public callback URL. The feedback suggests improving type safety by adding explicit TypeScript type annotations to the new helper functions and updating the localhost regular expression to support IPv6 loopback addresses ([::1] and ::1).

Comment on lines +15 to +31
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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure type safety and prevent potential runtime crashes, we should add explicit TypeScript type annotations to these helper functions and use optional chaining (env?.) when accessing environment variables. This protects against cases where env might be passed as null or undefined (e.g., in certain test configurations or lightweight runtimes).

function normalizeBaseUrl(value: unknown): string {
  const trimmed = typeof value === "string" ? value.trim() : "";
  if (!trimmed) return "";
  return trimmed.replace(/\/+$/, "");
}

function hasCustomGoogleOAuthCredentials(
  providerName: string,
  env: Record<string, string | undefined> = process.env
): boolean {
  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;
}

Comment on lines +40 to +44
export function resolveBrowserOAuthRedirectUri(
providerName,
redirectUri,
env = process.env
) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add explicit TypeScript type annotations to the exported resolveBrowserOAuthRedirectUri function to maintain type safety and consistency across the codebase.

Suggested change
export function resolveBrowserOAuthRedirectUri(
providerName,
redirectUri,
env = process.env
) {
export function resolveBrowserOAuthRedirectUri(
providerName: string,
redirectUri: string,
env: Record<string, string | undefined> = process.env
): string {

Comment on lines +60 to +68
try {
const requested = new URL(redirectUri);
const isLocalhostRedirect = /^(localhost|127\.0\.0\.1)$/i.test(requested.hostname);
if (!isLocalhostRedirect) {
return redirectUri;
}
} catch {
return redirectUri;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current localhost check only matches localhost and 127.0.0.1. However, modern operating systems and environments often resolve localhost to the IPv6 loopback address [::1] or ::1. If a user is running on IPv6 localhost, the redirect URI will not be recognized as a localhost redirect, and the helper will fail to resolve the public callback URL.

We should update the regular expression to also match IPv6 loopback addresses ([::1] and ::1).

Suggested change
try {
const requested = new URL(redirectUri);
const isLocalhostRedirect = /^(localhost|127\.0\.0\.1)$/i.test(requested.hostname);
if (!isLocalhostRedirect) {
return redirectUri;
}
} catch {
return redirectUri;
}
try {
const requested = new URL(redirectUri);
const isLocalhostRedirect = /^(localhost|127\.0\.0\.1|\[::1\]|::1)$/i.test(requested.hostname);
if (!isLocalhostRedirect) {
return redirectUri;
}
} catch {
return redirectUri;
}

@diegosouzapw
diegosouzapw merged commit 13de1c4 into cheaper-inference:release/v3.8.5 May 27, 2026
2 of 3 checks passed
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…gle-oauth-public-callback

fix: use public callbacks for remote Google OAuth with custom creds
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
…gle-oauth-public-callback

fix: use public callbacks for remote Google OAuth with custom creds
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…gle-oauth-public-callback

fix: use public callbacks for remote Google OAuth with custom creds
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants