mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-20 05:42:19 +03:00
fix(runtime): eliminate hardcoded 20128 port remnants and make loopback URLs dynamic (#13533)
* fix(runtime): eliminate hardcoded 20128 port remnants and make loopback URLs dynamic
- Make model assessment probe base URL resolve dynamically from getRuntimePorts() / env
- Support dynamic loopback in traffic inspector replay route and MITM handlers
- Make WebSocket live server allowlist dynamically include runtime PORT/DASHBOARD_PORT loopback origins
- Update CLI tools config/apply/letta-settings and tool-detector to adapt to configured runtime port
- Update client UI components (EndpointPageClient, ApiExplorerClient, RelayProxyClient) to use current window origin or dynamic port
- Make resolveOmniRouteBaseUrl, useDisplayBaseUrl, and wellKnown.ts respect configured port
- Update package.json electron:dev wait-on to use ${PORT:-20128}
- Add test coverage for custom port in resolveOmniRouteBaseUrl and liveServerAllowList
- Add changelog fragment for PR #13533
* fix(runtime): complete the truncated fallback comment in wellKnown.ts
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- **fix(runtime):** eliminate hardcoded 20128 port remnants and make loopback URLs dynamic ([#13533](https://github.com/diegosouzapw/OmniRoute/pull/13533)) — thanks @ggdayup
|
||||
@@ -122,7 +122,7 @@
|
||||
"lint:json": "node scripts/quality/run-eslint-json.mjs",
|
||||
"lint:md": "npx --yes markdownlint-cli2 \"docs/**/*.md\" \"*.md\" \"!docs/i18n\" \"!docs/research\"",
|
||||
"lint:prose": "vale docs",
|
||||
"electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:20128 && cd electron && npm run dev\"",
|
||||
"electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:${PORT:-20128} && cd electron && npm run dev\"",
|
||||
"electron:build": "npm run build && cd electron && npm run build",
|
||||
"electron:build:win": "npm run build && cd electron && npm run build:win",
|
||||
"electron:build:mac": "npm run build && cd electron && npm run build:mac",
|
||||
|
||||
@@ -168,7 +168,9 @@ export default function APIPageClient({ machineId }: Readonly<APIPageClientProps
|
||||
const [ngrokToken, setNgrokToken] = useState("");
|
||||
const [showNgrokTunnel, setShowNgrokTunnel] = useState(true);
|
||||
const [expandedTunnel, setExpandedTunnel] = useState<string | null>(null);
|
||||
const [localApiUrl, setLocalApiUrl] = useState("http://localhost:20128/v1");
|
||||
const [localApiUrl, setLocalApiUrl] = useState(
|
||||
typeof window !== "undefined" ? `${window.location.origin}/v1` : "http://localhost:20128/v1"
|
||||
);
|
||||
const [lanUrls, setLanUrls] = useState<string[]>([]);
|
||||
const [tailscaleIpUrl, setTailscaleIpUrl] = useState<string | null>(null);
|
||||
const [activeEndpointTab, setActiveEndpointTab] = useState<EndpointTab>("apis");
|
||||
|
||||
@@ -6,6 +6,7 @@ import Card from "@/shared/components/Card";
|
||||
import Badge from "@/shared/components/Badge";
|
||||
import Button from "@/shared/components/Button";
|
||||
import { useNotificationStore } from "@/store/notificationStore";
|
||||
import { useDisplayBaseUrl } from "@/shared/hooks";
|
||||
|
||||
interface RelayToken {
|
||||
id: string;
|
||||
@@ -23,6 +24,7 @@ interface RelayToken {
|
||||
|
||||
export default function RelayProxyClient() {
|
||||
const t = useTranslations("relay");
|
||||
const displayBaseUrl = useDisplayBaseUrl();
|
||||
const [tokens, setTokens] = useState<RelayToken[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showCreate, setShowCreate] = useState(false);
|
||||
@@ -193,7 +195,7 @@ export default function RelayProxyClient() {
|
||||
<h2 className="text-sm font-semibold">{t("usage")}</h2>
|
||||
<p className="text-xs text-text-muted">{t("usageDescription")}</p>
|
||||
<pre className="text-xs bg-surface/50 border border-border rounded-lg p-3 overflow-x-auto">
|
||||
{`curl http://localhost:20128/v1/relay/chat/completions \\
|
||||
{`curl ${displayBaseUrl}/v1/relay/chat/completions \\
|
||||
-H "Authorization: Bearer relay_..." \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{"model":"claude-sonnet-4","messages":[{"role":"user","content":"Hello"}]}'`}
|
||||
|
||||
@@ -41,7 +41,8 @@ export default async function AgentBridgePage() {
|
||||
try {
|
||||
const base =
|
||||
process.env.OMNIROUTE_BASE_URL ??
|
||||
`http://127.0.0.1:${process.env.PORT ?? 20128}`;
|
||||
process.env.BASE_URL ??
|
||||
`http://127.0.0.1:${process.env.DASHBOARD_PORT ?? process.env.PORT ?? 20128}`;
|
||||
const res = await fetch(`${base}/api/tools/agent-bridge/state`, {
|
||||
cache: "no-store",
|
||||
headers: { "x-internal-fetch": "1" },
|
||||
|
||||
@@ -11,9 +11,18 @@ import {
|
||||
import { validateBody } from "@/shared/validation/helpers";
|
||||
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
|
||||
|
||||
function getAssessBaseUrl(): string {
|
||||
return (
|
||||
process.env.OMNIROUTE_BASE_URL ??
|
||||
process.env.OMNIROUTe_BASE_URL ??
|
||||
process.env.BASE_URL ??
|
||||
`http://localhost:${process.env.API_PORT ?? process.env.PORT ?? 20128}/v1`
|
||||
);
|
||||
}
|
||||
|
||||
const assessor = new Assessor(
|
||||
process.env.OMNIROUTe_API_KEY ?? process.env.API_KEY ?? "",
|
||||
process.env.OMNIROUTe_BASE_URL ?? "http://localhost:20128/v1"
|
||||
process.env.OMNIROUTE_API_KEY ?? process.env.OMNIROUTe_API_KEY ?? process.env.API_KEY ?? "",
|
||||
getAssessBaseUrl()
|
||||
);
|
||||
|
||||
const categorizer = new Categorizer();
|
||||
@@ -142,9 +151,12 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
async function getAllModels(): Promise<Array<{ providerId: string; modelId: string }>> {
|
||||
try {
|
||||
const resp = await fetch("http://localhost:20128/v1/models", {
|
||||
const baseUrl = getAssessBaseUrl();
|
||||
const apiKey =
|
||||
process.env.OMNIROUTE_API_KEY ?? process.env.OMNIROUTe_API_KEY ?? process.env.API_KEY ?? "";
|
||||
const resp = await fetch(`${baseUrl}/models`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.OMNIROUTe_API_KEY ?? process.env.API_KEY ?? ""}`,
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
});
|
||||
const data = (await resp.json()) as { data?: unknown };
|
||||
|
||||
@@ -50,8 +50,14 @@ export async function POST(request: Request) {
|
||||
const { toolId, baseUrl, apiKey, model, dryRun } = parsed.data;
|
||||
const canonicalToolId = normalizeCliToolId(toolId);
|
||||
|
||||
const defaultPort = process.env.API_PORT || process.env.PORT || 20128;
|
||||
const defaultBaseUrl =
|
||||
process.env.OMNIROUTE_BASE_URL ||
|
||||
process.env.BASE_URL ||
|
||||
`http://localhost:${defaultPort}/v1`;
|
||||
|
||||
const result = await generateConfig(canonicalToolId, {
|
||||
baseUrl: baseUrl || "http://localhost:20128/v1",
|
||||
baseUrl: baseUrl || defaultBaseUrl,
|
||||
apiKey,
|
||||
model,
|
||||
});
|
||||
|
||||
@@ -16,7 +16,10 @@ export async function GET(request: Request) {
|
||||
if (authError) return authError;
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const baseUrl = searchParams.get("baseUrl") || "http://localhost:20128/v1";
|
||||
const defaultPort = process.env.API_PORT || process.env.PORT || 20128;
|
||||
const defaultBaseUrl =
|
||||
process.env.OMNIROUTE_BASE_URL || process.env.BASE_URL || `http://localhost:${defaultPort}/v1`;
|
||||
const baseUrl = searchParams.get("baseUrl") || defaultBaseUrl;
|
||||
const apiKey = searchParams.get("apiKey") || "";
|
||||
|
||||
if (!apiKey) {
|
||||
@@ -46,9 +49,14 @@ export async function POST(request: Request) {
|
||||
);
|
||||
}
|
||||
const { toolId, baseUrl, apiKey, model } = parsed.data;
|
||||
const defaultPort = process.env.API_PORT || process.env.PORT || 20128;
|
||||
const defaultBaseUrl =
|
||||
process.env.OMNIROUTE_BASE_URL ||
|
||||
process.env.BASE_URL ||
|
||||
`http://localhost:${defaultPort}/v1`;
|
||||
|
||||
const result = await generateConfig(toolId, {
|
||||
baseUrl: baseUrl || "http://localhost:20128/v1",
|
||||
baseUrl: baseUrl || defaultBaseUrl,
|
||||
apiKey,
|
||||
model,
|
||||
});
|
||||
|
||||
@@ -74,7 +74,13 @@ const readAuthFile = async () => {
|
||||
// ── Check if a base_url points to OmniRoute ──────────────────────────────
|
||||
const isOmniRouteUrl = (baseUrl) => {
|
||||
if (!baseUrl) return false;
|
||||
return baseUrl.includes(":20128") || baseUrl.includes(":3000") || baseUrl.includes("omniroute");
|
||||
const port = process.env.PORT || process.env.DASHBOARD_PORT;
|
||||
return (
|
||||
baseUrl.includes(":20128") ||
|
||||
baseUrl.includes(":3000") ||
|
||||
(!!port && baseUrl.includes(`:${port}`)) ||
|
||||
baseUrl.includes("omniroute")
|
||||
);
|
||||
};
|
||||
|
||||
// ── Check if OmniRoute is configured ─────────────────────────────────────
|
||||
@@ -122,10 +128,7 @@ export async function GET(request: Request) {
|
||||
backendMode: settings.preferredBackendMode || "api",
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: { message: sanitizeErrorMessage(error) } },
|
||||
{ status: 500 }
|
||||
);
|
||||
return NextResponse.json({ error: { message: sanitizeErrorMessage(error) } }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,10 +249,7 @@ export async function POST(request: Request) {
|
||||
needsRestart: true,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: { message: sanitizeErrorMessage(error) } },
|
||||
{ status: 500 }
|
||||
);
|
||||
return NextResponse.json({ error: { message: sanitizeErrorMessage(error) } }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,9 +321,6 @@ export async function DELETE(request: Request) {
|
||||
needsRestart: true,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: { message: sanitizeErrorMessage(error) } },
|
||||
{ status: 500 }
|
||||
);
|
||||
return NextResponse.json({ error: { message: sanitizeErrorMessage(error) } }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ export async function POST(request: Request): Promise<Response> {
|
||||
const chatBody = buildImproveChatBody(body);
|
||||
|
||||
// 5. Call /v1/chat/completions on ourselves (D8)
|
||||
const port = process.env.PORT ?? "20128";
|
||||
const port = process.env.API_PORT ?? process.env.PORT ?? "20128";
|
||||
const baseUrl = process.env.OMNIROUTE_BASE_URL ?? `http://127.0.0.1:${port}`;
|
||||
const upstreamUrl = `${baseUrl}/v1/chat/completions`;
|
||||
|
||||
|
||||
@@ -230,7 +230,8 @@ async function handleDisable(machineId: string, request: any) {
|
||||
}
|
||||
|
||||
// Update Claude CLI settings to use local endpoint
|
||||
const host = request.headers.get("host") || "localhost:20128";
|
||||
const defaultPort = process.env.PORT || process.env.DASHBOARD_PORT || "20128";
|
||||
const host = request.headers.get("host") || `localhost:${defaultPort}`;
|
||||
await updateClaudeSettingsToLocal(machineId, host);
|
||||
|
||||
return NextResponse.json({
|
||||
|
||||
@@ -15,7 +15,14 @@ interface Params {
|
||||
params: Promise<{ id: string }>;
|
||||
}
|
||||
|
||||
const OMNIROUTE_BASE = process.env.OMNIROUTE_BASE_URL ?? "http://127.0.0.1:20128";
|
||||
function getOmnirouteBaseUrl(): string {
|
||||
const port = process.env.API_PORT || process.env.PORT || 20128;
|
||||
return (
|
||||
process.env.OMNIROUTE_BASE_URL ||
|
||||
process.env.BASE_URL ||
|
||||
`http://127.0.0.1:${port}`
|
||||
).replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
export async function POST(_request: Request, { params }: Params): Promise<Response> {
|
||||
const { id } = await params;
|
||||
@@ -27,7 +34,7 @@ export async function POST(_request: Request, { params }: Params): Promise<Respo
|
||||
});
|
||||
}
|
||||
|
||||
const url = `${OMNIROUTE_BASE}${entry.path}`;
|
||||
const url = `${getOmnirouteBaseUrl()}${entry.path}`;
|
||||
|
||||
const replayHeaders: Record<string, string> = {
|
||||
"content-type": "application/json",
|
||||
|
||||
@@ -92,7 +92,9 @@ export function ApiExplorerClient() {
|
||||
const t = useTranslations("docs");
|
||||
const te = useTranslations("endpoint");
|
||||
const [selected, setSelected] = useState<OpenApiEndpoint | null>(null);
|
||||
const [baseUrl, setBaseUrl] = useState("http://localhost:20128");
|
||||
const [baseUrl, setBaseUrl] = useState(
|
||||
typeof window !== "undefined" ? window.location.origin : "http://localhost:20128"
|
||||
);
|
||||
const [apiKey, setApiKey] = useState("");
|
||||
const [requestBody, setRequestBody] = useState("");
|
||||
const [response, setResponse] = useState<string | null>(null);
|
||||
|
||||
@@ -34,7 +34,9 @@ export class Assessor {
|
||||
|
||||
constructor(
|
||||
apiKey: string,
|
||||
baseUrl: string = "http://localhost:20128/v1",
|
||||
baseUrl: string = process.env.OMNIROUTE_BASE_URL ??
|
||||
process.env.BASE_URL ??
|
||||
`http://localhost:${process.env.API_PORT ?? process.env.PORT ?? 20128}/v1`,
|
||||
config: Partial<AssessmentConfig> = {}
|
||||
) {
|
||||
this.apiKey = apiKey;
|
||||
|
||||
@@ -12,7 +12,12 @@ export interface LogStream {
|
||||
}
|
||||
|
||||
export function createLogStream(options: LogStreamOptions = {}): LogStream {
|
||||
const baseUrl = options.baseUrl || "http://localhost:20128";
|
||||
const port = process.env.PORT || process.env.DASHBOARD_PORT || 20128;
|
||||
const baseUrl =
|
||||
options.baseUrl ||
|
||||
process.env.OMNIROUTE_BASE_URL ||
|
||||
process.env.BASE_URL ||
|
||||
`http://localhost:${port}`;
|
||||
const filters = options.filters || [];
|
||||
const follow = options.follow ?? false;
|
||||
const timeout = options.timeout || 30000;
|
||||
|
||||
@@ -77,9 +77,11 @@ function expandHome(p: string): string {
|
||||
|
||||
function isConfigured(content: string, baseUrl: string): boolean {
|
||||
const normalized = baseUrl.replace(/\/+$/, "");
|
||||
const port = process.env.PORT || process.env.DASHBOARD_PORT;
|
||||
return (
|
||||
content.includes(normalized) ||
|
||||
content.includes("localhost:20128") ||
|
||||
(!!port && content.includes(`localhost:${port}`)) ||
|
||||
content.includes("OMNIROUTE_BASE_URL")
|
||||
);
|
||||
}
|
||||
@@ -170,7 +172,9 @@ export async function detectTool(id: string): Promise<DetectedTool | null> {
|
||||
: getCliPrimaryConfigPath(tool.id) ||
|
||||
(tool.id === "opencode" ? resolveOpencodeConfigPath() : "");
|
||||
const configContents = await readConfigFile(configPath);
|
||||
const configured = !!configContents && isConfigured(configContents, "http://localhost:20128");
|
||||
const defaultPort = process.env.PORT || process.env.DASHBOARD_PORT || 20128;
|
||||
const configured =
|
||||
!!configContents && isConfigured(configContents, `http://localhost:${defaultPort}`);
|
||||
|
||||
const result: DetectedTool = {
|
||||
id: canonicalId,
|
||||
@@ -187,12 +191,14 @@ export async function detectTool(id: string): Promise<DetectedTool | null> {
|
||||
try {
|
||||
const roles = await getCurrentHermesAgentRoles();
|
||||
const richRoles: Record<string, any> = {};
|
||||
const currentPort = String(process.env.PORT || process.env.DASHBOARD_PORT || 20128);
|
||||
|
||||
Object.entries(roles).forEach(([role, info]) => {
|
||||
const usingOmni =
|
||||
info?.provider === "omniroute" ||
|
||||
(info?.base_url || "").includes("20128") ||
|
||||
(info?.base_url || "").includes("localhost:20128");
|
||||
(info?.base_url || "").includes(currentPort) ||
|
||||
(info?.base_url || "").includes("localhost");
|
||||
|
||||
richRoles[role] = {
|
||||
model: info.model,
|
||||
|
||||
@@ -10,5 +10,6 @@ export function getBaseUrl(request?: NextRequest | null): string {
|
||||
if (process.env.OMNIROUTE_BASE_URL) return process.env.OMNIROUTE_BASE_URL;
|
||||
// Direct route-handler invocation (unit tests, programmatic calls) passes no
|
||||
// Request — fall back to the default local gateway origin instead of crashing.
|
||||
return request?.nextUrl?.origin ?? "http://localhost:20128";
|
||||
const defaultPort = process.env.PORT || process.env.DASHBOARD_PORT || 20128;
|
||||
return request?.nextUrl?.origin ?? `http://localhost:${defaultPort}`;
|
||||
}
|
||||
|
||||
@@ -179,7 +179,9 @@ export abstract class MitmHandlerBase {
|
||||
path: string,
|
||||
headers: IncomingHttpHeaders,
|
||||
): Promise<Response> {
|
||||
const base = process.env.OMNIROUTE_BASE_URL ?? "http://127.0.0.1:20128";
|
||||
const port = process.env.API_PORT || process.env.PORT || 20128;
|
||||
const base =
|
||||
process.env.OMNIROUTE_BASE_URL ?? process.env.BASE_URL ?? `http://127.0.0.1:${port}`;
|
||||
const url = `${base.replace(/\/+$/, "")}${path}`;
|
||||
const apiKey = process.env.ROUTER_API_KEY ?? "";
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ const MITM_IDLE_TIMEOUT_MS =
|
||||
const ROUTER_BASE_URL = (
|
||||
process.env.OMNIROUTE_BASE_URL ||
|
||||
process.env.BASE_URL ||
|
||||
"http://localhost:20128"
|
||||
`http://localhost:${process.env.API_PORT || process.env.PORT || 20128}`
|
||||
)
|
||||
.trim()
|
||||
.replace(/\/+$/, "");
|
||||
|
||||
@@ -3,10 +3,7 @@ import { PEER_IP_HEADER } from "@/server/authz/headers";
|
||||
import { resolveStampedPeer } from "@/server/authz/peerStamp";
|
||||
|
||||
export type PublicOriginSource =
|
||||
| "configured"
|
||||
| "trusted-forwarded"
|
||||
| "request-url"
|
||||
| "direct-local-host";
|
||||
"configured" | "trusted-forwarded" | "request-url" | "direct-local-host";
|
||||
|
||||
export interface PublicOriginCandidate {
|
||||
origin: string;
|
||||
@@ -200,7 +197,7 @@ function directLocalHostOrigin(request: Request): string | null {
|
||||
if (classifyHostLocality(peer) === "remote") return null;
|
||||
|
||||
const rawHost = trustsForwardedHeaders(request)
|
||||
? firstHeaderValue(request.headers.get("x-forwarded-host")) ?? request.headers.get("host")
|
||||
? (firstHeaderValue(request.headers.get("x-forwarded-host")) ?? request.headers.get("host"))
|
||||
: request.headers.get("host");
|
||||
const host = sanitizeForwardedHost(rawHost);
|
||||
if (!host) return null;
|
||||
@@ -246,7 +243,8 @@ export function resolvePublicOrigin(request: Request): PublicOriginCandidate {
|
||||
const requestOrigin = requestUrlOrigin(request);
|
||||
if (requestOrigin) return { origin: requestOrigin, source: "request-url" };
|
||||
|
||||
return { origin: "http://localhost:20128", source: "request-url" };
|
||||
const defaultPort = process.env.PORT || process.env.DASHBOARD_PORT || "20128";
|
||||
return { origin: `http://localhost:${defaultPort}`, source: "request-url" };
|
||||
}
|
||||
|
||||
export function validateBrowserMutationOrigin(request: Request): BrowserMutationOriginVerdict {
|
||||
|
||||
@@ -45,7 +45,17 @@ export function parseCsvEnv(value: string | undefined | null): Set<string> {
|
||||
*/
|
||||
export function buildAllowedOrigins(env: NodeJS.ProcessEnv = process.env): Set<string> {
|
||||
const extra = parseCsvEnv(env.LIVE_WS_ALLOWED_ORIGINS);
|
||||
return new Set([...DEFAULT_ALLOWED_ORIGINS, ...extra]);
|
||||
const runtimePort = env.PORT || env.DASHBOARD_PORT;
|
||||
const dynamicDefaults: string[] = [];
|
||||
if (runtimePort && runtimePort !== "20128") {
|
||||
dynamicDefaults.push(
|
||||
`http://127.0.0.1:${runtimePort}`,
|
||||
`http://localhost:${runtimePort}`,
|
||||
`http://[::1]:${runtimePort}`,
|
||||
`http://0.0.0.0:${runtimePort}`
|
||||
);
|
||||
}
|
||||
return new Set([...DEFAULT_ALLOWED_ORIGINS, ...dynamicDefaults, ...extra]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -209,7 +209,11 @@ export function resolveDisplayBaseUrl(
|
||||
return joinOriginAndBasePath(configuredUrl, basePath);
|
||||
}
|
||||
|
||||
const fallback = currentOrigin ?? configuredUrl ?? DEFAULT_DISPLAY_BASE_URL;
|
||||
const portFallback =
|
||||
typeof process !== "undefined" && (process.env.NEXT_PUBLIC_PORT || process.env.PORT)
|
||||
? `http://localhost:${process.env.NEXT_PUBLIC_PORT || process.env.PORT}`
|
||||
: DEFAULT_DISPLAY_BASE_URL;
|
||||
const fallback = currentOrigin ?? configuredUrl ?? portFallback;
|
||||
return joinOriginAndBasePath(fallback, basePath);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ type OmniRouteBaseUrlEnv = {
|
||||
OMNIROUTE_BASE_URL?: string;
|
||||
BASE_URL?: string;
|
||||
NEXT_PUBLIC_BASE_URL?: string;
|
||||
PORT?: string | number;
|
||||
API_PORT?: string | number;
|
||||
DASHBOARD_PORT?: string | number;
|
||||
};
|
||||
|
||||
function normalizeBaseUrl(value?: string): string | null {
|
||||
@@ -13,11 +16,14 @@ function normalizeBaseUrl(value?: string): string | null {
|
||||
}
|
||||
|
||||
export function resolveOmniRouteBaseUrl(env: OmniRouteBaseUrlEnv = process.env): string {
|
||||
const port = env.PORT || env.API_PORT || env.DASHBOARD_PORT;
|
||||
const fallback = port ? `http://localhost:${port}` : DEFAULT_OMNIROUTE_BASE_URL;
|
||||
|
||||
return (
|
||||
normalizeBaseUrl(env.OMNIROUTE_BASE_URL) ||
|
||||
normalizeBaseUrl(env.BASE_URL) ||
|
||||
normalizeBaseUrl(env.NEXT_PUBLIC_BASE_URL) ||
|
||||
DEFAULT_OMNIROUTE_BASE_URL
|
||||
fallback
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,3 +50,7 @@ test("resolveOmniRouteBaseUrl ignores blank values", () => {
|
||||
test("resolveOmniRouteBaseUrl uses the default localhost fallback", () => {
|
||||
assert.equal(resolveOmniRouteBaseUrl({}), DEFAULT_OMNIROUTE_BASE_URL);
|
||||
});
|
||||
|
||||
test("resolveOmniRouteBaseUrl uses custom port when PORT env is set", () => {
|
||||
assert.equal(resolveOmniRouteBaseUrl({ PORT: 37128 }), "http://localhost:37128");
|
||||
});
|
||||
|
||||
@@ -66,6 +66,18 @@ describe("buildAllowedOrigins", () => {
|
||||
// Defaults remain.
|
||||
assert.equal(out.has("http://localhost:20128"), true);
|
||||
});
|
||||
|
||||
it("includes dynamic loopback origins when custom PORT is configured", () => {
|
||||
const env = {
|
||||
...EMPTY_ENV,
|
||||
PORT: "37128",
|
||||
};
|
||||
const out = buildAllowedOrigins(env);
|
||||
assert.equal(out.has("http://localhost:37128"), true);
|
||||
assert.equal(out.has("http://127.0.0.1:37128"), true);
|
||||
assert.equal(out.has("http://[::1]:37128"), true);
|
||||
assert.equal(out.has("http://localhost:20128"), true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildAllowedHosts", () => {
|
||||
@@ -150,6 +162,11 @@ describe("isOriginAllowed", () => {
|
||||
assert.equal(isOriginAllowed("http://100.96.135.160:20128", env), true);
|
||||
});
|
||||
|
||||
it("does not treat a wildcard host as an allow-all origin policy", () => {
|
||||
const env = { ...EMPTY_ENV, LIVE_WS_ALLOWED_HOSTS: "*" };
|
||||
assert.equal(isOriginAllowed("http://100.90.139.116:37128", env), false);
|
||||
});
|
||||
|
||||
it("does NOT accept a Tailscale Origin when LIVE_WS_ALLOWED_HOSTS is unset", () => {
|
||||
// Critical security invariant: without explicit opt-in, the LAN/Tailscale
|
||||
// surface is closed even though the listener is reachable.
|
||||
|
||||
Reference in New Issue
Block a user