fix(auth): strip internal port from OAuth redirects on BYOD domains (#728)

* fix(auth): strip internal port from OAuth redirects on BYOD domains

When BYOD custom domains proxy auth requests through the dashboard to
PRIVATE_SERVER_URL (e.g. http://cio-api-vymh:10000), Node's URL host setter
preserves the upstream port. Better Auth then redirects browsers to
https://customer.example:10000/... which fails.

- Add oauth-proxy-redirect helpers to clear backend ports explicitly
- Rewrite inbound auth request URLs and outbound oauth-proxy-callback Location headers
- Send hostname-only X-Forwarded-Host from dashboard proxy when port matches backend

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(auth): address Greptile review on oauth port stripping

- Only treat PRIVATE_SERVER_URL as an internal backend port
- Add test for rewriteAuthRequestUrl when forwarded host still has :10000

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
This commit is contained in:
Rotimi Best
2026-07-11 06:17:26 +01:00
committed by GitHub
co-authored by Rotimi Best Cursor Agent
parent 3eb5e1268d
commit 2fb6b1c477
4 changed files with 255 additions and 14 deletions
@@ -34,8 +34,27 @@ function resolveApiUpstreamBase(): string | null {
return base.replace(/\/$/, '');
}
function resolveOriginalHost(request: Request, url: URL): string {
return request.headers.get('x-forwarded-host') ?? request.headers.get('host') ?? url.host;
function resolveBrowserForwardedHost(request: Request, url: URL): string {
const raw = request.headers.get('x-forwarded-host') ?? request.headers.get('host') ?? url.hostname;
const [hostname, port = ''] = raw.includes(':') ? raw.split(':', 2) : [raw, ''];
if (!port) {
return hostname;
}
const privateServerUrl = process.env.PRIVATE_SERVER_URL;
if (privateServerUrl) {
try {
const backendPort = new URL(privateServerUrl).port;
if (backendPort && port === backendPort) {
return hostname;
}
} catch {
// Ignore malformed PRIVATE_SERVER_URL.
}
}
return raw;
}
export async function proxyRequestToApi(request: Request): Promise<Response> {
@@ -49,7 +68,7 @@ export async function proxyRequestToApi(request: Request): Promise<Response> {
const upstreamPath = resolveApiUpstreamPath(url.pathname);
const upstreamUrl = new URL(`${upstreamPath}${url.search}`, apiBase);
const originalHost = resolveOriginalHost(request, url);
const originalHost = resolveBrowserForwardedHost(request, url);
const upstreamHeaders = new Headers(request.headers);
upstreamHeaders.set('host', upstreamUrl.host);