Browse Source

Add proxy route to FetchService

pull/7153/head
Alexander Linder 3 weeks ago
parent
commit
4d04117b25
Failed to extract signature
  1. 49
      apps/api/src/services/fetch/fetch.service.ts
  2. 22
      apps/api/src/services/fetch/interfaces/proxy-route.interface.ts
  3. 1
      libs/common/src/lib/config.ts

49
apps/api/src/services/fetch/fetch.service.ts

@ -4,6 +4,7 @@ import {
PROPERTY_API_KEY_OPENROUTER,
PROPERTY_OPENROUTER_MODEL,
PROPERTY_OPENROUTER_MODEL_WEB_FETCH,
PROPERTY_PROXY_ROUTES,
PROPERTY_WEB_FETCH_ROUTES
} from '@ghostfolio/common/config';
@ -12,6 +13,7 @@ import { createOpenRouter } from '@openrouter/ai-sdk-provider';
import { generateText, jsonSchema, tool } from 'ai';
import ms from 'ms';
import { ProxyRoute } from './interfaces/proxy-route.interface';
import { WebFetchRoute } from './interfaces/web-fetch-route.interface';
@Injectable()
@ -21,11 +23,17 @@ export class FetchService implements OnModuleInit {
private static readonly REDACTED_QUERY_PARAM_NAMES = ['apikey', 'api_token'];
private static readonly WEB_FETCH_TIMEOUT = ms('30 seconds');
private proxyRoutes: ProxyRoute[] = [];
private webFetchRoutes: WebFetchRoute[] = [];
public constructor(private readonly propertyService: PropertyService) {}
public async onModuleInit() {
this.proxyRoutes =
(await this.propertyService.getByKey<ProxyRoute[]>(
PROPERTY_PROXY_ROUTES
)) ?? [];
this.webFetchRoutes =
(await this.propertyService.getByKey<WebFetchRoute[]>(
PROPERTY_WEB_FETCH_ROUTES
@ -33,6 +41,10 @@ export class FetchService implements OnModuleInit {
}
public async fetch(input: RequestInfo | URL, init?: RequestInit) {
// Apply a generic per-domain origin override (e.g. route a provider through
// a local proxy) before doing anything else with the request
input = this.applyProxyRoute(input);
const method = (
init?.method ??
(input instanceof Request ? input.method : undefined) ??
@ -171,18 +183,53 @@ export class FetchService implements OnModuleInit {
}
}
/**
* Rewrites the origin (protocol, host and port) of a request when its domain
* matches a configured {@link ProxyRoute}, preserving path and query. Returns
* the input unchanged when no route matches or parsing fails.
*/
private applyProxyRoute(input: RequestInfo | URL): RequestInfo | URL {
try {
const url = new URL(
input instanceof Request ? input.url : input.toString()
);
const route = this.proxyRoutes.find(({ domain }) => {
return this.matchesDomain(url.hostname, domain);
});
if (!route) {
return input;
}
const target = new URL(route.url);
url.protocol = target.protocol;
url.host = target.host;
return input instanceof Request
? new Request(url.toString(), input)
: url.toString();
} catch {
return input;
}
}
private getMatchingWebFetchRoute(url: string) {
try {
const { hostname } = new URL(url);
return this.webFetchRoutes.find(({ domain }) => {
return hostname === domain || hostname.endsWith(`.${domain}`);
return this.matchesDomain(hostname, domain);
});
} catch {
return undefined;
}
}
private matchesDomain(hostname: string, domain: string): boolean {
return hostname === domain || hostname.endsWith(`.${domain}`);
}
private redactUrl(rawUrl: string): string {
try {
const url = new URL(rawUrl);

22
apps/api/src/services/fetch/interfaces/proxy-route.interface.ts

@ -0,0 +1,22 @@
/**
* Overrides the origin (protocol, host and port) of outgoing requests for a
* given domain. This lets a self-hosted instance transparently route a
* provider's traffic through a local proxy (e.g. one that solves a bot
* challenge) without changing any call sites the request path and query are
* preserved.
*
* Configured via the `PROXY_ROUTES` property as a JSON array, e.g.
*
* [
* {
* "domain": "www.trackinsight.com",
* "url": "http://trackinsight-proxy:8191"
* }
* ]
*
* Matches the domain itself and its subdomains (e.g. `api.example.com`).
*/
export interface ProxyRoute {
domain: string;
url: string;
}

1
libs/common/src/lib/config.ts

@ -259,6 +259,7 @@ export const PROPERTY_IS_READ_ONLY_MODE = 'IS_READ_ONLY_MODE';
export const PROPERTY_IS_USER_SIGNUP_ENABLED = 'IS_USER_SIGNUP_ENABLED';
export const PROPERTY_OPENROUTER_MODEL = 'OPENROUTER_MODEL';
export const PROPERTY_OPENROUTER_MODEL_WEB_FETCH = 'OPENROUTER_MODEL_WEB_FETCH';
export const PROPERTY_PROXY_ROUTES = 'PROXY_ROUTES';
export const PROPERTY_REFERRAL_PARTNERS = 'REFERRAL_PARTNERS';
export const PROPERTY_SLACK_COMMUNITY_USERS = 'SLACK_COMMUNITY_USERS';
export const PROPERTY_STRIPE_CONFIG = 'STRIPE_CONFIG';

Loading…
Cancel
Save