Repository navigation
fix: use public callbacks for remote Google OAuth with custom creds - #2787
Conversation
|
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. |
There was a problem hiding this comment.
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
resolveBrowserOAuthRedirectUrito 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]/authorizeroute and prefer returnedredirectUriinOAuthModal. - 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. |
| 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) { |
| 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); |
| // - 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( |
There was a problem hiding this comment.
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).
| 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; | ||
| } |
There was a problem hiding this comment.
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;
}| export function resolveBrowserOAuthRedirectUri( | ||
| providerName, | ||
| redirectUri, | ||
| env = process.env | ||
| ) { |
There was a problem hiding this comment.
Add explicit TypeScript type annotations to the exported resolveBrowserOAuthRedirectUri function to maintain type safety and consistency across the codebase.
| export function resolveBrowserOAuthRedirectUri( | |
| providerName, | |
| redirectUri, | |
| env = process.env | |
| ) { | |
| export function resolveBrowserOAuthRedirectUri( | |
| providerName: string, | |
| redirectUri: string, | |
| env: Record<string, string | undefined> = process.env | |
| ): string { |
| try { | ||
| const requested = new URL(redirectUri); | ||
| const isLocalhostRedirect = /^(localhost|127\.0\.0\.1)$/i.test(requested.hostname); | ||
| if (!isLocalhostRedirect) { | ||
| return redirectUri; | ||
| } | ||
| } catch { | ||
| return redirectUri; | ||
| } |
There was a problem hiding this comment.
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).
| 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; | |
| } |
13de1c4
into
cheaper-inference:release/v3.8.5
…gle-oauth-public-callback fix: use public callbacks for remote Google OAuth with custom creds
…gle-oauth-public-callback fix: use public callbacks for remote Google OAuth with custom creds
…gle-oauth-public-callback fix: use public callbacks for remote Google OAuth with custom creds
Summary
This fixes the remaining remote Google OAuth callback regression for
antigravityandgemini-cliwhen 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-codeshttp://localhost:<port>/callbackin 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
OAuthModalalways chooses a localhost callback for Google providers./api/oauth/[provider]/authorizecan already return aredirectUri, but the modal overwrites it with the client-side localhost value..env.example/ warning copy describe a supported custom-credentials remote flow that the runtime code never actually completes.What changed
resolveBrowserOAuthRedirectUri()insrc/lib/oauth/providers.tsNEXT_PUBLIC_BASE_URLorOMNIROUTE_PUBLIC_BASE_URLwhen custom Google OAuth credentials are configured/api/oauth/[provider]/authorizeOAuthModalrespect theredirectUrireturned by the authorize endpointNEXT_PUBLIC_BASE_URLOMNIROUTE_PUBLIC_BASE_URLCompatibility / behavior after this PR
/callbackURL as documentedValidation
docker build --target runner-base -t local/omniroute:v3.8.5-temp .Why this targets
release/v3.8.5This 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.