mirror of
				https://github.com/louislam/uptime-kuma.git
				synced 2025-11-04 13:46:13 +08:00 
			
		
		
		
	* feat: Set and send cookies on redirection (louislam#3587). * feat: Make proxy agents handle cookies * Merge package-lock.json * Merge package-lock.json * Fix lint --------- Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
		
			
				
	
	
		
			203 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			203 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
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,
 | 
						|
};
 |