# All files / src/app/api/proxy proxyUtils.ts

0% Statements 0/69
0% Branches 0/69
0% Functions 0/13
0% Lines 0/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108

     const DEFAULT_ALLOWED_HOSTS_DEV = '*';

    function parseBoolEnv(key: string, defaultValue: boolean): boolean {
      const raw = process.env[key];
      if (raw == null) return defaultValue;
      const v = String(raw).trim().toLowerCase();
      return v === '1' || v === 'true' || v === 'yes' || v === 'y' || v === 'on';
    }

    export function requireAuthEnabled(): boolean {
      return parseBoolEnv('STREAMCORE_PROXY_REQUIRE_AUTH', process.env.NODE_ENV !== 'development');
    }

    export function allowPrivateEnabled(): boolean {
      return parseBoolEnv('STREAMCORE_PROXY_ALLOW_PRIVATE', process.env.NODE_ENV === 'development');
    }

    export function getAllowedHosts(): string[] {
      // Default to allowing all hosts since this is a streaming platform
      // that needs to proxy content from various CDNs and sources
      const raw = process.env.STREAMCORE_PROXY_ALLOWED_HOSTS || '*';

      const list = String(raw)
        .split(',')
        .map((s) => s.trim())
        .filter(Boolean);

      return list;
    }

    export function hasAuth(req: Request): boolean {
      const auth = req.headers.get('authorization') || '';
      if (auth.toLowerCase().startsWith('bearer ')) return true;

      const cookie = req.headers.get('cookie') || '';
      // Simple cookie lookup to avoid deps
      return cookie.split(';').some((part) => part.trim().startsWith('iptv_auth_token='));
    }

    function isIpV4(hostname: string): boolean {
      return /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname);
    }

    function ipV4ToBytes(ip: string): number[] | null {
      const parts = ip.split('.').map((p) => Number(p));
      if (parts.length !== 4) return null;
      if (parts.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) return null;
      return parts;
    }

    function isPrivateIpV4(ip: string): boolean {
      const b = ipV4ToBytes(ip);
      if (!b) return false;
      const [a, c] = b;
      // 10.0.0.0/8
      if (a === 10) return true;
      // 127.0.0.0/8 (loopback)
      if (a === 127) return true;
      // 169.254.0.0/16 (link-local)
      if (a === 169 && c === 254) return true;
      // 172.16.0.0/12
      if (a === 172 && c >= 16 && c <= 31) return true;
      // 192.168.0.0/16
      if (a === 192 && c === 168) return true;
      // 0.0.0.0/8, 100.64.0.0/10 (CGNAT)
      if (a === 0) return true;
      if (a === 100 && c >= 64 && c <= 127) return true;
      return false;
    }

    export function validateUpstreamUrl(urlParam: string): { ok: true; url: URL } | { ok: false; error: string; status: number } {
      let url: URL;
      try {
        url = new URL(urlParam);
      } catch {
        return { ok: false, error: 'Invalid url param', status: 400 };
      }

      if (!/^https?:$/.test(url.protocol)) {
        return { ok: false, error: 'Only http/https URLs are allowed', status: 400 };
      }

      if (url.username || url.password) {
        return { ok: false, error: 'Credentials in URL are not allowed', status: 400 };
      }

      const hostname = url.hostname.toLowerCase();
      if (hostname === 'localhost' || hostname.endsWith('.localhost')) {
        return { ok: false, error: 'Localhost is not allowed', status: 403 };
      }

      if (isIpV4(hostname) && isPrivateIpV4(hostname) && !allowPrivateEnabled()) {
        return { ok: false, error: 'Private IP ranges are not allowed', status: 403 };
      }

      const allowed = getAllowedHosts();
      if (allowed.length === 0) {
        return { ok: false, error: 'Proxy is disabled (no allowlist configured)', status: 403 };
      }

      if (!allowed.includes('*') && !allowed.includes(hostname)) {
        return { ok: false, error: `Host not allowed: ${hostname}`, status: 403 };
      }

      return { ok: true, url };
    }