From 53cb6a67f2f65a5c43b31e280f70541ccf9dfc5d Mon Sep 17 00:00:00 2001 From: bia-pain-bache Date: Sat, 2 Nov 2024 16:47:38 +0330 Subject: [PATCH] Refactored. --- src/authentication/auth.js | 64 +++++++- src/cores-configs/clash.js | 34 ++++- src/cores-configs/helpers.js | 2 +- src/cores-configs/normalConfigs.js | 22 ++- src/cores-configs/sing-box.js | 34 ++++- src/cores-configs/xray.js | 47 ++++-- src/helpers/helpers.js | 29 ++++ src/helpers/init.js | 18 ++- src/kv/handlers.js | 44 ++++-- src/pages/errorPage.js | 10 +- src/pages/homePage.js | 23 ++- src/pages/loginPage.js | 23 ++- src/protocols/trojan.js | 7 +- src/protocols/vless.js | 9 +- src/worker.js | 235 ++++------------------------- 15 files changed, 322 insertions(+), 279 deletions(-) diff --git a/src/authentication/auth.js b/src/authentication/auth.js index aa4d558e9..521136806 100644 --- a/src/authentication/auth.js +++ b/src/authentication/auth.js @@ -1,19 +1,36 @@ import { SignJWT, jwtVerify } from 'jose'; import nacl from 'tweetnacl'; -import { initializeParams, userID } from "../helpers/init.js"; +import { initializeParams, userID, origin } from "../helpers/init"; +import { renderLoginPage } from '../pages/loginPage'; +import { renderErrorPage } from '../pages/errorPage'; - -export async function generateJWTToken (env, secretKey) { - await initializeParams(env); +async function generateJWTToken (request, env) { + await initializeParams(request, env); + const password = await request.text(); + const savedPass = await env.bpb.get('pwd'); + if (password !== savedPass) return new Response('Method Not Allowed', { status: 405 }); + let secretKey = await env.bpb.get('secretKey'); + if (!secretKey) { + secretKey = generateSecretKey(); + await env.bpb.put('secretKey', secretKey); + } const secret = new TextEncoder().encode(secretKey); - return await new SignJWT({ userID }) + const jwtToken = await new SignJWT({ userID }) .setProtectedHeader({ alg: 'HS256' }) .setIssuedAt() .setExpirationTime('24h') .sign(secret); + + return new Response('Success', { + status: 200, + headers: { + 'Set-Cookie': `jwtToken=${jwtToken}; HttpOnly; Secure; Max-Age=${7 * 24 * 60 * 60}; Path=/; SameSite=Strict`, + 'Content-Type': 'text/plain', + } + }); } -export function generateSecretKey () { +function generateSecretKey () { const key = nacl.randomBytes(32); return Array.from(key, byte => byte.toString(16).padStart(2, '0')).join(''); } @@ -37,4 +54,39 @@ export async function Authenticate (request, env) { console.log(error); return false; } +} + +export function logout() { + return new Response('Success', { + status: 200, + headers: { + 'Set-Cookie': 'jwtToken=; Secure; SameSite=None; Expires=Thu, 01 Jan 1970 00:00:00 GMT', + 'Content-Type': 'text/plain' + } + }); +} + +export async function resetPassword(request, env) { + let auth = await Authenticate(request, env); + const oldPwd = await env.bpb.get('pwd'); + if (oldPwd && !auth) return new Response('Unauthorized!', { status: 401 }); + const newPwd = await request.text(); + if (newPwd === oldPwd) return new Response('Please enter a new Password!', { status: 400 }); + await env.bpb.put('pwd', newPwd); + return new Response('Success', { + status: 200, + headers: { + 'Set-Cookie': 'jwtToken=; Path=/; Secure; SameSite=None; Expires=Thu, 01 Jan 1970 00:00:00 GMT', + 'Content-Type': 'text/plain', + } + }); +} + +export async function login(request, env) { + await initializeParams(request, env); + if (typeof env.bpb !== 'object') return await renderErrorPage(request, env, 'KV Dataset is not properly set!', null, true); + const auth = await Authenticate(request, env); + if (auth) return Response.redirect(`${origin}/panel`, 302); + if (request.method === 'POST') return await generateJWTToken(request, env); + return await renderLoginPage(request, env); } \ No newline at end of file diff --git a/src/cores-configs/clash.js b/src/cores-configs/clash.js index dafc1ec98..5cd2de9ff 100644 --- a/src/cores-configs/clash.js +++ b/src/cores-configs/clash.js @@ -1,5 +1,7 @@ -import { getConfigAddresses, extractWireguardParams, generateRemark, randomUpperCase, getRandomPath, isIPv6 } from './helpers.js'; -import { initializeParams, userID, trojanPassword, defaultHttpsPorts } from "../helpers/init.js"; +import { getConfigAddresses, extractWireguardParams, generateRemark, randomUpperCase, getRandomPath, isIPv6 } from './helpers'; +import { initializeParams, userID, trojanPassword, hostName, defaultHttpsPorts } from "../helpers/init"; +import { getDataset } from '../kv/handlers'; +import { renderErrorPage } from '../pages/errorPage'; async function buildClashDNS (proxySettings, isWarp) { const { @@ -279,7 +281,9 @@ function buildClashChainOutbound(chainProxyParams) { return chainOutbound; } -export async function getClashWarpConfig(proxySettings, warpConfigs) { +export async function getClashWarpConfig(request, env) { + const { kvNotFound, proxySettings, warpConfigs } = await getDataset(request, env); + if (kvNotFound) return await renderErrorPage(request, env, 'KV Dataset is not properly set!', null, true); const { warpEndpoints, warpEnableIPv6 } = proxySettings; let config = structuredClone(clashConfigTemp); config.ipv6 = warpEnableIPv6; @@ -308,11 +312,20 @@ export async function getClashWarpConfig(proxySettings, warpConfigs) { }); selector.proxies.push(...warpRemarks, ...WoWRemarks); - return config; + return new Response(JSON.stringify(config, null, 4), { + status: 200, + headers: { + 'Content-Type': 'text/plain;charset=utf-8', + 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', + 'CDN-Cache-Control': 'no-store' + } + }); } -export async function getClashNormalConfig (env, hostName, proxySettings) { - await initializeParams(env); +export async function getClashNormalConfig (request, env) { + await initializeParams(request, env); + const { kvNotFound, proxySettings } = await getDataset(request, env); + if (kvNotFound) return await renderErrorPage(request, env, 'KV Dataset is not properly set!', null, true); let chainProxy; const { cleanIPs, @@ -418,7 +431,14 @@ export async function getClashNormalConfig (env, hostName, proxySettings) { }); }); - return config; + return new Response(JSON.stringify(config, null, 4), { + status: 200, + headers: { + 'Content-Type': 'text/plain;charset=utf-8', + 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', + 'CDN-Cache-Control': 'no-store' + } + }); } const clashConfigTemp = { diff --git a/src/cores-configs/helpers.js b/src/cores-configs/helpers.js index b6a6ce06d..3cf21dbfb 100644 --- a/src/cores-configs/helpers.js +++ b/src/cores-configs/helpers.js @@ -1,4 +1,4 @@ -import { resolveDNS, isDomain } from '../helpers/helpers.js'; +import { resolveDNS, isDomain } from '../helpers/helpers'; export async function getConfigAddresses(hostName, cleanIPs, enableIPv6) { const resolved = await resolveDNS(hostName); diff --git a/src/cores-configs/normalConfigs.js b/src/cores-configs/normalConfigs.js index daf7436cd..b21619dc7 100644 --- a/src/cores-configs/normalConfigs.js +++ b/src/cores-configs/normalConfigs.js @@ -1,8 +1,12 @@ -import { getConfigAddresses, generateRemark, randomUpperCase, getRandomPath } from './helpers.js'; -import { initializeParams, userID, trojanPassword, defaultHttpsPorts } from "../helpers/init.js"; +import { getConfigAddresses, generateRemark, randomUpperCase, getRandomPath } from './helpers'; +import { initializeParams, userID, trojanPassword, hostName, client, defaultHttpsPorts } from "../helpers/init"; +import { getDataset } from '../kv/handlers'; +import { renderErrorPage } from '../pages/errorPage'; -export async function getNormalConfigs(env, hostName, proxySettings, client) { - await initializeParams(env); +export async function getNormalConfigs(request, env) { + await initializeParams(request, env); + const { kvNotFound, proxySettings } = await getDataset(request, env); + if (kvNotFound) return await renderErrorPage(request, env, 'KV Dataset is not properly set!', null, true); const { cleanIPs, proxyIP, @@ -66,5 +70,13 @@ export async function getNormalConfigs(env, hostName, proxySettings, client) { } } - return btoa(vlessConfs + trojanConfs + chainProxy); + const configs = btoa(vlessConfs + trojanConfs + chainProxy); + return new Response(JSON.stringify(configs, null, 4), { + status: 200, + headers: { + 'Content-Type': 'text/plain;charset=utf-8', + 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', + 'CDN-Cache-Control': 'no-store' + } + }); } \ No newline at end of file diff --git a/src/cores-configs/sing-box.js b/src/cores-configs/sing-box.js index d2e24dead..d1bde69ad 100644 --- a/src/cores-configs/sing-box.js +++ b/src/cores-configs/sing-box.js @@ -1,5 +1,7 @@ -import { getConfigAddresses, extractWireguardParams, generateRemark, randomUpperCase, getRandomPath } from './helpers.js'; -import { initializeParams, userID, trojanPassword, defaultHttpsPorts } from "../helpers/init.js"; +import { getConfigAddresses, extractWireguardParams, generateRemark, randomUpperCase, getRandomPath } from './helpers'; +import { initializeParams, userID, trojanPassword, hostName, defaultHttpsPorts } from "../helpers/init"; +import { renderErrorPage } from '../pages/errorPage'; +import { getDataset } from '../kv/handlers'; function buildSingBoxDNS (proxySettings, isChain, isWarp) { const { @@ -508,7 +510,9 @@ function buildSingBoxChainOutbound (chainProxyParams) { return chainOutbound; } -export async function getSingBoxWarpConfig (proxySettings, warpConfigs, client) { +export async function getSingBoxWarpConfig (request, env, client) { + const { kvNotFound, proxySettings, warpConfigs } = await getDataset(request, env); + if (kvNotFound) return await renderErrorPage(request, env, 'KV Dataset is not properly set!', null, true); const { warpEndpoints } = proxySettings; let config = structuredClone(singboxConfigTemp); const dnsObject = buildSingBoxDNS(proxySettings, false, true); @@ -543,11 +547,20 @@ export async function getSingBoxWarpConfig (proxySettings, warpConfigs, client) }); selector.outbounds.push(...warpRemarks, ...WoWRemarks); - return config; + return new Response(JSON.stringify(config, null, 4), { + status: 200, + headers: { + 'Content-Type': 'text/plain;charset=utf-8', + 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', + 'CDN-Cache-Control': 'no-store' + } + }); } -export async function getSingBoxCustomConfig(env, hostName, proxySettings, isFragment) { - await initializeParams(env); +export async function getSingBoxCustomConfig(request, env, isFragment) { + await initializeParams(request, env); + const { kvNotFound, proxySettings } = await getDataset(request, env); + if (kvNotFound) return await renderErrorPage(request, env, 'KV Dataset is not properly set!', null, true); let chainProxyOutbound; const { cleanIPs, @@ -655,7 +668,14 @@ export async function getSingBoxCustomConfig(env, hostName, proxySettings, isFra }); }); - return config; + return new Response(JSON.stringify(config, null, 4), { + status: 200, + headers: { + 'Content-Type': 'text/plain;charset=utf-8', + 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', + 'CDN-Cache-Control': 'no-store' + } + }); } const singboxConfigTemp = { diff --git a/src/cores-configs/xray.js b/src/cores-configs/xray.js index 3f8e9f081..24afc69b6 100644 --- a/src/cores-configs/xray.js +++ b/src/cores-configs/xray.js @@ -1,7 +1,8 @@ import { resolveDNS, isDomain } from '../helpers/helpers.js'; -import { getConfigAddresses, extractWireguardParams, base64ToDecimal, generateRemark, randomUpperCase, getRandomPath } from './helpers.js'; -import { initializeParams, userID, trojanPassword, defaultHttpsPorts } from "../helpers/init.js"; - +import { getConfigAddresses, extractWireguardParams, base64ToDecimal, generateRemark, randomUpperCase, getRandomPath } from './helpers'; +import { initializeParams, userID, trojanPassword, hostName, defaultHttpsPorts } from "../helpers/init"; +import { getDataset } from '../kv/handlers.js'; +import { renderErrorPage } from '../pages/errorPage.js'; async function buildXrayDNS (proxySettings, outboundAddrs, domainToStaticIPs, isWorkerLess, isBalancer, isWarp) { const { @@ -632,8 +633,10 @@ async function buildXrayWorkerLessConfig(proxySettings) { return config; } -export async function getXrayCustomConfigs(env, hostName, proxySettings, isFragment) { - await initializeParams(env); +export async function getXrayCustomConfigs(request, env, isFragment) { + await initializeParams(request, env); + const { kvNotFound, proxySettings } = await getDataset(request, env); + if (kvNotFound) return await renderErrorPage(request, env, 'KV Dataset is not properly set!', null, true); let configs = []; let outbounds = []; let protocols = []; @@ -711,15 +714,25 @@ export async function getXrayCustomConfigs(env, hostName, proxySettings, isFragm } const bestPing = await buildXrayBestPingConfig(proxySettings, totalAddresses, chainProxy, outbounds, isFragment); - if (!isFragment) return [...configs, bestPing]; - const bestFragment = await buildXrayBestFragmentConfig(proxySettings, hostName, chainProxy, outbounds); - const workerLessConfig = await buildXrayWorkerLessConfig(proxySettings); - configs.push(bestPing, bestFragment, workerLessConfig); - - return configs; + let finalConfigs = [...configs, bestPing]; + if (isFragment) { + const bestFragment = await buildXrayBestFragmentConfig(proxySettings, hostName, chainProxy, outbounds); + const workerLessConfig = await buildXrayWorkerLessConfig(proxySettings); + finalConfigs.push(bestFragment, workerLessConfig); + } + return new Response(JSON.stringify(finalConfigs, null, 4), { + status: 200, + headers: { + 'Content-Type': 'text/plain;charset=utf-8', + 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', + 'CDN-Cache-Control': 'no-store' + } + }); } -export async function getXrayWarpConfigs (proxySettings, warpConfigs, client) { +export async function getXrayWarpConfigs (request, env, client) { + const { kvNotFound, proxySettings, warpConfigs } = await getDataset(request, env); + if (kvNotFound) return await renderErrorPage(request, env, 'KV Dataset is not properly set!', null, true); let xrayWarpConfigs = []; let xrayWoWConfigs = []; let xrayWarpOutbounds = []; @@ -761,7 +774,15 @@ export async function getXrayWarpConfigs (proxySettings, warpConfigs, client) { xrayWoWBestPing.dns = dnsObject; xrayWoWBestPing.routing.rules = buildXrayRoutingRules(proxySettings, outboundDomains, true, true, false); xrayWoWBestPing.outbounds.unshift(...xrayWoWOutbounds, ...xrayWarpOutbounds); - return [...xrayWarpConfigs, ...xrayWoWConfigs, xrayWarpBestPing, xrayWoWBestPing]; + const configs = [...xrayWarpConfigs, ...xrayWoWConfigs, xrayWarpBestPing, xrayWoWBestPing]; + return new Response(JSON.stringify(configs, null, 4), { + status: 200, + headers: { + 'Content-Type': 'text/plain;charset=utf-8', + 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', + 'CDN-Cache-Control': 'no-store' + } + }); } const xrayConfigTemp = { diff --git a/src/helpers/helpers.js b/src/helpers/helpers.js index 76e289e1e..ddf1be127 100644 --- a/src/helpers/helpers.js +++ b/src/helpers/helpers.js @@ -1,3 +1,9 @@ +import { Authenticate } from "../authentication/auth"; +import { getDataset, updateDataset } from "../kv/handlers"; +import { renderErrorPage } from "../pages/errorPage"; +import { renderHomePage } from "../pages/homePage"; +import { initializeParams, origin } from "./init"; + export function isValidUUID(uuid) { const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; return uuidRegex.test(uuid); @@ -36,4 +42,27 @@ export function isDomain(address) { return domainPattern.test(address); } +export async function handlePanel(request, env) { + await initializeParams(request, env); + const auth = await Authenticate(request, env); + if (request.method === 'POST') { + if (!auth) return new Response('Unauthorized or expired session!', { status: 401 }); + await updateDataset(request, env); + return new Response('Success', { status: 200 }); + } + + const pwd = await env.bpb.get('pwd'); + if (pwd && !auth) return Response.redirect(`${origin}/login`, 302); + const isPassSet = pwd?.length >= 8; + const { kvNotFound, proxySettings } = await getDataset(request, env); + if (kvNotFound) return await renderErrorPage(request, env, 'KV Dataset is not properly set!', null, true); + return await renderHomePage(request, env, proxySettings, isPassSet); +} +export async function fallback(request) { + const url = new URL(request.url); + url.hostname = 'www.speedtest.net'; + url.protocol = 'https:'; + request = new Request(url, request); + return await fetch(request); +} \ No newline at end of file diff --git a/src/helpers/init.js b/src/helpers/init.js index f740bbad4..f57525fc1 100644 --- a/src/helpers/init.js +++ b/src/helpers/init.js @@ -1,8 +1,8 @@ -import { isValidUUID } from './helpers.js'; +import { isValidUUID } from './helpers'; const proxyIPs = ['bpb.yousef.isegaro.com']; -let userID, dohURL, proxyIP, trojanPassword, defaultHttpPorts, defaultHttpsPorts, panelVersion; +let userID, dohURL, proxyIP, trojanPassword, defaultHttpPorts, defaultHttpsPorts, panelVersion, hostName, origin, client, pathName; -function initParams(env) { +function initParams(request, env) { userID = env.UUID || '89b3cbba-e6ac-485a-9481-976a0415eab9'; if (!isValidUUID(userID)) throw new Error(`Invalid UUID: ${userID}`); dohURL = env.DOH_URL || 'https://cloudflare-dns.com/dns-query'; @@ -11,11 +11,17 @@ function initParams(env) { defaultHttpPorts = ['80', '8080', '2052', '2082', '2086', '2095', '8880']; defaultHttpsPorts = ['443', '8443', '2053', '2083', '2087', '2096']; panelVersion = '2.7.2'; + hostName = request.headers.get('Host'); + const url = new URL(request.url); + const searchParams = new URLSearchParams(url.search); + client = searchParams.get('app'); + origin = url.origin; + pathName = url.pathname; } -export function initializeParams(env) { - initParams(env); +export function initializeParams(request, env) { + initParams(request, env); return Promise.resolve(); } -export { userID, dohURL, proxyIP, trojanPassword, defaultHttpPorts, defaultHttpsPorts, panelVersion }; +export { userID, dohURL, proxyIP, trojanPassword, hostName, origin, client, pathName, defaultHttpPorts, defaultHttpsPorts, panelVersion }; diff --git a/src/kv/handlers.js b/src/kv/handlers.js index 0196f1cbe..242537592 100644 --- a/src/kv/handlers.js +++ b/src/kv/handlers.js @@ -1,9 +1,11 @@ -import { fetchWgConfig } from '../protocols/warp.js'; -import { isDomain, resolveDNS } from '../helpers/helpers.js'; -import { initializeParams, panelVersion } from '../helpers/init.js'; +import { fetchWgConfig } from '../protocols/warp'; +import { isDomain, resolveDNS } from '../helpers/helpers'; +import { initializeParams, panelVersion } from '../helpers/init'; +import { Authenticate } from '../authentication/auth'; +import { renderErrorPage } from '../pages/errorPage'; -export async function getDataset(env) { - await initializeParams(env); +export async function getDataset(request, env) { + await initializeParams(request, env); let proxySettings, warpConfigs; if (typeof env.bpb !== 'object') { return {kvNotFound: true, proxySettings: null, warpConfigs: null} @@ -18,20 +20,22 @@ export async function getDataset(env) { } if (!proxySettings) { - proxySettings = await updateDataset(env); + proxySettings = await updateDataset(request, env); const { error, configs } = await fetchWgConfig(env, proxySettings); if (error) throw new Error(`An error occurred while getting Warp configs - ${error}`); warpConfigs = configs; } - if (panelVersion !== proxySettings.panelVersion) proxySettings = await updateDataset(env); + if (panelVersion !== proxySettings.panelVersion) proxySettings = await updateDataset(request, env); return {kvNotFound: false, proxySettings, warpConfigs} } -export async function updateDataset (env, newSettings, resetSettings) { - await initializeParams(env); +export async function updateDataset (request, env) { + await initializeParams(request, env); + let newSettings = await request.formData(); + const isReset = newSettings.get('resetSettings') === 'true'; let currentSettings; - if (!resetSettings) { + if (!isReset) { try { currentSettings = await env.bpb.get("proxySettings", {type: 'json'}); } catch (error) { @@ -40,6 +44,7 @@ export async function updateDataset (env, newSettings, resetSettings) { } } else { await env.bpb.delete('warpConfigs'); + newSettings = null; } const validateField = (field) => { @@ -152,4 +157,23 @@ function extractChainProxyParams(chainProxy) { } return JSON.stringify(configParams); +} + +export async function updateWarpConfigs(request, env) { + const auth = await Authenticate(request, env); + if (!auth) return new Response('Unauthorized', { status: 401 }); + if (request.method === 'POST') { + try { + const { kvNotFound, proxySettings } = await getDataset(request, env); + if (kvNotFound) return await renderErrorPage(request, env, 'KV Dataset is not properly set!', null, true); + const { error: warpPlusError } = await fetchWgConfig(env, proxySettings); + if (warpPlusError) return new Response(warpPlusError, { status: 400 }); + return new Response('Warp configs updated successfully', { status: 200 }); + } catch (error) { + console.log(error); + return new Response(`An error occurred while updating Warp configs! - ${error}`, { status: 500 }); + } + } else { + return new Response('Unsupported request', { status: 405 }); + } } \ No newline at end of file diff --git a/src/pages/errorPage.js b/src/pages/errorPage.js index 8b583abb3..fa1574fe0 100644 --- a/src/pages/errorPage.js +++ b/src/pages/errorPage.js @@ -1,8 +1,8 @@ -import { initializeParams, panelVersion } from "../helpers/init.js"; +import { initializeParams, panelVersion } from "../helpers/init"; -export async function renderErrorPage (env, message, error, refer) { - await initializeParams(env); - return ` +export async function renderErrorPage (request, env, message, error, refer) { + await initializeParams(request, env); + const errorPage = ` @@ -54,4 +54,6 @@ export async function renderErrorPage (env, message, error, refer) { `; + + return new Response(errorPage, { status: 200, headers: {'Content-Type': 'text/html'}}); } \ No newline at end of file diff --git a/src/pages/homePage.js b/src/pages/homePage.js index 19d913f8b..44865eae1 100644 --- a/src/pages/homePage.js +++ b/src/pages/homePage.js @@ -1,7 +1,7 @@ -import { initializeParams, userID, defaultHttpPorts, defaultHttpsPorts, panelVersion } from "../helpers/init.js"; +import { initializeParams, userID, hostName, origin, defaultHttpPorts, defaultHttpsPorts, panelVersion } from "../helpers/init"; -export async function renderHomePage (request, env, hostName, proxySettings, isPassSet) { - await initializeParams(env); +export async function renderHomePage (request, env, proxySettings, isPassSet) { + await initializeParams(request, env); const { remoteDNS, localDNS, @@ -64,7 +64,7 @@ export async function renderHomePage (request, env, hostName, proxySettings, isP defaultHttpsPorts.includes(port) ? httpsPortsBlock += portBlock : httpPortsBlock += portBlock; }); - return ` + const homePage = ` @@ -1519,4 +1519,19 @@ export async function renderHomePage (request, env, hostName, proxySettings, isP `; + + return new Response(homePage, { + status: 200, + headers: { + 'Content-Type': 'text/html;charset=utf-8', + 'Access-Control-Allow-Origin': origin, + 'Access-Control-Allow-Methods': 'GET, POST', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + 'X-Content-Type-Options': 'nosniff', + 'X-Frame-Options': 'DENY', + 'Referrer-Policy': 'strict-origin-when-cross-origin', + 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate, no-transform', + 'CDN-Cache-Control': 'no-store' + } + }); } \ No newline at end of file diff --git a/src/pages/loginPage.js b/src/pages/loginPage.js index facc22a95..a23d425c5 100644 --- a/src/pages/loginPage.js +++ b/src/pages/loginPage.js @@ -1,8 +1,8 @@ -import { initializeParams, panelVersion } from "../helpers/init.js"; +import { initializeParams, origin, panelVersion } from "../helpers/init"; -export async function renderLoginPage (env) { - await initializeParams(env); - return ` +export async function renderLoginPage (request, env) { + await initializeParams(request, env); + const loginPage = ` @@ -146,4 +146,19 @@ export async function renderLoginPage (env) { `; + + return new Response(loginPage, { + status: 200, + headers: { + 'Content-Type': 'text/html;charset=utf-8', + 'Access-Control-Allow-Origin': origin, + 'Access-Control-Allow-Methods': 'GET, POST', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + 'X-Content-Type-Options': 'nosniff', + 'X-Frame-Options': 'DENY', + 'Referrer-Policy': 'strict-origin-when-cross-origin', + 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate, no-transform', + 'CDN-Cache-Control': 'no-store' + } + }); } \ No newline at end of file diff --git a/src/protocols/trojan.js b/src/protocols/trojan.js index 4664487f1..cc9da1080 100644 --- a/src/protocols/trojan.js +++ b/src/protocols/trojan.js @@ -1,9 +1,9 @@ import { connect } from 'cloudflare:sockets'; import sha256 from 'js-sha256'; -import { initializeParams, trojanPassword, proxyIP } from "../helpers/init.js"; +import { initializeParams, trojanPassword, proxyIP, pathName } from "../helpers/init"; export async function trojanOverWSHandler(request, env) { - await initializeParams(env); + await initializeParams(request, env); const webSocketPair = new WebSocketPair(); const [client, webSocket] = Object.values(webSocketPair); webSocket.accept(); @@ -200,8 +200,7 @@ async function handleTCPOutBound( // if the cf connect tcp socket have no incoming data, we retry to redirect ip async function retry() { - const { pathname } = new URL(request.url); - let panelProxyIP = pathname.split('/')[2]; + let panelProxyIP = pathName.split('/')[2]; panelProxyIP = panelProxyIP ? atob(panelProxyIP) : undefined; const tcpSocket = await connectAndWrite(panelProxyIP || proxyIP || addressRemote, portRemote); // no matter retry success or not, close websocket diff --git a/src/protocols/vless.js b/src/protocols/vless.js index 4cd6b8efd..1d4f3c1a7 100644 --- a/src/protocols/vless.js +++ b/src/protocols/vless.js @@ -1,6 +1,6 @@ import { connect } from 'cloudflare:sockets'; -import { isValidUUID } from '../helpers/helpers.js'; -import { initializeParams, userID, dohURL, proxyIP } from "../helpers/init.js"; +import { isValidUUID } from '../helpers/helpers'; +import { initializeParams, userID, dohURL, proxyIP, pathName } from "../helpers/init"; /** * Handles VLESS over WebSocket requests by creating a WebSocket pair, accepting the WebSocket connection, and processing the VLESS header. @@ -10,7 +10,7 @@ import { initializeParams, userID, dohURL, proxyIP } from "../helpers/init.js"; export async function vlessOverWSHandler(request, env) { /** @type {import("@cloudflare/workers-types").WebSocket[]} */ // @ts-ignore - await initializeParams(env); + await initializeParams(request, env); const webSocketPair = new WebSocketPair(); const [client, webSocket] = Object.values(webSocketPair); @@ -176,8 +176,7 @@ async function handleTCPOutBound( // if the cf connect tcp socket have no incoming data, we retry to redirect ip async function retry() { - const { pathname } = new URL(request.url); - let panelProxyIP = pathname.split('/')[2]; + let panelProxyIP = pathName.split('/')[2]; panelProxyIP = panelProxyIP ? atob(panelProxyIP) : undefined; const tcpSocket = await connectAndWrite(panelProxyIP || proxyIP || addressRemote, portRemote); // no matter retry success or not, close websocket diff --git a/src/worker.js b/src/worker.js index 521220542..a04408884 100644 --- a/src/worker.js +++ b/src/worker.js @@ -1,235 +1,64 @@ // https://github.com/bia-pain-bache/BPB-Worker-Panel -import { vlessOverWSHandler } from './protocols/vless.js'; -import { trojanOverWSHandler } from './protocols/trojan.js'; -import { fetchWgConfig } from './protocols/warp.js'; -import { getDataset, updateDataset } from './kv/handlers.js'; -import { generateJWTToken, generateSecretKey, Authenticate } from './authentication/auth.js'; -import { renderHomePage } from './pages/homePage.js'; -import { renderLoginPage } from './pages/loginPage.js'; -import { renderErrorPage } from './pages/errorPage.js'; -import { getXrayCustomConfigs, getXrayWarpConfigs } from './cores-configs/xray.js'; -import { getSingBoxCustomConfig, getSingBoxWarpConfig } from './cores-configs/sing-box.js'; -import { getClashNormalConfig, getClashWarpConfig } from './cores-configs/clash.js'; -import { getNormalConfigs } from './cores-configs/normalConfigs.js'; -import { initializeParams, userID } from './helpers/init.js'; +import { vlessOverWSHandler } from './protocols/vless'; +import { trojanOverWSHandler } from './protocols/trojan'; +import { updateWarpConfigs } from './kv/handlers'; +import { logout, resetPassword, login } from './authentication/auth'; +import { renderErrorPage } from './pages/errorPage'; +import { getXrayCustomConfigs, getXrayWarpConfigs } from './cores-configs/xray'; +import { getSingBoxCustomConfig, getSingBoxWarpConfig } from './cores-configs/sing-box'; +import { getClashNormalConfig, getClashWarpConfig } from './cores-configs/clash'; +import { getNormalConfigs } from './cores-configs/normalConfigs'; +import { initializeParams, userID, client, pathName } from './helpers/init'; +import { fallback, handlePanel } from './helpers/helpers'; export default { async fetch(request, env) { try { const upgradeHeader = request.headers.get('Upgrade'); - const url = new URL(request.url); - if (!upgradeHeader || upgradeHeader !== 'websocket') { - await initializeParams(env); - const hostName = request.headers.get('Host'); - const searchParams = new URLSearchParams(url.search); - const client = searchParams.get('app'); - const clientResponseHeader = { - 'Content-Type': 'text/plain;charset=utf-8', - 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', - 'CDN-Cache-Control': 'no-store' - }; - const pageResponseHeader = { - 'Content-Type': 'text/html;charset=utf-8', - 'Access-Control-Allow-Origin': url.origin, - 'Access-Control-Allow-Methods': 'GET, POST', - 'Access-Control-Allow-Headers': 'Content-Type, Authorization', - 'X-Content-Type-Options': 'nosniff', - 'X-Frame-Options': 'DENY', - 'Referrer-Policy': 'strict-origin-when-cross-origin', - 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate, no-transform', - 'CDN-Cache-Control': 'no-store' - }; - const { kvNotFound, proxySettings, warpConfigs } = await getDataset(env); - if (kvNotFound) { - const errorPage = await renderErrorPage(env, 'KV Dataset is not properly set!', null, true); - return new Response(errorPage, { status: 200, headers: {'Content-Type': 'text/html'}}); - } - - - switch (url.pathname) { + await initializeParams(request, env); + if (!upgradeHeader || upgradeHeader !== 'websocket') { + switch (pathName) { case '/update-warp': - const Auth = await Authenticate(request, env); - if (!Auth) return new Response('Unauthorized', { status: 401 }); - if (request.method === 'POST') { - try { - const { error: warpPlusError } = await fetchWgConfig(env, proxySettings); - if (warpPlusError) { - return new Response(warpPlusError, { status: 400 }); - } else { - return new Response('Warp configs updated successfully', { status: 200 }); - } - } catch (error) { - console.log(error); - return new Response(`An error occurred while updating Warp configs! - ${error}`, { status: 500 }); - } - - } else { - return new Response('Unsupported request', { status: 405 }); - } + return await updateWarpConfigs(request, env); case `/sub/${userID}`: - if (client === 'sfa') { - const BestPingSFA = await getSingBoxCustomConfig(env, hostName, proxySettings, false); - return new Response(JSON.stringify(BestPingSFA, null, 4), { - status: 200, - headers: clientResponseHeader - }); - } - - if (client === 'clash') { - const BestPingClash = await getClashNormalConfig(env, hostName, proxySettings); - return new Response(JSON.stringify(BestPingClash, null, 4), { - status: 200, - headers: clientResponseHeader - }); - } - - if (client === 'xray') { - const xrayFullConfigs = await getXrayCustomConfigs(env, hostName, proxySettings, false); - return new Response(JSON.stringify(xrayFullConfigs, null, 4), { - status: 200, - headers: clientResponseHeader - }); - } - - const normalConfigs = await getNormalConfigs(env, hostName, proxySettings, client); - return new Response(normalConfigs, { - status: 200, - headers: clientResponseHeader - }); + if (client === 'sfa') return await getSingBoxCustomConfig(request, env, false); + if (client === 'clash') return await getClashNormalConfig(request, env); + if (client === 'xray') return await getXrayCustomConfigs(request, env, false); + return await getNormalConfigs(request, env); case `/fragsub/${userID}`: - let fragConfigs = client === 'hiddify' - ? await getSingBoxCustomConfig(env, hostName, proxySettings, true) - : await getXrayCustomConfigs(env, hostName, proxySettings, true); - - return new Response(JSON.stringify(fragConfigs, null, 4), { - status: 200, - headers: clientResponseHeader - }); + return client === 'hiddify' + ? await getSingBoxCustomConfig(request, env, true) + : await getXrayCustomConfigs(request, env, true); case `/warpsub/${userID}`: - if (client === 'clash') { - const clashWarpConfig = await getClashWarpConfig(proxySettings, warpConfigs); - return new Response(JSON.stringify(clashWarpConfig, null, 4), { - status: 200, - headers: clientResponseHeader - }); - } - - if (client === 'singbox' || client === 'hiddify') { - const singboxWarpConfig = await getSingBoxWarpConfig(proxySettings, warpConfigs, client); - return new Response(JSON.stringify(singboxWarpConfig, null, 4), { - status: 200, - headers: clientResponseHeader - }); - } - - const warpConfig = await getXrayWarpConfigs(proxySettings, warpConfigs, client); - return new Response(JSON.stringify(warpConfig, null, 4), { - status: 200, - headers: clientResponseHeader - }); + if (client === 'clash') return await getClashWarpConfig(request, env); + if (client === 'singbox' || client === 'hiddify') return await getSingBoxWarpConfig(request, env, client); + return await getXrayWarpConfigs(request, env, client); case '/panel': - const isAuth = await Authenticate(request, env); - if (request.method === 'POST') { - if (!isAuth) return new Response('Unauthorized or expired session!', { status: 401 }); - const formData = await request.formData(); - const isReset = formData.get('resetSettings') === 'true'; - isReset - ? await updateDataset(env, null, true) - : await updateDataset(env, formData); - - return new Response('Success', { status: 200 }); - } - - const pwd = await env.bpb.get('pwd'); - if (pwd && !isAuth) return Response.redirect(`${url.origin}/login`, 302); - const isPassSet = pwd?.length >= 8; - const homePage = await renderHomePage(request, env, hostName, proxySettings, isPassSet); - return new Response(homePage, { - status: 200, - headers: pageResponseHeader - }); + return await handlePanel(request, env); case '/login': - if (typeof env.bpb !== 'object') { - const errorPage = await renderErrorPage(env, 'KV Dataset is not properly set!', null, true); - return new Response(errorPage, { status: 200, headers: {'Content-Type': 'text/html'}}); - } - - const loginAuth = await Authenticate(request, env); - if (loginAuth) return Response.redirect(`${url.origin}/panel`, 302); - let secretKey = await env.bpb.get('secretKey'); - if (!secretKey) { - secretKey = generateSecretKey(); - await env.bpb.put('secretKey', secretKey); - } - - if (request.method === 'POST') { - const password = await request.text(); - const savedPass = await env.bpb.get('pwd'); - - if (password === savedPass) { - const jwtToken = await generateJWTToken(env, secretKey); - const cookieHeader = `jwtToken=${jwtToken}; HttpOnly; Secure; Max-Age=${7 * 24 * 60 * 60}; Path=/; SameSite=Strict`; - return new Response('Success', { - status: 200, - headers: { - 'Set-Cookie': cookieHeader, - 'Content-Type': 'text/plain', - } - }); - } else { - return new Response('Method Not Allowed', { status: 405 }); - } - } - - const loginPage = await renderLoginPage(env); - return new Response(loginPage, { - status: 200, - headers: pageResponseHeader - }); + return await login(request, env); case '/logout': - return new Response('Success', { - status: 200, - headers: { - 'Set-Cookie': 'jwtToken=; Secure; SameSite=None; Expires=Thu, 01 Jan 1970 00:00:00 GMT', - 'Content-Type': 'text/plain' - } - }); + return logout(); case '/panel/password': - const oldPwd = await env.bpb.get('pwd'); - let passAuth = await Authenticate(request, env); - if (oldPwd && !passAuth) return new Response('Unauthorized!', { status: 401 }); - const newPwd = await request.text(); - if (newPwd === oldPwd) return new Response('Please enter a new Password!', { status: 400 }); - await env.bpb.put('pwd', newPwd); - return new Response('Success', { - status: 200, - headers: { - 'Set-Cookie': 'jwtToken=; Path=/; Secure; SameSite=None; Expires=Thu, 01 Jan 1970 00:00:00 GMT', - 'Content-Type': 'text/plain', - } - }); + return await resetPassword(request, env); default: - url.hostname = 'www.speedtest.net'; - url.protocol = 'https:'; - request = new Request(url, request); - return await fetch(request); + return await fallback(request); } } else { - return url.pathname.startsWith('/tr') + return pathName.startsWith('/tr') ? await trojanOverWSHandler(request, env) : await vlessOverWSHandler(request, env); } } catch (err) { - const errorPage = await renderErrorPage(env, 'Something went wrong!', err, false); - return new Response(errorPage, { status: 200, headers: {'Content-Type': 'text/html'}}); + return await renderErrorPage(request, env, 'Something went wrong!', err, false); } } }; \ No newline at end of file