-
Notifications
You must be signed in to change notification settings - Fork 339
Fix infinite sign-in redirect loop under base-path deploys #1692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| --- | ||
| "@agent-native/core": patch | ||
| --- | ||
|
|
||
| Fix the org app-switcher escaping to the official *.agent-native.com site on | ||
| custom deployments. `defaultOrgAppLinks` hardcoded each template's prod URL, so a | ||
| path-prefixed deployment (served at `<origin>/<app>/`, which bakes | ||
| `VITE_APP_BASE_PATH`) linked sibling apps to the first-party hosted site instead | ||
| of the current deployment. It now builds `<origin>/<app>/` links when the app is | ||
| path-prefixed, and keeps the prod URLs for the first-party subdomain layout. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| --- | ||
| "@agent-native/core": patch | ||
| --- | ||
|
|
||
| Allow a shared deployment OAuth client for authenticated users. OAuth *client* | ||
| credentials (`GOOGLE_CLIENT_ID/SECRET`, `GOOGLE_SIGN_IN_CLIENT_*`, | ||
| `GITHUB_CLIENT_*`) are deployment-wide identity, not per-tenant secrets — every | ||
| user authorizes the same app for their own account and gets their own tokens. | ||
| `resolveSecret` now lets these keys fall back to the deploy env even for | ||
| signed-in users in a hosted runtime (the same treatment Builder credential keys | ||
| get), so one deployment can offer a single "Sign in with Google" instead of | ||
| forcing every user to register their own Google Cloud OAuth app. A per-tenant | ||
| client (BYO upload) still wins — user/org/workspace scopes are resolved first. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| --- | ||
| "@agent-native/core": patch | ||
| --- | ||
|
|
||
| Fix an infinite sign-in redirect loop under base-path deploys. When the | ||
| authenticated app shell (wrapped in `RequireSession`) was served at the sign-in | ||
| path — e.g. `/<app>/_agent-native/sign-in` on a path-prefixed deploy — the gate | ||
| redirected to the sign-in page from the sign-in page, nesting and re-encoding the | ||
| current URL as a fresh `?return=` on every hop (`…sign-in?return=%252F…sign-in%253Freturn…`). | ||
| `RequireSession` now refuses to redirect when already on the sign-in entry point | ||
| (new exported `isOnSignInPage` helper), and `safeReturnPath` collapses any | ||
| `return` that resolves back to `…/_agent-native/sign-in` to `/`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,6 +108,33 @@ function isBuilderCredentialKey(key: string): boolean { | |
| return (BUILDER_CREDENTIAL_KEYS as readonly string[]).includes(key); | ||
| } | ||
|
|
||
| /** | ||
| * OAuth *client* credentials — the client_id / client_secret that identify the | ||
| * deployment's own OAuth app (Google / GitHub sign-in, Gmail / Calendar / Docs | ||
| * connect). Unlike a per-tenant API key, a shared OAuth client is deployment- | ||
| * wide identity, not a secret that belongs to one tenant: every user authorizes | ||
| * the SAME app for their OWN account and receives their OWN tokens (stored | ||
| * per-user in `oauth_tokens`), so a deploy-level value can never impersonate | ||
| * another tenant. A tenant that supplies its own client (the BYO upload path) | ||
| * still wins — user/org/workspace scopes are read first, above this env | ||
| * fallback. So these keys are allowed to fall back to the deploy env even for | ||
| * authenticated hosted users (the same treatment Builder credential keys get), | ||
| * which lets one deployment offer a single shared "Sign in with Google" instead | ||
| * of forcing every user to register their own Google Cloud OAuth app. | ||
| */ | ||
| const DEPLOYMENT_WIDE_OAUTH_CLIENT_KEYS = [ | ||
| "GOOGLE_CLIENT_ID", | ||
| "GOOGLE_CLIENT_SECRET", | ||
| "GOOGLE_SIGN_IN_CLIENT_ID", | ||
| "GOOGLE_SIGN_IN_CLIENT_SECRET", | ||
| "GITHUB_CLIENT_ID", | ||
| "GITHUB_CLIENT_SECRET", | ||
| ] as const; | ||
|
|
||
| function isDeploymentWideOAuthClientKey(key: string): boolean { | ||
| return (DEPLOYMENT_WIDE_OAUTH_CLIENT_KEYS as readonly string[]).includes(key); | ||
| } | ||
|
|
||
| function isHostedWorkspaceRuntime(): boolean { | ||
| const hasFusionPreview = Boolean( | ||
| process.env.FUSION_ENVIRONMENT || | ||
|
|
@@ -966,13 +993,15 @@ export async function resolveSecret(key: string): Promise<string | null> { | |
| // The deploy-level value would silently impersonate the actual key | ||
| // owner across every tenant. Local/single-tenant deployments keep the | ||
| // original env fallback for BYO-server workflows. | ||
| const envFallback = ( | ||
| isBuilderCredentialKey(key) | ||
| ? canUseBuilderDeployCredentialFallbackForRequest() | ||
| : canUseDeployCredentialFallbackForRequest() | ||
| ) | ||
| ? process.env[key] || null | ||
| : null; | ||
| const allowDeployFallback = isBuilderCredentialKey(key) | ||
| ? canUseBuilderDeployCredentialFallbackForRequest() | ||
| : isDeploymentWideOAuthClientKey(key) | ||
| ? // A shared OAuth client is deployment-wide identity, not a per-tenant | ||
| // secret — safe to use for authenticated users (BYO per-tenant client | ||
| // is still resolved first, above). See DEPLOYMENT_WIDE_OAUTH_CLIENT_KEYS. | ||
| true | ||
| : canUseDeployCredentialFallbackForRequest(); | ||
| const envFallback = allowDeployFallback ? process.env[key] || null : null; | ||
|
Comment on lines
+996
to
+1004
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 OAuth client env fallback can silently mix tenant and deploy credentials
Additional Info |
||
| if (traceLookup) { | ||
| console.log( | ||
| `[resolve-secret] key=${key} email=${email} orgId=${getRequestOrgId() ?? "(none)"} scope=${envFallback ? "env-fallback" : "none"} hit=${!!envFallback}`, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,17 @@ interface EnvKeyStatus { | |
| configured: boolean; | ||
| } | ||
|
|
||
| // Only the Google OAuth client keys decide whether Google is set up. The | ||
| // env-status endpoint returns every registered key (Slack, Resend, all the LLM | ||
| // providers, …); requiring all of them to be configured meant Google was never | ||
| // recognized as ready, so the setup wizard showed even with valid creds. | ||
| const GOOGLE_CRED_KEYS = ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"]; | ||
|
|
||
| function googleCredsConfigured(keys: EnvKeyStatus[]): boolean { | ||
| const googleKeys = keys.filter((k) => GOOGLE_CRED_KEYS.includes(k.key)); | ||
| return googleKeys.length > 0 && googleKeys.every((k) => k.configured); | ||
| } | ||
|
|
||
| const STEPS = [ | ||
| { | ||
| titleKey: "mail.googleConnect.enableGmailApi", | ||
|
|
@@ -185,8 +196,7 @@ export function GoogleConnectBanner({ | |
| if (res.ok) { | ||
| const data: EnvKeyStatus[] = await res.json(); | ||
| setEnvStatus(data); | ||
| const allConfigured = data.every((k) => k.configured); | ||
| if (allConfigured && data.length > 0) { | ||
| if (googleCredsConfigured(data)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Google setup UI still depends on unrelated env keys
Additional Info |
||
| setSaved(true); | ||
| setCurrentStep(STEPS.length - 1); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Loop guard drops base-path deployments onto site root
When
safeReturnPath()collapses a looping sign-in return to/, the/_agent-native/sign-inhandler later redirects to that raw path instead of remapping it togetAppBasePath() || "/". On base-path deploys, a successful sign-in can therefore land on the host root (or 404) instead of the mounted app.Additional Info