hfs
Version:
HTTP File Server
204 lines (203 loc) • 9.42 kB
JavaScript
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.title = exports.favicon = exports.adminNet = exports.localhostAdmin = exports.adminApis = void 0;
exports.ctxAdminAccess = ctxAdminAccess;
exports.anyAccountCanLoginAdmin = anyAccountCanLoginAdmin;
exports.allowAdmin = allowAdmin;
const apiMiddleware_1 = require("./apiMiddleware");
const config_1 = require("./config");
const listen_1 = require("./listen");
const const_1 = require("./const");
const api_vfs_1 = __importDefault(require("./api.vfs"));
const api_accounts_1 = __importDefault(require("./api.accounts"));
const api_plugins_1 = __importDefault(require("./api.plugins"));
const api_monitor_1 = __importDefault(require("./api.monitor"));
const api_lang_1 = __importDefault(require("./api.lang"));
const api_net_1 = __importDefault(require("./api.net"));
const api_log_1 = __importDefault(require("./api.log"));
const api_cert_1 = __importDefault(require("./api.cert"));
const connections_1 = require("./connections");
const misc_1 = require("./misc");
const perm_1 = require("./perm");
const middlewares_1 = require("./middlewares");
const child_process_1 = require("child_process");
const util_1 = require("util");
const customHtml_1 = require("./customHtml");
const lodash_1 = __importDefault(require("lodash"));
const update_1 = require("./update");
const path_1 = require("path");
const errorPages_1 = require("./errorPages");
const geo_1 = require("./geo");
const roots_1 = require("./roots");
const SendList_1 = require("./SendList");
const ddns_1 = require("./ddns");
const block_1 = require("./block");
const github_1 = require("./github");
const acme_1 = require("./acme");
exports.adminApis = {
...api_vfs_1.default,
...api_accounts_1.default,
...api_plugins_1.default,
...api_monitor_1.default,
...api_lang_1.default,
...api_net_1.default,
...api_log_1.default,
...api_cert_1.default,
get_dynamic_dns_error: ddns_1.get_dynamic_dns_error,
async set_config({ values }) {
var _a;
(0, misc_1.apiAssertTypes)({ object: { values } });
(0, config_1.setConfig)(values);
if (values.port === 0 || values.https_port === 0)
return (_a = await (0, misc_1.waitFor)(async () => {
const st = await (0, listen_1.getServerStatus)();
// wait for all random ports to be done, so we communicate new numbers
if ((values.port !== 0 || st.http.listening)
&& (values.https_port !== 0 || st.https.listening))
return st;
}, { timeout: 1000 })) !== null && _a !== void 0 ? _a : new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, "something went wrong changing ports");
return {};
},
get_config: config_1.getWholeConfig,
get_config_text() {
return {
path: config_1.configFile.getPath(),
fullPath: (0, path_1.resolve)(config_1.configFile.getPath()),
text: config_1.configFile.getText(),
customHtml: customHtml_1.customHtml.getText(),
};
},
set_config_text: ({ text }) => config_1.configFile.save(text, { reparse: true }),
update: ({ tag }) => (0, update_1.update)(tag).catch(e => {
var _a, _b;
throw ((_a = e.cause) === null || _a === void 0 ? void 0 : _a.statusCode) ? new apiMiddleware_1.ApiError((_b = e.cause) === null || _b === void 0 ? void 0 : _b.statusCode) : e;
}),
async check_update() {
return { options: await (0, update_1.getUpdates)() };
},
async get_other_versions() {
let left = 50;
const res = await (0, update_1.getVersions)(r => !r.prerelease && !left--);
return { options: res.filter(x => !x.prerelease) };
},
async wait_project_info() {
await (0, github_1.getProjectInfo)();
return {};
},
async ip_country({ ips }) {
const res = await Promise.allSettled(ips.map(geo_1.ip2country));
return {
codes: res.map(x => x.status === 'rejected' || x.value === '-' ? '' : x.value)
};
},
is_ip_blocked({ ips }) {
(0, misc_1.apiAssertTypes)({
array: { ips },
string: { ips0: ips[0] }
});
return { blocked: ips.map((x) => (0, block_1.isBlocked)(x) ? 1 : 0) };
},
get_custom_html() {
return {
enabled: !customHtml_1.disableCustomHtml.get(),
sections: Object.fromEntries([
...customHtml_1.customHtmlSections.concat((0, errorPages_1.getErrorSections)()).map(k => [k, '']), // be sure to output all sections
...customHtml_1.customHtml.sections // override entries above
]),
};
},
async set_custom_html({ sections }) {
await (0, customHtml_1.saveCustomHtml)(sections);
return {};
},
quit() {
setTimeout(() => process.exit());
return {};
},
async get_status() {
return {
started: const_1.HFS_STARTED,
build: const_1.BUILD_TIMESTAMP,
version: const_1.VERSION,
apiVersion: const_1.API_VERSION,
compatibleApiVersion: const_1.COMPATIBLE_API_VERSION,
...await (0, listen_1.getServerStatus)(false),
platform: process.platform,
urls: await (0, listen_1.getUrls)(),
ips: await (0, listen_1.getIps)(false),
baseUrl: await (0, listen_1.getBaseUrlOrDefault)(),
roots: roots_1.roots.get(),
updatePossible: !await (0, update_1.updateSupported)() ? false : (await (0, update_1.localUpdateAvailable)()) ? 'local' : true,
previousVersionAvailable: await (0, update_1.previousAvailable)(),
autoCheckUpdateResult: update_1.autoCheckUpdateResult.get(), // in this form, we get the same type of the serialized json
alerts: github_1.alerts.get(),
proxyDetected: (0, middlewares_1.getProxyDetected)(),
cloudflareDetected: middlewares_1.cloudflareDetected,
ram: process.memoryUsage.rss(),
acmeRenewError: acme_1.acmeRenewError,
blacklistedInstalledPlugins: github_1.blacklistedInstalledPlugins,
frpDetected: exports.localhostAdmin.get() && !(0, middlewares_1.getProxyDetected)()
&& (0, connections_1.getConnections)().every(misc_1.isLocalHost)
&& await frpDebounced(),
};
},
async add_block({ merge, ip, expire, comment }) {
(0, misc_1.apiAssertTypes)({
string: { ip },
string_undefined: { comment, expire },
object_undefined: { merge },
});
const optionals = lodash_1.default.pickBy({ expire, comment }, v => v !== undefined); // passing undefined-s would override values in merge
(0, block_1.addBlock)({ ip, ...optionals }, merge);
return {};
},
async geo_ip({ ip }) {
(0, misc_1.apiAssertTypes)({ string: { ip } });
return { country: await (0, geo_1.ip2country)(ip) };
},
validate_net_mask({ mask }) {
(0, misc_1.apiAssertTypes)({ string: { mask } });
return { result: Boolean((0, misc_1.try_)(() => (0, misc_1.makeNetMatcher)(mask))) };
},
};
for (const [k, was] of (0, misc_1.typedEntries)(exports.adminApis))
exports.adminApis[k] = ((params, ctx) => {
if (!allowAdmin(ctx))
return new apiMiddleware_1.ApiError(const_1.HTTP_FORBIDDEN);
if (ctxAdminAccess(ctx))
return was(params, ctx);
const props = { possible: anyAccountCanLoginAdmin() };
return ctx.headers.accept === 'text/event-stream'
? new SendList_1.SendListReadable({ doAtStart: x => x.error(const_1.HTTP_UNAUTHORIZED, true, props) })
: new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED, props);
});
exports.localhostAdmin = (0, config_1.defineConfig)('localhost_admin', true);
exports.adminNet = (0, config_1.defineConfig)('admin_net', '', v => (0, misc_1.makeNetMatcher)(v, true));
exports.favicon = (0, config_1.defineConfig)('favicon', '');
exports.title = (0, config_1.defineConfig)('title', "File server");
function ctxAdminAccess(ctx) {
return !ctx.ips.length // we consider localhost_admin only if no proxy is being used
&& exports.localhostAdmin.get() && (0, misc_1.isLocalHost)(ctx)
|| ctx.state.account && (0, perm_1.accountCanLoginAdmin)(ctx.state.account);
}
const frpDebounced = (0, misc_1.debounceAsync)(async () => {
if (!const_1.IS_WINDOWS)
return false;
try { // guy with win11 reported missing tasklist, so don't take it for granted
const { stdout } = await (0, util_1.promisify)(child_process_1.execFile)('tasklist', ['/fi', 'imagename eq frpc.exe', '/nh']);
return stdout.includes('frpc');
}
catch (_a) {
return false;
}
}, { retain: 10000 });
function anyAccountCanLoginAdmin() {
return Boolean(lodash_1.default.find(perm_1.accounts.get(), perm_1.accountCanLoginAdmin));
}
function allowAdmin(ctx) {
return (0, misc_1.isLocalHost)(ctx) || exports.adminNet.compiled()(ctx.ip);
}
;