Skip to content

Add alternativeWebUrl param#956

Open
ibdafna wants to merge 3 commits into
coder:mainfrom
ibdafna:nflx/support-alternative-web-url
Open

Add alternativeWebUrl param#956
ibdafna wants to merge 3 commits into
coder:mainfrom
ibdafna:nflx/support-alternative-web-url

Conversation

@ibdafna
Copy link
Copy Markdown

@ibdafna ibdafna commented May 15, 2026

This PR adds a new parameter for specifying an alternative URL to use when opening Coder pages in the browser. When set, it replaces the connection URL for browser links only (dashboard, workspace pages, token authentication page). The connection URL is still used for API calls, SSH, and CLI operations. Useful when the Coder API runs on a port that browsers restrict (e.g., 7004) but the web UI is accessible on a standard port (e.g., 443)."

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e9e8beed19

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/webviews/chat/chatPanelProvider.ts Outdated
Comment on lines +159 to +162
const browserUrl = new URL(
resolved.pathname + resolved.search + resolved.hash,
browserBase,
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve path prefixes in chat navigation

When coder.alternativeWebUrl contains a path prefix (for example a reverse proxy at https://proxy.example.com/coder), constructing new URL(resolved.pathname..., browserBase) drops that prefix because the first argument starts with /; a chat navigation to /templates opens https://proxy.example.com/templates instead of https://proxy.example.com/coder/templates. Other browser links concatenate onto the resolved base and preserve such prefixes, so this makes chat links fail only for path-based alternative web URLs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed!

Comment on lines +365 to +367
await vscode.env.openExternal(
vscode.Uri.parse(`${resolveBrowserUrl(url)}/cli-auth`),
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply alternative URL to OAuth login

This only switches the legacy token page to coder.alternativeWebUrl; when coder.experimental.oauth is enabled and the user chooses OAuth, loginWithOAuth still goes through OAuthAuthorizer.startAuthorization, which opens the discovered authorization URL directly. In deployments where the connection URL uses a browser-restricted/unreachable port and the web UI is available via the alternative URL, OAuth login still opens the restricted connection URL and cannot complete, while token login works.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

We didn't touch the OAuth path deliberately, but I think the case highlighted here is worth addressing so we have consistency. Will submit a fix shortly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed!

@EhabY EhabY self-assigned this May 17, 2026
Copy link
Copy Markdown
Collaborator

@EhabY EhabY left a comment

Choose a reason for hiding this comment

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

Nice improvement. Make sure to run pnpm format and pnpm lint:fix/pnpm typecheck!

Comment thread src/util.ts
* `coder.alternativeWebUrl` setting when configured, otherwise returns
* the connection URL unchanged.
*/
export function resolveBrowserUrl(connectionUrl: string): string {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

resolveUiUrl makes more sense to me and fits the setting better coder.alternativeWebUrl

Comment thread src/oauth/authorizer.ts
Comment on lines +205 to +208
// The server-advertised endpoint is authoritative for the path, but the
// origin may be unreachable from a browser (e.g. blocked port). When
// `coder.alternativeWebUrl` is set, swap the origin so the user lands on
// a reachable host while preserving the path the server told us to use.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Agents tend to over explain, I think this can be trimmed or removed entirely

Comment on lines +159 to +161
// Concatenate rather than `new URL(path, base)` so a path prefix on
// the alternative URL (e.g. a reverse proxy at https://host/coder)
// is preserved.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

// Preserve the server-advertised path; only the origin/prefix may change.

Comment on lines +326 to 328
const baseUrl = resolveBrowserUrl(connUrl);
const task = await this.client.getTask("me", taskId);
vscode.env.openExternal(vscode.Uri.parse(getTaskBuildUrl(baseUrl, task)));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Instead of always doing resolveXUrl then vscode.open or vscode.env.openExternal, how about we just add openInBrowser(connectionUrl, path) helper

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We could even do it in a safer way like vscode.Uri.joinPath + Uri.with({ query, fragment }) so that we do not have to use some regex magic and concatenation

Comment on lines +91 to +92
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({
get: vi.fn(),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do use MockConfigurationProvider instead of mocking this directly (here and in tasksPanelProvider.test.ts

Comment thread package.json
"default": ""
},
"coder.alternativeWebUrl": {
"markdownDescription": "An alternative URL to use when opening Coder pages in the browser. When set, this replaces the connection URL for browser links only (dashboard, workspace pages, token authentication page, OAuth authorization page). The connection URL is still used for API calls, SSH, and CLI operations. Useful when the Coder API runs on a port that browsers restrict (e.g., 7004) but the web UI is accessible on a standard port (e.g., 443).",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do add something like "when empty, the connection URL is used for UI". I do think we need to be clear that this only affects calls like vscode.env.openExternal (without mentioning this here of course)

Comment thread src/oauth/authorizer.ts
Comment on lines +209 to 212
const endpoint = new URL(metadata.authorization_endpoint);
const browserBase = resolveBrowserUrl(endpoint.origin);
const url = `${browserBase}${endpoint.pathname}?${params.toString()}`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The old code was:

const url = `${metadata.authorization_endpoint}?${params.toString()}`;

The new code drops endpoint.search and passes endpoint.origin (not a full connection URL) into the helper, which is inconsistent with every other call site. Preserve the original shape and just swap the origin:

const endpoint = new URL(metadata.authorization_endpoint);
const browserOrigin = new URL(resolveUiUrl(coderApi.getHost())).origin;
endpoint.protocol = new URL(browserOrigin).protocol;
endpoint.host = new URL(browserOrigin).host;
for (const [key, value] of Object.entries(params)) {
    endpoint.searchParams.set(key, value);
}
const url = endpoint.toString();

Or, if the helper is as per the suggestion above, just call the open helper with the endpoint pathname and the params as the query. Either way, query strings already on authorization_endpoint are preserved and the helper receives a real connection URL

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.

2 participants