fix(xtream): address portal review feedback

This commit is contained in:
4gray committed 2026-06-14 13:47:42 +02:00
1 parent 254879f92b
commit 20d0f01428
12 files changed
+295 -51

No files matched your search

+2 -1
View File
@@ -31,7 +31,8 @@
"PORT": "3333",
"CLIENT_URL": "http://localhost:4200",
"BACKEND_URL": "/api",
"IPTVNATOR_PROXY_ALLOW_PRIVATE_NETWORKS": "1"
"IPTVNATOR_PROXY_ALLOW_PRIVATE_NETWORKS": "1",
"TSX_TSCONFIG_PATH": "tsconfig.base.json"
}
}
},
@@ -467,6 +467,36 @@ https://stream.example/news.m3u8`);
);
});
it('rejects Xtream proxy calls when a registered target no longer passes the URL policy', async () => {
const httpClient = new StubHttpClient();
const resolvedAddresses = [['93.184.216.34'], ['127.0.0.1']];
await withServer(
createWebBackendApp({
httpClient,
resolveHostname: async () =>
resolvedAddresses.shift() ?? ['127.0.0.1'],
}),
async (baseUrl) => {
const targetId = await registerProviderTarget(
baseUrl,
'http://xtream.example'
);
const response = await fetch(
`${baseUrl}/xtream?targetId=${targetId}&action=get_account_info`
);
expect(response.status).toBe(400);
await expect(response.json()).resolves.toEqual({
message:
'Provider URL points to a private or local network address',
status: 400,
});
expect(httpClient.requests).toEqual([]);
}
);
});
it('allows private target URLs when explicitly enabled for local self-hosted testing', async () => {
const httpClient = new StubHttpClient();
httpClient.queueResponse({ user_info: { username: 'demo' } });
+37 -7
View File
@@ -177,16 +177,30 @@ export function createWebBackendApp(
});
app.get('/xtream', corsMiddleware, async (req, res) => {
const url = getRegisteredProviderUrl(req, res, providerTargets);
if (!url) {
const registeredUrl = getRegisteredProviderUrl(
req,
res,
providerTargets
);
if (!registeredUrl) {
return;
}
const url = new URL(registeredUrl.href);
try {
const providerUrlError = await normalizeAndValidateXtreamProviderUrl(
url,
providerUrlPolicy
);
if (providerUrlError) {
res.status(providerUrlError.status).json(providerUrlError);
return;
}
// Provider URLs are validated by /provider-targets before they enter the registry.
// codeql[js/request-forgery]
const response = await httpClient.get(
buildXtreamPlayerApiUrl(url),
appendPathSegment(url, 'player_api.php'),
{
params: getProxyParams(req, ['targetId']),
}
@@ -395,11 +409,27 @@ function appendPathSegment(url: URL, segment: string): string {
return nextUrl.href;
}
function buildXtreamPlayerApiUrl(url: URL): string {
return appendPathSegment(
new URL(normalizeXtreamServerUrl(url.href)),
'player_api.php'
async function normalizeAndValidateXtreamProviderUrl(
url: URL,
policy: ProviderUrlPolicy
): Promise<ProviderUrlError | null> {
let normalizedUrl: URL;
try {
normalizedUrl = new URL(normalizeXtreamServerUrl(url.href));
} catch {
return { message: 'Provider URL is not a valid URL', status: 400 };
}
const validatedUrl = await validateProviderUrl(
appendPathSegment(normalizedUrl, 'player_api.php'),
policy
);
if ('message' in validatedUrl) {
return validatedUrl;
}
url.href = normalizedUrl.href;
return null;
}
async function handlePlaylistParse(options: {