diff --git a/packages/addon/src/config.ts b/packages/addon/src/config.ts index 005a8079..7fa1a130 100644 --- a/packages/addon/src/config.ts +++ b/packages/addon/src/config.ts @@ -92,6 +92,13 @@ export function validateConfig(config: Config): { } for (const addon of config.addons) { + if (Settings.DISABLE_TORRENTIO && addon.id === 'torrentio') { + return createResponse( + false, + 'torrentioDisabled', + Settings.DISABLE_TORRENTIO_MESSAGE + ); + } // if torbox addon is enabled, torbox service must be enabled and torbox api key must be set if (addon.id === 'torbox') { const torboxService = config.services.find( @@ -134,19 +141,58 @@ export function validateConfig(config: Config): { if ( option.id.toLowerCase().includes('url') && - addon.options[option.id] && - addon.options[option.id]?.match( - /^E-[0-9a-fA-F]{32}-[0-9a-fA-F]+$/ - ) === null + addon.options[option.id] ) { - try { - new URL(addon.options[option.id] as string); - } catch (_) { + if ( + Settings.DISABLE_TORRENTIO && + addon.options[option.id]?.match(/torrentio.strem.fun/) !== null + ) { + // if torrentio is disabled, don't allow the user to set URLs with torrentio.strem.fun return createResponse( false, - 'invalidUrl', - `Invalid URL for ${option.label}` + 'torrentioDisabled', + Settings.DISABLE_TORRENTIO_MESSAGE ); + } else if ( + Settings.DISABLE_TORRENTIO && + addon.options[option.id]?.match(/stremthru.elfhosted.com/) !== null + ) { + // if torrentio is disabled, we need to inspect the stremthru URL to see if it's using torrentio + try { + const url = new URL(addon.options[option.id] as string); + // get the component before manifest.json + const pathComponents = url.pathname.split('/'); + if (pathComponents.includes('manifest.json')) { + const index = pathComponents.indexOf('manifest.json'); + const componentBeforeManifest = pathComponents[index - 1]; + // base64 decode the component before manifest.json + const decodedComponent = atob(componentBeforeManifest); + const stremthruData = JSON.parse(decodedComponent); + if (stremthruData?.manifest_url?.match(/torrentio.strem.fun/)) { + return createResponse( + false, + 'torrentioDisabled', + Settings.DISABLE_TORRENTIO_MESSAGE + ); + } + } + } catch (_) { + // ignore + } + } else if ( + addon.options[option.id]?.match( + /^E-[0-9a-fA-F]{32}-[0-9a-fA-F]+$/ + ) === null + ) { + try { + new URL(addon.options[option.id] as string); + } catch (_) { + return createResponse( + false, + 'invalidUrl', + ` Invalid URL for ${option.label}` + ); + } } } diff --git a/packages/addon/src/server.ts b/packages/addon/src/server.ts index f83b3811..4b31a7bf 100644 --- a/packages/addon/src/server.ts +++ b/packages/addon/src/server.ts @@ -210,6 +210,16 @@ app.post('/encrypt-user-data', (req, res) => { res.status(500).json({ success: false, message: 'Secret key not set' }); return; } + const { valid, errorCode, errorMessage } = validateConfig(JSON.parse(data)); + if (!valid) { + console.error( + `|ERR| server > /encrypt-user-data: Invalid config: ${errorCode} - ${errorMessage}` + ); + res + .status(200) + .json({ success: false, message: errorMessage, error: errorMessage }); + return; + } const encryptedData = compressAndEncrypt(data); console.log(`|DBG| server > /encrypt-user-data: Encrypted data`); res.status(200).json({ success: true, data: encryptedData }); diff --git a/packages/frontend/src/app/configure/page.tsx b/packages/frontend/src/app/configure/page.tsx index 4d62a702..eeda1aa5 100644 --- a/packages/frontend/src/app/configure/page.tsx +++ b/packages/frontend/src/app/configure/page.tsx @@ -277,15 +277,24 @@ export default function Configure() { if (!response) { throw new Error('encrypt-user-data failed: no response within timeout'); } + if (!response.ok) { throw new Error( - `encrypt-user-data failed with status ${response.status}` + `encrypt-user-data failed with status ${response.status} and statusText ${response.statusText}` ); } const data = await response.json(); - if (!data.success) + if (!data.success) { + if (data.error) { + return { + success: false, + manifest: null, + message: data.error, + }; + } throw new Error(`Encryption service failed, ${data.message}`); + } const encryptedConfig = data.data; return { @@ -342,7 +351,7 @@ export default function Configure() { if (!manifestUrl.success || !manifestUrl.manifest) { setDisableButtons(false); toast.update(id, { - render: 'Failed to generate manifest URL', + render: manifestUrl.message || 'Failed to generate manifest URL', type: 'error', autoClose: 5000, isLoading: false, @@ -387,7 +396,7 @@ export default function Configure() { const manifestUrl = await getManifestUrl(); if (!manifestUrl.success || !manifestUrl.manifest) { toast.update(id, { - render: 'Failed to generate manifest URL', + render: manifestUrl.message || 'Failed to generate manifest URL', type: 'error', autoClose: 5000, isLoading: false, @@ -436,7 +445,7 @@ export default function Configure() { const manifestUrl = await getManifestUrl(); if (!manifestUrl.success || !manifestUrl.manifest) { toast.update(id, { - render: 'Failed to generate manifest URL', + render: manifestUrl.message || 'Failed to generate manifest URL', type: 'error', autoClose: 5000, isLoading: false, diff --git a/packages/utils/src/settings.ts b/packages/utils/src/settings.ts index 6f020621..95f0ea5f 100644 --- a/packages/utils/src/settings.ts +++ b/packages/utils/src/settings.ts @@ -95,4 +95,10 @@ export class Settings { public static readonly LOG_SENSITIVE_INFO = process.env.LOG_SENSITIVE_INFO ? process.env.LOG_SENSITIVE_INFO === 'true' : false; + public static readonly DISABLE_TORRENTIO = process.env.DISABLE_TORRENTIO + ? process.env.DISABLE_TORRENTIO === 'true' + : false; + public static readonly DISABLE_TORRENTIO_MESSAGE = + process.env.DISABLE_TORRENTIO_MESSAGE || + 'The Torrentio addon has been disabled, please remove it to use this addon.'; }