diff options
Diffstat (limited to 'server/proxy.js')
-rw-r--r-- | server/proxy.js | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/server/proxy.js b/server/proxy.js new file mode 100644 index 0000000..3f3771a --- /dev/null +++ b/server/proxy.js @@ -0,0 +1,202 @@ +const { R } = require("redbean-node"); +const HttpProxyAgent = require("http-proxy-agent"); +const HttpsProxyAgent = require("https-proxy-agent"); +const SocksProxyAgent = require("socks-proxy-agent"); +const { debug } = require("../src/util"); +const { UptimeKumaServer } = require("./uptime-kuma-server"); +const { CookieJar } = require("tough-cookie"); +const { createCookieAgent } = require("http-cookie-agent/http"); + +class Proxy { + + static SUPPORTED_PROXY_PROTOCOLS = [ "http", "https", "socks", "socks5", "socks5h", "socks4" ]; + + /** + * Saves and updates given proxy entity + * @param {object} proxy Proxy to store + * @param {number} proxyID ID of proxy to update + * @param {number} userID ID of user the proxy belongs to + * @returns {Promise<Bean>} Updated proxy + */ + static async save(proxy, proxyID, userID) { + let bean; + + if (proxyID) { + bean = await R.findOne("proxy", " id = ? AND user_id = ? ", [ proxyID, userID ]); + + if (!bean) { + throw new Error("proxy not found"); + } + + } else { + bean = R.dispense("proxy"); + } + + // Make sure given proxy protocol is supported + if (!this.SUPPORTED_PROXY_PROTOCOLS.includes(proxy.protocol)) { + throw new Error(` + Unsupported proxy protocol "${proxy.protocol}. + Supported protocols are ${this.SUPPORTED_PROXY_PROTOCOLS.join(", ")}."` + ); + } + + // When proxy is default update deactivate old default proxy + if (proxy.default) { + await R.exec("UPDATE proxy SET `default` = 0 WHERE `default` = 1"); + } + + bean.user_id = userID; + bean.protocol = proxy.protocol; + bean.host = proxy.host; + bean.port = proxy.port; + bean.auth = proxy.auth; + bean.username = proxy.username; + bean.password = proxy.password; + bean.active = proxy.active || true; + bean.default = proxy.default || false; + + await R.store(bean); + + if (proxy.applyExisting) { + await applyProxyEveryMonitor(bean.id, userID); + } + + return bean; + } + + /** + * Deletes proxy with given id and removes it from monitors + * @param {number} proxyID ID of proxy to delete + * @param {number} userID ID of proxy owner + * @returns {Promise<void>} + */ + static async delete(proxyID, userID) { + const bean = await R.findOne("proxy", " id = ? AND user_id = ? ", [ proxyID, userID ]); + + if (!bean) { + throw new Error("proxy not found"); + } + + // Delete removed proxy from monitors if exists + await R.exec("UPDATE monitor SET proxy_id = null WHERE proxy_id = ?", [ proxyID ]); + + // Delete proxy from list + await R.trash(bean); + } + + /** + * Create HTTP and HTTPS agents related with given proxy bean object + * @param {object} proxy proxy bean object + * @param {object} options http and https agent options + * @returns {{httpAgent: Agent, httpsAgent: Agent}} New HTTP and HTTPS agents + * @throws Proxy protocol is unsupported + */ + static createAgents(proxy, options) { + const { httpAgentOptions, httpsAgentOptions } = options || {}; + let agent; + let httpAgent; + let httpsAgent; + + let jar = new CookieJar(); + + const proxyOptions = { + protocol: proxy.protocol, + host: proxy.host, + port: proxy.port, + cookies: { jar }, + }; + + if (proxy.auth) { + proxyOptions.auth = `${proxy.username}:${proxy.password}`; + } + + debug(`Proxy Options: ${JSON.stringify(proxyOptions)}`); + debug(`HTTP Agent Options: ${JSON.stringify(httpAgentOptions)}`); + debug(`HTTPS Agent Options: ${JSON.stringify(httpsAgentOptions)}`); + + switch (proxy.protocol) { + case "http": + case "https": + // eslint-disable-next-line no-case-declarations + const HttpCookieProxyAgent = createCookieAgent(HttpProxyAgent); + // eslint-disable-next-line no-case-declarations + const HttpsCookieProxyAgent = createCookieAgent(HttpsProxyAgent); + + httpAgent = new HttpCookieProxyAgent({ + ...httpAgentOptions || {}, + ...proxyOptions, + }); + + httpsAgent = new HttpsCookieProxyAgent({ + ...httpsAgentOptions || {}, + ...proxyOptions, + }); + break; + case "socks": + case "socks5": + case "socks5h": + case "socks4": + // eslint-disable-next-line no-case-declarations + const SocksCookieProxyAgent = createCookieAgent(SocksProxyAgent); + agent = new SocksCookieProxyAgent({ + ...httpAgentOptions, + ...httpsAgentOptions, + ...proxyOptions, + tls: { + rejectUnauthorized: httpsAgentOptions.rejectUnauthorized, + }, + }); + + httpAgent = agent; + httpsAgent = agent; + break; + + default: throw new Error(`Unsupported proxy protocol provided. ${proxy.protocol}`); + } + + return { + httpAgent, + httpsAgent + }; + } + + /** + * Reload proxy settings for current monitors + * @returns {Promise<void>} + */ + static async reloadProxy() { + const server = UptimeKumaServer.getInstance(); + + let updatedList = await R.getAssoc("SELECT id, proxy_id FROM monitor"); + + for (let monitorID in server.monitorList) { + let monitor = server.monitorList[monitorID]; + + if (updatedList[monitorID]) { + monitor.proxy_id = updatedList[monitorID].proxy_id; + } + } + } +} + +/** + * Applies given proxy id to monitors + * @param {number} proxyID ID of proxy to apply + * @param {number} userID ID of proxy owner + * @returns {Promise<void>} + */ +async function applyProxyEveryMonitor(proxyID, userID) { + // Find all monitors with id and proxy id + const monitors = await R.getAll("SELECT id, proxy_id FROM monitor WHERE user_id = ?", [ userID ]); + + // Update proxy id not match with given proxy id + for (const monitor of monitors) { + if (monitor.proxy_id !== proxyID) { + await R.exec("UPDATE monitor SET proxy_id = ? WHERE id = ?", [ proxyID, monitor.id ]); + } + } +} + +module.exports = { + Proxy, +}; |