summaryrefslogtreecommitdiffstats
path: root/server/proxy.js
diff options
context:
space:
mode:
Diffstat (limited to 'server/proxy.js')
-rw-r--r--server/proxy.js202
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,
+};