mirror of
https://github.com/bolucat/Archive.git
synced 2025-09-27 04:30:12 +08:00
168 lines
6.2 KiB
JavaScript
168 lines
6.2 KiB
JavaScript
import { callosum, event, utilitas } from 'utilitas';
|
|
import { render } from './log.mjs';
|
|
|
|
import {
|
|
CLRF, ERROR_CODES, HEADERS, HTTP_BODIES, HTTP_METHODS, HTTP_RESPONSES,
|
|
IDLE_CLIENT_TIMEOUT, MAX_CLIENT_COUNT
|
|
} from './consts.mjs';
|
|
|
|
const [SECURITY_LOG, BYPASS_LIST, MAX_BYPASS_COUNT]
|
|
= ['SECURITY_LOG', 'BYPASS_LIST', 1000];
|
|
const { AUTH_REQUIRED, NOT_FOUND, NOT_OK, OK, TIMED_OUT, UNAUTHORIZED, }
|
|
= HTTP_RESPONSES;
|
|
const { ENOTFOUND, EPIPE, EPROTO, ETIMEDOUT } = ERROR_CODES;
|
|
const { GET } = HTTP_METHODS;
|
|
const packResult = (status, body) => ({ status, body: body || '' });
|
|
const log = message => utilitas.log(message, import.meta.url, { time: true });
|
|
const lastVisit = date => ({ lastVisit: date ? new Date(date) : new Date() });
|
|
const getSecurityLog = async address => await getMapping(SECURITY_LOG, address);
|
|
const getBypassList = async address => await getMapping(BYPASS_LIST, address);
|
|
const getBypassHost = async () => Object.keys(await getBypassList());
|
|
const setSecurityLog = async (add, d) => await setMapping(SECURITY_LOG, add, d);
|
|
const setBypassList = async (add, d) => await setMapping(BYPASS_LIST, add, d);
|
|
const getAllMapping = () => Promise.all([getSecurityLog(), getBypassList()]);
|
|
|
|
function FindProxyForURL(url, host) {
|
|
|
|
// Socratex by @LeaskH
|
|
// https://github.com/Leask/socratex
|
|
|
|
const [local, bypass] = [[
|
|
'*.lan', '*.local', '*.internal',
|
|
'10.*.*.*', '127.*.*.*', '172.16.*.*', '192.168.*.*',
|
|
], {
|
|
/*BYPASS*/
|
|
}];
|
|
|
|
for (let item of local) { if (shExpMatch(host, item)) { return 'DIRECT'; } }
|
|
if (isPlainHostName(host) || bypass[host]) { return 'DIRECT'; }
|
|
return '/*PROXY*/';
|
|
|
|
};
|
|
|
|
const setMapping = async (key, address, date) => await callosum.assign(
|
|
key, { [address]: lastVisit(date) }
|
|
);
|
|
|
|
const getMapping = async (key, address) => {
|
|
const resp = await callosum.get(key, ...address ? [address] : []);
|
|
return resp || (address ? null : {});
|
|
};
|
|
|
|
const makePac = (bypass, proxy) => {
|
|
let [rules, pac] = [{
|
|
'/*BYPASS*/': bypass.map(x => `'${x}': 1`).join(',\n '),
|
|
'/*PROXY*/': proxy, // '; DIRECT' // @todo by @LeaskH
|
|
}, FindProxyForURL.toString()];
|
|
for (let i in rules) {
|
|
pac = pac.replace(new RegExp(RegExp.escape(i), 'ig'), rules[i]);
|
|
}
|
|
return pac;
|
|
};
|
|
|
|
const route = async (method, path, protocol, req) => {
|
|
const objUrl = new URL(path, `https://${globalThis._socratex?.domain}`);
|
|
const token = objUrl.searchParams.get('token');
|
|
const authenticated = token === globalThis._socratex?.token;
|
|
const reeStr = [
|
|
String(method).toUpperCase(), String(objUrl.pathname).toLowerCase(),
|
|
].join(' ');
|
|
switch (reeStr) {
|
|
case `${GET} /`:
|
|
return packResult(OK, '42');
|
|
case `${GET} /favicon.ico`:
|
|
return packResult(OK, '');
|
|
case `${GET} /console`:
|
|
return authenticated
|
|
? packResult(OK, await render())
|
|
: await error({ code: UNAUTHORIZED });
|
|
case `${GET} /proxy.pac`:
|
|
case `${GET} /wpad.dat`:
|
|
return authenticated ? packResult(`${OK}${CLRF}${HEADERS.PAC}`,
|
|
makePac(await getBypassHost(), _socratex.address)
|
|
) : await error({ code: UNAUTHORIZED });
|
|
default:
|
|
return await error({ code: ENOTFOUND });
|
|
}
|
|
};
|
|
|
|
const error = async (err) => {
|
|
switch (err.code) {
|
|
case ETIMEDOUT:
|
|
return packResult(TIMED_OUT);
|
|
case ENOTFOUND:
|
|
return packResult(NOT_FOUND, HTTP_BODIES.NOT_FOUND);
|
|
case EPROTO:
|
|
return packResult(NOT_OK, HTTP_BODIES.NOT_FOUND);
|
|
case UNAUTHORIZED:
|
|
return packResult(UNAUTHORIZED, HTTP_BODIES.UNAUTHORIZED);
|
|
case AUTH_REQUIRED:
|
|
return packResult(AUTH_REQUIRED, HTTP_BODIES.AUTH_REQUIRED);
|
|
case EPIPE: default:
|
|
return packResult(NOT_OK);
|
|
}
|
|
};
|
|
|
|
const init = async (options) => {
|
|
Function.isFunction(options.getStatus) && (async () => {
|
|
const config = (await utilitas.resolve(options.getStatus()))?.config;
|
|
const sLog = config?.securityLog || {};
|
|
const list = config?.bypassList || {};
|
|
for (let i in sLog) { await setSecurityLog(i, sLog[i].lastVisit); }
|
|
for (let i in list) { await setBypassList(i, list[i].lastVisit); }
|
|
const [securityLog, bypassList] = await getAllMapping();
|
|
log(`Restored ${Object.keys(securityLog).length} session(s), `
|
|
+ `${Object.keys(bypassList).length} bypass-item(s).`);
|
|
})();
|
|
const releaseClient = async (add) => {
|
|
await callosum.del(SECURITY_LOG, add);
|
|
log(`Released inactive client: ${add}.`);
|
|
};
|
|
const removeBypass = async (host) => {
|
|
await callosum.del(BYPASS_LIST, host);
|
|
log(`Remove inactive bypass-item: ${host}.`);
|
|
};
|
|
const cleanStatus = async () => {
|
|
const [now, ips, hosts] = [new Date(), [], []];
|
|
let [securityLog, bypassList] = await getAllMapping();
|
|
// clean security log
|
|
for (let a in securityLog) {
|
|
if (securityLog[a].lastVisit + IDLE_CLIENT_TIMEOUT < now) {
|
|
await releaseClient(a);
|
|
} else { ips.push([securityLog[a].lastVisit, a]); }
|
|
}
|
|
ips.sort((a, b) => a[0] - b[0]);
|
|
while (ips.length > MAX_CLIENT_COUNT) {
|
|
await releaseClient(ips.shift()[1]);
|
|
}
|
|
// clean bypass list
|
|
for (let h in bypassList) { hosts.push([bypassList[h].lastVisit, h]); }
|
|
hosts.sort((a, b) => a[0] - b[0]);
|
|
while (hosts.length > MAX_BYPASS_COUNT) {
|
|
await removeBypass(hosts.shift()[1]);
|
|
}
|
|
// save
|
|
[securityLog, bypassList] = await getAllMapping();
|
|
if (!Function.isFunction(options.setStatus)) { return; }
|
|
await options.setStatus({ securityLog, bypassList });
|
|
log(`Saved ${Object.keys(securityLog).length} session(s), `
|
|
+ `${Object.keys(bypassList).length} bypass-item(s).`);
|
|
};
|
|
return await event.loop(
|
|
cleanStatus, 60, 60, 0, utilitas.basename(import.meta.url),
|
|
{ silent: true }
|
|
);
|
|
};
|
|
|
|
export default init;
|
|
export {
|
|
error,
|
|
getBypassHost,
|
|
getBypassList,
|
|
getSecurityLog,
|
|
init,
|
|
route,
|
|
setBypassList,
|
|
setSecurityLog,
|
|
};
|