const CF_BASE_URL = "https://api.cloudflare.com/client/v4"; const HTML_CONTENT = String.raw` Zero Trust Setup Pro
PRO DASHBOARD

Zero Trust Manager

Masuk untuk setup Cloudflared Tunnel

Sukses Membuat Tunnel!

Hostname:

Token Instalasi:
`; function cfHeaders(email, apiKey) { return { "X-Auth-Email": email, "X-Auth-Key": apiKey, "Content-Type": "application/json" }; } function jsonResponse(payload, status = 200) { return new Response(JSON.stringify(payload), { status, headers: { "Content-Type": "application/json" } }); } function randomTunnelSecret() { const bytes = new Uint8Array(32); crypto.getRandomValues(bytes); let binary = ""; for (let i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } export default { async fetch(request) { const url = new URL(request.url); if (request.method === "GET" && url.pathname === "/") { return new Response(HTML_CONTENT, { headers: { "Content-Type": "text/html;charset=UTF-8" } }); } if (request.method === "POST" && url.pathname === "/api/login") { try { const body = await request.json(); const headers = cfHeaders(body.email, body.apiKey); const res = await fetch(`${CF_BASE_URL}/zones?status=active&per_page=50`, { method: "GET", headers }); const data = await res.json(); if (!data.success) { return jsonResponse({ success: false, error: data.errors?.[0]?.message || "Autentikasi gagal" }); } const accountIds = new Set(); const domains = data.result.map((zone) => { accountIds.add(zone.account.id); return { zoneId: zone.id, domainName: zone.name, accountId: zone.account.id }; }); return jsonResponse({ success: true, domains, accounts: Array.from(accountIds) }); } catch (err) { return jsonResponse({ success: false, error: err.message }, 500); } } if (request.method === "POST" && url.pathname === "/api/setup") { try { const body = await request.json(); const headers = cfHeaders(body.email, body.apiKey); const tunnelSecret = randomTunnelSecret(); const createTunnelRes = await fetch(`${CF_BASE_URL}/accounts/${body.accountId}/cfd_tunnel`, { method: "POST", headers, body: JSON.stringify({ name: body.subdomain, tunnel_secret: tunnelSecret }) }); const createTunnelData = await createTunnelRes.json(); if (!createTunnelData.success) { throw new Error(JSON.stringify(createTunnelData.errors)); } const tunnelId = createTunnelData.result.id; const token = btoa(JSON.stringify({ a: body.accountId, t: tunnelId, s: tunnelSecret })); const configRes = await fetch(`${CF_BASE_URL}/accounts/${body.accountId}/cfd_tunnel/${tunnelId}/configurations`, { method: "PUT", headers, body: JSON.stringify({ config: { ingress: [ { hostname: `${body.subdomain}.${body.domain}`, service: body.target }, { service: "http_status:404" } ] } }) }); const configData = await configRes.json(); if (!configData.success) { // PESAN ERROR DETAIL throw new Error("Gagal setting routing: " + (configData.errors?.[0]?.message || JSON.stringify(configData.errors))); } await fetch(`${CF_BASE_URL}/zones/${body.zoneId}/dns_records`, { method: "POST", headers, body: JSON.stringify({ type: "CNAME", name: body.subdomain, content: `${tunnelId}.cfargotunnel.com`, proxied: true }) }); return jsonResponse({ success: true, tunnelId, token, hostname: `${body.subdomain}.${body.domain}` }); } catch (err) { return jsonResponse({ success: false, error: err.message }, 500); } } if (request.method === "POST" && url.pathname === "/api/tunnels") { try { const body = await request.json(); const headers = cfHeaders(body.email, body.apiKey); let tunnels = []; for (const accountId of body.accounts) { const listRes = await fetch(`${CF_BASE_URL}/accounts/${accountId}/cfd_tunnel`, { method: "GET", headers }); const listData = await listRes.json(); if (listData.success) { const enriched = listData.result.map(async (tunnel) => { tunnel.accountId = accountId; try { const configRes = await fetch(`${CF_BASE_URL}/accounts/${accountId}/cfd_tunnel/${tunnel.id}/configurations`, { method: "GET", headers }); const configData = await configRes.json(); const ingress = configData?.result?.config?.ingress; if (configData.success && Array.isArray(ingress)) { const publicRoutes = ingress.filter((item) => item.hostname); if (publicRoutes.length > 0) { tunnel.hostname = publicRoutes[0].hostname; tunnel.hostnames = publicRoutes.map(r => r.hostname); tunnel.routes = publicRoutes.map(r => ({ hostname: r.hostname, service: r.service })); // Simpan untuk fitur edit } } } catch (_) {} return tunnel; }); tunnels = tunnels.concat(await Promise.all(enriched)); } } tunnels.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); return jsonResponse({ success: true, tunnels }); } catch (err) { return jsonResponse({ success: false, error: err.message }, 500); } } if (request.method === "POST" && url.pathname === "/api/tunnels/route") { try { const body = await request.json(); const headers = cfHeaders(body.email, body.apiKey); const getConfigRes = await fetch(`${CF_BASE_URL}/accounts/${body.accountId}/cfd_tunnel/${body.tunnelId}/configurations`, { method: "GET", headers }); const configData = await getConfigRes.json(); let currentIngress = configData?.result?.config?.ingress || [{ service: "http_status:404" }]; const catchAllRules = currentIngress.filter(i => !i.hostname && i.service === "http_status:404"); let newIngress = currentIngress.filter(i => i.hostname || i.service !== "http_status:404"); const newHostname = `${body.subdomain}.${body.domain}`; newIngress.push({ hostname: newHostname, service: body.target }); if (catchAllRules.length > 0) { newIngress.push(catchAllRules[0]); } else { newIngress.push({ service: "http_status:404" }); } const updateConfigRes = await fetch(`${CF_BASE_URL}/accounts/${body.accountId}/cfd_tunnel/${body.tunnelId}/configurations`, { method: "PUT", headers, body: JSON.stringify({ config: { ingress: newIngress } }) }); const updateConfigData = await updateConfigRes.json(); if (!updateConfigData.success) { // PESAN ERROR DETAIL throw new Error("Gagal routing: " + (updateConfigData.errors?.[0]?.message || JSON.stringify(updateConfigData.errors))); } await fetch(`${CF_BASE_URL}/zones/${body.zoneId}/dns_records`, { method: "POST", headers, body: JSON.stringify({ type: "CNAME", name: body.subdomain, content: `${body.tunnelId}.cfargotunnel.com`, proxied: true }) }); return jsonResponse({ success: true, hostname: newHostname }); } catch (err) { return jsonResponse({ success: false, error: err.message }, 500); } } // --- API FITUR BARU: EDIT ROUTE --- if (request.method === "POST" && url.pathname === "/api/tunnels/edit") { try { const body = await request.json(); const headers = cfHeaders(body.email, body.apiKey); const getConfigRes = await fetch(`${CF_BASE_URL}/accounts/${body.accountId}/cfd_tunnel/${body.tunnelId}/configurations`, { method: "GET", headers }); const configData = await getConfigRes.json(); let currentIngress = configData?.result?.config?.ingress || [{ service: "http_status:404" }]; // Cari hostname yang cocok dan ubah service targetnya let isUpdated = false; currentIngress = currentIngress.map(item => { if (item.hostname === body.hostname) { item.service = body.target; isUpdated = true; } return item; }); if (!isUpdated) throw new Error("Hostname tidak ditemukan dalam konfigurasi Tunnel ini."); const updateConfigRes = await fetch(`${CF_BASE_URL}/accounts/${body.accountId}/cfd_tunnel/${body.tunnelId}/configurations`, { method: "PUT", headers, body: JSON.stringify({ config: { ingress: currentIngress } }) }); const updateConfigData = await updateConfigRes.json(); if (!updateConfigData.success) { throw new Error("Gagal update routing: " + (updateConfigData.errors?.[0]?.message || JSON.stringify(updateConfigData.errors))); } return jsonResponse({ success: true }); } catch (err) { return jsonResponse({ success: false, error: err.message }, 500); } } if (request.method === "POST" && url.pathname === "/api/tunnels/token") { try { const body = await request.json(); const headers = cfHeaders(body.email, body.apiKey); const res = await fetch(`${CF_BASE_URL}/accounts/${body.accountId}/cfd_tunnel/${body.tunnelId}/token`, { method: "GET", headers }); const data = await res.json(); if (!data.success) throw new Error(data.errors?.[0]?.message || "Gagal mengambil token"); return jsonResponse({ success: true, token: data.result }); } catch (err) { return jsonResponse({ success: false, error: err.message }, 500); } } if (request.method === "POST" && url.pathname === "/api/tunnels/delete") { try { const body = await request.json(); const headers = cfHeaders(body.email, body.apiKey); // FORCE DELETE FIX: Tambahkan ?force=true const res = await fetch(`${CF_BASE_URL}/accounts/${body.accountId}/cfd_tunnel/${body.tunnelId}?force=true`, { method: "DELETE", headers }); const data = await res.json(); if (!data.success) throw new Error(data.errors?.[0]?.message || "Gagal menghapus"); return jsonResponse({ success: true }); } catch (err) { return jsonResponse({ success: false, error: err.message }, 500); } } return new Response("Endpoint tidak ditemukan", { status: 404 }); } };