mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
fix(xtream): address portal review feedback
This commit is contained in:
1 parent
254879f92b
commit
20d0f01428
12 files changed
+295
-51
No files matched your search
@@ -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' } });
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in new issue
Block a user