hfs 0.52.5 → 0.53.0-alpha2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/assets/index-Dsv2tjnU.js +805 -0
- package/admin/assets/{sha512-7RFRcaOB.js → sha512-BjLhiBem.js} +1 -1
- package/admin/index.html +2 -2
- package/frontend/assets/index-legacy-C21Ot_ue.js +60 -0
- package/frontend/assets/polyfills-legacy-DMrMt_pQ.js +4 -0
- package/frontend/assets/sha512-legacy-DYmEZJfO.js +9 -0
- package/frontend/index.html +3 -2
- package/package.json +1 -1
- package/plugins/antibrute/plugin.js +34 -23
- package/src/QuickZipStream.js +1 -1
- package/src/SendList.js +1 -3
- package/src/api.accounts.js +2 -2
- package/src/api.auth.js +7 -2
- package/src/api.get_file_list.js +1 -1
- package/src/api.log.js +2 -6
- package/src/api.plugins.js +22 -23
- package/src/api.vfs.js +1 -1
- package/src/auth.js +14 -5
- package/src/comments.js +5 -1
- package/src/config.js +15 -21
- package/src/const.js +2 -2
- package/src/cross.js +6 -8
- package/src/ddns.js +19 -18
- package/src/events.js +56 -5
- package/src/geo.js +1 -1
- package/src/github.js +21 -5
- package/src/index.js +1 -1
- package/src/langs/hfs-lang-it.json +7 -3
- package/src/listen.js +1 -1
- package/src/log.js +4 -0
- package/src/middlewares.js +8 -5
- package/src/misc.js +2 -15
- package/src/perm.js +15 -10
- package/src/plugins.js +5 -5
- package/src/roots.js +15 -23
- package/src/serveGuiAndSharedFiles.js +11 -8
- package/src/serveGuiFiles.js +9 -15
- package/src/throttler.js +11 -9
- package/src/upload.js +7 -1
- package/admin/assets/index-V2liwvHg.js +0 -811
- package/frontend/assets/index-c8LVGHJd.js +0 -101
- package/frontend/assets/index-oekM6dN8.css +0 -1
- package/frontend/assets/sha512-PGV0A--c.js +0 -8
- /package/admin/assets/{index-sCDewjON.css → index-CwIN7CM4.css} +0 -0
package/src/ddns.js
CHANGED
|
@@ -12,42 +12,43 @@ const node_net_1 = require("node:net");
|
|
|
12
12
|
const net_1 = require("net");
|
|
13
13
|
const const_1 = require("./const");
|
|
14
14
|
const events_1 = __importDefault(require("./events"));
|
|
15
|
-
const stream_1 = require("stream");
|
|
16
15
|
const nat_1 = require("./nat");
|
|
17
16
|
// optionally you can append '>' and a regular expression to determine what body is considered successful
|
|
18
17
|
const dynamicDnsUrl = (0, config_1.defineConfig)(cross_1.CFG.dynamic_dns_url, '');
|
|
19
18
|
let stop;
|
|
20
|
-
let last;
|
|
21
19
|
dynamicDnsUrl.sub(v => {
|
|
22
20
|
stop === null || stop === void 0 ? void 0 : stop();
|
|
23
21
|
if (!v)
|
|
24
22
|
return;
|
|
25
23
|
let lastIps;
|
|
26
|
-
const [templateUrl, re] = (0, cross_1.splitAt)('>', v);
|
|
27
24
|
stop = (0, cross_1.repeat)(cross_1.HOUR, async () => {
|
|
28
25
|
const ips = await (0, nat_1.getPublicIps)();
|
|
29
26
|
if (lodash_1.default.isEqual(lastIps, ips))
|
|
30
27
|
return;
|
|
31
28
|
lastIps = ips;
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
29
|
+
const all = await Promise.all(v.split('\n').map(async (line) => {
|
|
30
|
+
const [templateUrl, re] = (0, cross_1.splitAt)('>', line);
|
|
31
|
+
const url = (0, cross_1.replace)(templateUrl, {
|
|
32
|
+
IPX: ips[0] || '',
|
|
33
|
+
IP4: lodash_1.default.find(ips, node_net_1.isIPv4) || '',
|
|
34
|
+
IP6: lodash_1.default.find(ips, net_1.isIPv6) || '',
|
|
35
|
+
}, '$');
|
|
36
|
+
const error = await (0, util_http_1.httpWithBody)(url, { httpThrow: false, headers: { 'User-Agent': "HFS/" + const_1.VERSION } }) // UA specified as requested by no-ip guidelines
|
|
37
|
+
.then(async (res) => {
|
|
38
|
+
const str = String(res.body).trim();
|
|
39
|
+
return (re ? str.match(re) : res.ok) ? '' : (str || res.statusMessage);
|
|
40
|
+
}, (err) => err.code || err.message || String(err));
|
|
41
|
+
return { ts: new Date().toJSON(), error, url };
|
|
42
|
+
}));
|
|
43
|
+
const best = lodash_1.default.find(all, 'error') || all[0]; // the system is designed for just one result, and we give precedence to errors
|
|
44
|
+
events_1.default.emit('dynamicDnsError', best);
|
|
45
|
+
console.log('dynamic dns update', (best === null || best === void 0 ? void 0 : best.error) || 'ok');
|
|
45
46
|
});
|
|
46
47
|
});
|
|
47
48
|
async function* get_dynamic_dns_error() {
|
|
48
49
|
while (1) {
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
const res = await events_1.default.once('dynamicDnsError');
|
|
51
|
+
yield res[0];
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
exports.get_dynamic_dns_error = get_dynamic_dns_error;
|
package/src/events.js
CHANGED
|
@@ -1,9 +1,60 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
-
};
|
|
6
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
|
|
4
|
+
exports.BetterEventEmitter = void 0;
|
|
5
|
+
class BetterEventEmitter {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.listeners = new Map();
|
|
8
|
+
}
|
|
9
|
+
on(event, listener, { warnAfter = 10 } = {}) {
|
|
10
|
+
if (typeof event === 'string')
|
|
11
|
+
event = [event];
|
|
12
|
+
for (const e of event) {
|
|
13
|
+
let cbs = this.listeners.get(e);
|
|
14
|
+
if (!cbs)
|
|
15
|
+
this.listeners.set(e, cbs = new Set());
|
|
16
|
+
cbs.add(listener);
|
|
17
|
+
if (cbs.size > warnAfter)
|
|
18
|
+
console.warn("Warning: many events listeners for ", e);
|
|
19
|
+
}
|
|
20
|
+
return () => {
|
|
21
|
+
var _a;
|
|
22
|
+
for (const e of event)
|
|
23
|
+
(_a = this.listeners.get(e)) === null || _a === void 0 ? void 0 : _a.delete(listener);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
once(event, listener) {
|
|
27
|
+
return new Promise(resolve => {
|
|
28
|
+
const off = this.on(event, function (...args) {
|
|
29
|
+
off();
|
|
30
|
+
resolve(args);
|
|
31
|
+
return listener === null || listener === void 0 ? void 0 : listener(...arguments);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
multi(map) {
|
|
36
|
+
const cbs = Object.entries(map).map(([name, cb]) => this.on(name.split(' '), cb));
|
|
37
|
+
return () => {
|
|
38
|
+
for (const cb of cbs)
|
|
39
|
+
cb();
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
emit(event, ...args) {
|
|
43
|
+
let cbs = this.listeners.get(event);
|
|
44
|
+
if (!(cbs === null || cbs === void 0 ? void 0 : cbs.size))
|
|
45
|
+
return;
|
|
46
|
+
const ret = [];
|
|
47
|
+
for (const cb of cbs) {
|
|
48
|
+
const res = cb(...args);
|
|
49
|
+
if (res !== undefined)
|
|
50
|
+
ret.push(res);
|
|
51
|
+
}
|
|
52
|
+
return ret;
|
|
53
|
+
}
|
|
54
|
+
emitAsync(event, ...args) {
|
|
55
|
+
return Promise.all(this.emit(event, ...args) || []);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.BetterEventEmitter = BetterEventEmitter;
|
|
8
59
|
// app-wide events
|
|
9
|
-
exports.default = new
|
|
60
|
+
exports.default = new BetterEventEmitter;
|
package/src/geo.js
CHANGED
|
@@ -45,7 +45,7 @@ async function checkFiles() {
|
|
|
45
45
|
const TEMP = LOCAL_FILE + '.downloading';
|
|
46
46
|
const { mtime = 0 } = await (0, promises_1.stat)(LOCAL_FILE).catch(() => ({ mtime: 0 }));
|
|
47
47
|
const now = Date.now();
|
|
48
|
-
if (mtime < now - 31 * misc_1.DAY) { // month-old or non-existing
|
|
48
|
+
if (+mtime < now - 31 * misc_1.DAY) { // month-old or non-existing
|
|
49
49
|
console.log('downloading geo-ip db');
|
|
50
50
|
await (0, misc_1.unzip)(await (0, misc_1.httpStream)(URL), path => path.toUpperCase().endsWith(ZIP_FILE) && TEMP);
|
|
51
51
|
if (await (0, promises_1.stat)(TEMP))
|
package/src/github.js
CHANGED
|
@@ -21,7 +21,7 @@ function downloadProgress(id, status) {
|
|
|
21
21
|
delete downloading[id];
|
|
22
22
|
else
|
|
23
23
|
downloading[id] = status;
|
|
24
|
-
events_1.default.emit('
|
|
24
|
+
events_1.default.emit('pluginDownload', { id, status });
|
|
25
25
|
}
|
|
26
26
|
// determine default branch, possibly without consuming api quota
|
|
27
27
|
async function getGithubDefaultBranch(repo) {
|
|
@@ -161,13 +161,29 @@ async function apiGithub(uri) {
|
|
|
161
161
|
: e;
|
|
162
162
|
});
|
|
163
163
|
}
|
|
164
|
-
async function
|
|
164
|
+
async function* apiGithubPaginated(uri) {
|
|
165
|
+
const PAGE_SIZE = 100;
|
|
166
|
+
let page = 1;
|
|
167
|
+
let n = 0;
|
|
168
|
+
while (1) {
|
|
169
|
+
const res = await apiGithub(uri + `&page=${page++}&per_page=${PAGE_SIZE}`);
|
|
170
|
+
for (const x of res.items)
|
|
171
|
+
yield x;
|
|
172
|
+
const now = res.items.length;
|
|
173
|
+
n += now;
|
|
174
|
+
if (!now || n >= res.total_count)
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function searchPlugins(text = '', { skipRepos = [''] } = {}) {
|
|
165
179
|
const projectInfo = await (0, exports.getProjectInfo)();
|
|
166
|
-
const list = await
|
|
167
|
-
|
|
180
|
+
const list = await Promise.all(['', 'user:'].map(specifier => // search text with multiple specifiers
|
|
181
|
+
(0, misc_1.asyncGeneratorToArray)(apiGithubPaginated(`search/repositories?q=topic:hfs-plugin+${specifier}${encodeURI(text)}`))))
|
|
182
|
+
.then(all => all.flat()); // make it a single array
|
|
183
|
+
return new misc_1.AsapStream(list.map(async (it) => {
|
|
168
184
|
var _a, _b;
|
|
169
185
|
const repo = it.full_name;
|
|
170
|
-
if ((_a = projectInfo === null || projectInfo === void 0 ? void 0 : projectInfo.plugins_blacklist) === null || _a === void 0 ? void 0 : _a.includes(repo))
|
|
186
|
+
if (((_a = projectInfo === null || projectInfo === void 0 ? void 0 : projectInfo.plugins_blacklist) === null || _a === void 0 ? void 0 : _a.includes(repo)) || skipRepos.includes(repo))
|
|
171
187
|
return;
|
|
172
188
|
const pl = await readOnlineCompatiblePlugin(repo, it.default_branch).catch(() => undefined);
|
|
173
189
|
if (!pl)
|
package/src/index.js
CHANGED
|
@@ -45,9 +45,9 @@ exports.app.use(middlewares_1.sessionMiddleware)
|
|
|
45
45
|
.use(middlewares_1.gzipper)
|
|
46
46
|
.use(middlewares_1.paramsDecoder) // must be done before plugins, so they can manipulate params
|
|
47
47
|
.use(middlewares_1.headRequests)
|
|
48
|
+
.use(roots_1.rootsMiddleware)
|
|
48
49
|
.use(log_1.logMw)
|
|
49
50
|
.use(throttler_1.throttler)
|
|
50
|
-
.use(roots_1.rootsMiddleware)
|
|
51
51
|
.use(plugins_1.pluginsMiddleware)
|
|
52
52
|
.use((0, koa_mount_1.default)(const_1.API_URI, (0, apiMiddleware_1.apiMiddleware)({ ...frontEndApis_1.frontEndApis, ...adminApis_1.adminApis })))
|
|
53
53
|
.use(serveGuiAndSharedFiles_1.serveGuiAndSharedFiles)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "Massimo Melina",
|
|
3
|
-
"version":
|
|
4
|
-
"hfs_version": "0.
|
|
3
|
+
"version": 2.53,
|
|
4
|
+
"hfs_version": "0.53.0",
|
|
5
5
|
"translate": {
|
|
6
6
|
"Select": "Seleziona",
|
|
7
7
|
"n_files": "{n} file",
|
|
@@ -145,6 +145,10 @@
|
|
|
145
145
|
"upload_skipped": "{n,plural, one{# saltato} other{# saltati}}",
|
|
146
146
|
"Overwrite policy": "Per i file esistenti",
|
|
147
147
|
"Rename to avoid overwriting": "Rinomina per non sovrascrivere",
|
|
148
|
-
"Overwrite existing files": "Sovrascrivi i file esistenti"
|
|
148
|
+
"Overwrite existing files": "Sovrascrivi i file esistenti",
|
|
149
|
+
"Menu": "Menu",
|
|
150
|
+
"clipboard": "Appunti ({content})",
|
|
151
|
+
"to_clipboard_source_tooltip": "Vai alla cartella d'origine dei file negli appunti",
|
|
152
|
+
"more_items": "{rest,plural,one{Un altro elemento} other{# altri elementi}}"
|
|
149
153
|
}
|
|
150
154
|
}
|
package/src/listen.js
CHANGED
|
@@ -280,7 +280,7 @@ exports.getServerStatus = getServerStatus;
|
|
|
280
280
|
const ignore = /^(lo|.*loopback.*|virtualbox.*|.*\(wsl\).*|llw\d|awdl\d|utun\d|anpi\d)$/i; // avoid giving too much information
|
|
281
281
|
const isLinkLocal = (0, misc_1.makeNetMatcher)('169.254.0.0/16|FE80::/16');
|
|
282
282
|
async function getIps(external = true) {
|
|
283
|
-
const ips = (0, misc_1.onlyTruthy)(Object.entries((0, os_1.networkInterfaces)()).
|
|
283
|
+
const ips = (0, misc_1.onlyTruthy)(Object.entries((0, os_1.networkInterfaces)()).flatMap(([name, nets]) => nets && !ignore.test(name) && nets.map(net => !net.internal && net.address)));
|
|
284
284
|
const e = external && nat_1.defaultBaseUrl.externalIp;
|
|
285
285
|
if (e && !ips.includes(e))
|
|
286
286
|
ips.unshift(e);
|
package/src/log.js
CHANGED
|
@@ -85,6 +85,7 @@ errorLogFile.sub(path => {
|
|
|
85
85
|
const logRotation = (0, config_1.defineConfig)(misc_1.CFG.log_rotation, 'weekly');
|
|
86
86
|
const dontLogNet = (0, config_1.defineConfig)(misc_1.CFG.dont_log_net, '127.0.0.1|::1', v => (0, misc_1.makeNetMatcher)(v));
|
|
87
87
|
const logUA = (0, config_1.defineConfig)(misc_1.CFG.log_ua, false);
|
|
88
|
+
const logSpam = (0, config_1.defineConfig)(misc_1.CFG.log_spam, false);
|
|
88
89
|
const debounce = lodash_1.default.debounce(cb => cb(), 1000);
|
|
89
90
|
const logMw = async (ctx, next) => {
|
|
90
91
|
const now = new Date();
|
|
@@ -92,6 +93,9 @@ const logMw = async (ctx, next) => {
|
|
|
92
93
|
ctx.state.completed = Promise.race([(0, stream_1.once)(ctx.res, 'finish'), (0, stream_1.once)(ctx.res, 'close')]);
|
|
93
94
|
await next();
|
|
94
95
|
console.debug(ctx.status, ctx.method, ctx.originalUrl);
|
|
96
|
+
if (ctx.status === misc_1.HTTP_NOT_FOUND && !logSpam.get()
|
|
97
|
+
&& /wlwmanifest.xml$|robots.txt$|\.(php)$|cgi/.test(ctx.path))
|
|
98
|
+
return;
|
|
95
99
|
const conn = (0, connections_1.getConnection)(ctx); // collect reference before close
|
|
96
100
|
// don't await, as we don't want to hold the middlewares chain
|
|
97
101
|
ctx.state.completed.then(() => {
|
package/src/middlewares.js
CHANGED
|
@@ -22,7 +22,6 @@ const index_1 = require("./index");
|
|
|
22
22
|
const events_1 = __importDefault(require("./events"));
|
|
23
23
|
const forceHttps = (0, config_1.defineConfig)('force_https', true);
|
|
24
24
|
const ignoreProxies = (0, config_1.defineConfig)('ignore_proxies', false);
|
|
25
|
-
const forceBaseUrl = (0, config_1.defineConfig)('force_base_url', false);
|
|
26
25
|
exports.sessionDuration = (0, config_1.defineConfig)('session_duration', Number(process.env.SESSION_DURATION) || misc_1.DAY / 1000, v => v * 1000);
|
|
27
26
|
exports.gzipper = (0, koa_compress_1.default)({
|
|
28
27
|
threshold: 2048,
|
|
@@ -75,8 +74,6 @@ const someSecurity = async (ctx, next) => {
|
|
|
75
74
|
catch (_a) {
|
|
76
75
|
return ctx.status = const_1.HTTP_FOOL;
|
|
77
76
|
}
|
|
78
|
-
if (!ctx.state.skipFilters && forceBaseUrl.get() && listen_1.baseUrl.compiled() && !(0, misc_1.isLocalHost)(ctx) && ctx.host !== listen_1.baseUrl.compiled())
|
|
79
|
-
return (0, connections_1.disconnect)(ctx, 'force-domain');
|
|
80
77
|
if (!ctx.secure && forceHttps.get() && (0, listen_1.getHttpsWorkingPort)() && !(0, misc_1.isLocalHost)(ctx)) {
|
|
81
78
|
const { URL } = ctx;
|
|
82
79
|
URL.protocol = 'https';
|
|
@@ -98,7 +95,7 @@ exports.getProxyDetected = getProxyDetected;
|
|
|
98
95
|
const prepareState = async (ctx, next) => {
|
|
99
96
|
var _a, _b;
|
|
100
97
|
if ((_a = ctx.session) === null || _a === void 0 ? void 0 : _a.username) {
|
|
101
|
-
if (auth_1.
|
|
98
|
+
if (ctx.session.ts < auth_1.invalidateSessionBefore.get(ctx.session.username))
|
|
102
99
|
delete ctx.session.username;
|
|
103
100
|
ctx.session.maxAge = exports.sessionDuration.compiled();
|
|
104
101
|
}
|
|
@@ -123,11 +120,17 @@ const prepareState = async (ctx, next) => {
|
|
|
123
120
|
return doLogin((credentials === null || credentials === void 0 ? void 0 : credentials.name) || '', (credentials === null || credentials === void 0 ? void 0 : credentials.pass) || '');
|
|
124
121
|
}
|
|
125
122
|
async function doLogin(u, p) {
|
|
123
|
+
var _a;
|
|
124
|
+
if (!u || u === ((_a = ctx.session) === null || _a === void 0 ? void 0 : _a.username))
|
|
125
|
+
return; // providing credentials, but not needed
|
|
126
|
+
await events_1.default.emitAsync('attemptingLogin', ctx);
|
|
126
127
|
const a = await (0, auth_1.srpCheck)(u, p);
|
|
127
128
|
if (a) {
|
|
128
|
-
(0, auth_1.setLoggedIn)(ctx, a.username);
|
|
129
|
+
await (0, auth_1.setLoggedIn)(ctx, a.username);
|
|
129
130
|
ctx.headers['x-username'] = a.username; // give an easier way to determine if the login was successful
|
|
130
131
|
}
|
|
132
|
+
else if (u)
|
|
133
|
+
events_1.default.emit('failedLogin', ctx, { username: u });
|
|
131
134
|
return a;
|
|
132
135
|
}
|
|
133
136
|
};
|
package/src/misc.js
CHANGED
|
@@ -18,7 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.apiAssertTypes = exports.AsapStream = exports.asyncGeneratorToReadable = exports.same = exports.makeNetMatcher = exports.isLocalHost = exports.
|
|
21
|
+
exports.apiAssertTypes = exports.AsapStream = exports.asyncGeneratorToReadable = exports.same = exports.makeNetMatcher = exports.isLocalHost = exports.pattern2filter = exports.onFirstEvent = exports.onProcessExit = void 0;
|
|
22
22
|
const path_1 = require("path");
|
|
23
23
|
const assert_1 = __importDefault(require("assert"));
|
|
24
24
|
__exportStar(require("./util-http"), exports);
|
|
@@ -55,19 +55,6 @@ function pattern2filter(pattern) {
|
|
|
55
55
|
return (s) => !s || !pattern || matcher((0, path_1.basename)(s));
|
|
56
56
|
}
|
|
57
57
|
exports.pattern2filter = pattern2filter;
|
|
58
|
-
// install multiple handlers and returns a handy 'uninstall' function which requires no parameter. Pass a map {event:handler}
|
|
59
|
-
function onOff(em, events) {
|
|
60
|
-
events = { ...events }; // avoid later modifications, as we need this later for uninstallation
|
|
61
|
-
for (const [k, cb] of Object.entries(events))
|
|
62
|
-
for (const e of k.split(' '))
|
|
63
|
-
em.on(e, cb);
|
|
64
|
-
return () => {
|
|
65
|
-
for (const [k, cb] of Object.entries(events))
|
|
66
|
-
for (const e of k.split(' '))
|
|
67
|
-
em.off(e, cb);
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
exports.onOff = onOff;
|
|
71
58
|
function isLocalHost(c) {
|
|
72
59
|
const ip = typeof c === 'string' ? c : c.socket.remoteAddress; // don't use Context.ip as it is subject to proxied ips, and that's no use for localhost detection
|
|
73
60
|
return ip && (0, cross_1.isIpLocalHost)(ip);
|
|
@@ -120,7 +107,7 @@ function asyncGeneratorToReadable(generator) {
|
|
|
120
107
|
objectMode: true,
|
|
121
108
|
destroy() {
|
|
122
109
|
var _a;
|
|
123
|
-
(_a = iterator.return) === null || _a === void 0 ? void 0 : _a.call(iterator);
|
|
110
|
+
void ((_a = iterator.return) === null || _a === void 0 ? void 0 : _a.call(iterator));
|
|
124
111
|
},
|
|
125
112
|
read() {
|
|
126
113
|
iterator.next().then(it => this.push(it.done ? null : it.value));
|
package/src/perm.js
CHANGED
|
@@ -46,10 +46,8 @@ createAdminConfig.sub(v => {
|
|
|
46
46
|
setTimeout(() => createAdmin(v));
|
|
47
47
|
});
|
|
48
48
|
async function createAdmin(password, username = 'admin') {
|
|
49
|
-
const acc = await addAccount(username, { admin: true, password });
|
|
50
|
-
|
|
51
|
-
return console.log("cannot create, already exists");
|
|
52
|
-
console.log("account admin created");
|
|
49
|
+
const acc = await addAccount(username, { admin: true, password }, true);
|
|
50
|
+
console.log(acc ? "account admin created" : "something went wrong");
|
|
53
51
|
}
|
|
54
52
|
exports.createAdmin = createAdmin;
|
|
55
53
|
const srp6aNimbusRoutines = new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters());
|
|
@@ -60,6 +58,9 @@ async function updateAccount(account, change) {
|
|
|
60
58
|
await (change === null || change === void 0 ? void 0 : change(account));
|
|
61
59
|
else
|
|
62
60
|
Object.assign(account, (0, misc_1.objSameKeys)(change, x => x || undefined));
|
|
61
|
+
for (const [k, v] of (0, misc_1.typedEntries)(account))
|
|
62
|
+
if (!v)
|
|
63
|
+
delete account[k]; // we consider all account fields, when falsy, as equivalent to be missing (so, default value applies)
|
|
63
64
|
const { username, password } = account;
|
|
64
65
|
if (password) {
|
|
65
66
|
console.debug('hashing password for', username);
|
|
@@ -126,14 +127,18 @@ function renameAccount(from, to) {
|
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
exports.renameAccount = renameAccount;
|
|
129
|
-
async function addAccount(username, props) {
|
|
130
|
+
async function addAccount(username, props, updateExisting = false) {
|
|
130
131
|
username = normalizeUsername(username);
|
|
131
|
-
if (!username
|
|
132
|
+
if (!username)
|
|
132
133
|
return;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
let account = getAccount(username, false);
|
|
135
|
+
if (account && !updateExisting)
|
|
136
|
+
return;
|
|
137
|
+
account = (0, misc_1.setHidden)(account || {}, { username }); // hidden so that stringification won't include it
|
|
138
|
+
Object.assign(account, lodash_1.default.pickBy(props, Boolean));
|
|
139
|
+
exports.accountsConfig.set(accounts => Object.assign(accounts, { [username]: account }));
|
|
140
|
+
await updateAccount(account, account);
|
|
141
|
+
return account;
|
|
137
142
|
}
|
|
138
143
|
exports.addAccount = addAccount;
|
|
139
144
|
function delAccount(username) {
|
package/src/plugins.js
CHANGED
|
@@ -27,7 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
27
27
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
28
|
};
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.getMissingDependencies = exports.parsePluginSource = exports.rescan = exports.pluginsConfig = exports.enablePlugins = exports.pluginsWatcher = exports.getAvailablePlugins = exports.mapPlugins = exports.Plugin = exports.pluginsMiddleware = exports.getPluginConfigFields = exports.findPluginByRepo = exports.getPluginInfo = exports.setPluginConfig = exports.startPlugin = exports.stopPlugin = exports.enablePlugin = exports.isPluginEnabled = exports.isPluginRunning = exports.STORAGE_FOLDER = exports.DISABLING_SUFFIX = exports.PATH = void 0;
|
|
30
|
+
exports.getMissingDependencies = exports.parsePluginSource = exports.rescan = exports.pluginsConfig = exports.enablePlugins = exports.pluginsWatcher = exports.getAvailablePlugins = exports.mapPlugins = exports.SERVER_CODE_ID = exports.Plugin = exports.pluginsMiddleware = exports.getPluginConfigFields = exports.findPluginByRepo = exports.getPluginInfo = exports.setPluginConfig = exports.startPlugin = exports.stopPlugin = exports.enablePlugin = exports.isPluginEnabled = exports.isPluginRunning = exports.STORAGE_FOLDER = exports.DISABLING_SUFFIX = exports.PATH = void 0;
|
|
31
31
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
32
32
|
const watchLoad_1 = require("./watchLoad");
|
|
33
33
|
const lodash_1 = __importDefault(require("lodash"));
|
|
@@ -232,7 +232,7 @@ class Plugin {
|
|
|
232
232
|
const { id } = this;
|
|
233
233
|
try {
|
|
234
234
|
await ((_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.unload) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
235
|
-
if (!reloading && id !== SERVER_CODE_ID) // we already printed 'reloading'
|
|
235
|
+
if (!reloading && id !== exports.SERVER_CODE_ID) // we already printed 'reloading'
|
|
236
236
|
console.log('unloaded plugin', id);
|
|
237
237
|
}
|
|
238
238
|
catch (e) {
|
|
@@ -244,12 +244,12 @@ class Plugin {
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
exports.Plugin = Plugin;
|
|
247
|
-
|
|
247
|
+
exports.SERVER_CODE_ID = '.'; // a name that will surely be not found among plugin folders
|
|
248
248
|
const serverCode = (0, config_1.defineConfig)('server_code', '', async (script, { k }) => {
|
|
249
249
|
const res = {};
|
|
250
250
|
try {
|
|
251
251
|
new Function('exports', script)(res); // parse
|
|
252
|
-
return new Plugin(SERVER_CODE_ID, '', await initPlugin(res), lodash_1.default.noop);
|
|
252
|
+
return new Plugin(exports.SERVER_CODE_ID, '', await initPlugin(res), lodash_1.default.noop);
|
|
253
253
|
}
|
|
254
254
|
catch (e) {
|
|
255
255
|
return console.error(k + ':', e.message || String(e));
|
|
@@ -260,7 +260,7 @@ serverCode.sub(() => { var _a; return (_a = serverCode.compiled()) === null || _
|
|
|
260
260
|
function mapPlugins(cb, includeServerCode = true) {
|
|
261
261
|
const entries = Object.entries(plugins);
|
|
262
262
|
return entries.map(([plName, pl]) => {
|
|
263
|
-
if (!includeServerCode && plName === SERVER_CODE_ID)
|
|
263
|
+
if (!includeServerCode && plName === exports.SERVER_CODE_ID)
|
|
264
264
|
return;
|
|
265
265
|
try {
|
|
266
266
|
return cb(pl, plName);
|
package/src/roots.js
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.rootsMiddleware = exports.roots = void 0;
|
|
7
4
|
const config_1 = require("./config");
|
|
8
5
|
const misc_1 = require("./misc");
|
|
9
6
|
const connections_1 = require("./connections");
|
|
10
|
-
const
|
|
7
|
+
const listen_1 = require("./listen");
|
|
11
8
|
exports.roots = (0, config_1.defineConfig)(misc_1.CFG.roots, {}, map => {
|
|
12
|
-
if (lodash_1.default.isArray(map)) { // legacy pre 0.51.0-alpha5, remove in 0.52
|
|
13
|
-
exports.roots.set(Object.fromEntries(map.map(x => [x.host, x.root])));
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
9
|
const list = Object.keys(map);
|
|
17
10
|
const matchers = list.map(hostMask => (0, misc_1.makeMatcher)(hostMask));
|
|
18
11
|
const values = Object.values(map);
|
|
19
12
|
return (host) => values[matchers.findIndex(m => m(host))];
|
|
20
13
|
});
|
|
21
|
-
const
|
|
14
|
+
const forceAddress = (0, config_1.defineConfig)(misc_1.CFG.force_address, false);
|
|
15
|
+
forceAddress.sub((v, { version }) => {
|
|
16
|
+
if (version === null || version === void 0 ? void 0 : version.olderThan('0.53.0'))
|
|
17
|
+
forceAddress.set((0, config_1.getConfig)('force_base_url') || (0, config_1.getConfig)('roots_mandatory') || false);
|
|
18
|
+
});
|
|
22
19
|
const rootsMiddleware = (ctx, next) => (() => {
|
|
20
|
+
var _a;
|
|
21
|
+
const root = (_a = exports.roots.compiled()) === null || _a === void 0 ? void 0 : _a(ctx.host);
|
|
22
|
+
if (!ctx.state.skipFilters && forceAddress.get())
|
|
23
|
+
if (root === undefined && !(0, misc_1.isLocalHost)(ctx) && ctx.host !== listen_1.baseUrl.compiled()) {
|
|
24
|
+
(0, connections_1.disconnect)(ctx, forceAddress.key());
|
|
25
|
+
return true; // true will avoid calling next
|
|
26
|
+
}
|
|
27
|
+
if (!root || root === '/')
|
|
28
|
+
return; // not transformation is required
|
|
23
29
|
let params; // undefined if we are not going to work on api parameters
|
|
24
30
|
if (ctx.path.startsWith(misc_1.SPECIAL_URI)) { // special uris should be excluded...
|
|
25
31
|
if (!ctx.path.startsWith(misc_1.API_URI))
|
|
@@ -30,20 +36,6 @@ const rootsMiddleware = (ctx, next) => (() => {
|
|
|
30
36
|
return; // exclude apis for admin-panel
|
|
31
37
|
params = ctx.state.params || ctx.query; // for api we'll translate params
|
|
32
38
|
}
|
|
33
|
-
if (lodash_1.default.isEmpty(exports.roots.get()))
|
|
34
|
-
return;
|
|
35
|
-
const host2root = exports.roots.compiled();
|
|
36
|
-
if (!host2root)
|
|
37
|
-
return;
|
|
38
|
-
const root = host2root(ctx.host);
|
|
39
|
-
if (root === '' || root === '/')
|
|
40
|
-
return;
|
|
41
|
-
if (root === undefined) {
|
|
42
|
-
if (ctx.state.skipFilters || !rootsMandatory.get() || (0, misc_1.isLocalHost)(ctx))
|
|
43
|
-
return;
|
|
44
|
-
(0, connections_1.disconnect)(ctx, 'bad-domain');
|
|
45
|
-
return true; // true will avoid calling next
|
|
46
|
-
}
|
|
47
39
|
if (!params) {
|
|
48
40
|
ctx.path = join(root, ctx.path);
|
|
49
41
|
return;
|
|
@@ -86,19 +86,22 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
86
86
|
if (node.default && path.endsWith('/') && !get) // final/ needed on browser to make resource urls correctly with html pages
|
|
87
87
|
node = (_a = await (0, vfs_1.urlToNode)(node.default, ctx, node)) !== null && _a !== void 0 ? _a : node;
|
|
88
88
|
if (!await (0, vfs_1.nodeIsDirectory)(node))
|
|
89
|
-
return
|
|
90
|
-
: !
|
|
91
|
-
:
|
|
92
|
-
:
|
|
93
|
-
:
|
|
89
|
+
return node.url ? ctx.redirect(node.url)
|
|
90
|
+
: !node.source ? (0, errorPages_1.sendErrorPage)(ctx, cross_const_1.HTTP_METHOD_NOT_ALLOWED) // !dir && !source is not supported at this moment
|
|
91
|
+
: !(0, vfs_1.statusCodeForMissingPerm)(node, 'can_read', ctx) ? (0, serveFile_1.serveFileNode)(ctx, node) // all good
|
|
92
|
+
: ctx.status !== cross_const_1.HTTP_UNAUTHORIZED ? null // all errors don't need extra handling, except unauthorized
|
|
93
|
+
: path.endsWith('/') ? (ctx.state.serveApp = true) && serveFrontendFiles(ctx, next) // since this is no dir, final / means we are dealing with default file, for which we still provide fancy login
|
|
94
|
+
: (ctx.set('WWW-Authenticate', 'Basic'), (0, errorPages_1.sendErrorPage)(ctx)); // this is necessary to support standard urls with credentials
|
|
94
95
|
if (!path.endsWith('/'))
|
|
95
96
|
return ctx.redirect(ctx.state.revProxyPath + ctx.originalUrl.replace(/(\?|$)/, '/$1')); // keep query-string, if any
|
|
96
97
|
if ((0, vfs_1.statusCodeForMissingPerm)(node, 'can_list', ctx)) {
|
|
97
98
|
if (ctx.status === cross_const_1.HTTP_FORBIDDEN)
|
|
98
99
|
return (0, errorPages_1.sendErrorPage)(ctx, cross_const_1.HTTP_FORBIDDEN);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
// detect if we are dealing with a download-manager, as it may need basic authentication, while we don't want it on browsers
|
|
101
|
+
const { authenticate } = ctx.query;
|
|
102
|
+
const downloadManagerDetected = /DAP|FDM|[Mm]anager/.test(ctx.get('user-agent'));
|
|
103
|
+
if (downloadManagerDetected || authenticate)
|
|
104
|
+
return ctx.set('WWW-Authenticate', authenticate || 'Basic'); // we support basic authentication
|
|
102
105
|
ctx.state.serveApp = true;
|
|
103
106
|
return serveFrontendFiles(ctx, next);
|
|
104
107
|
}
|
package/src/serveGuiFiles.js
CHANGED
|
@@ -63,11 +63,8 @@ function serveStatic(uri) {
|
|
|
63
63
|
return ctx.status = const_1.HTTP_METHOD_NOT_ALLOWED;
|
|
64
64
|
const serveApp = shouldServeApp(ctx);
|
|
65
65
|
const fullPath = (0, path_1.join)(__dirname, '..', DEV_STATIC, folder, serveApp ? '/index.html' : ctx.path);
|
|
66
|
-
const content = await (0, misc_1.
|
|
67
|
-
|
|
68
|
-
return serveApp || !data ? data
|
|
69
|
-
: adjustBundlerLinks(ctx, uri, data);
|
|
70
|
-
});
|
|
66
|
+
const content = await (0, misc_1.parseFile)(fullPath, raw => serveApp || !raw.length ? raw : adjustBundlerLinks(ctx, uri, raw))
|
|
67
|
+
.catch(() => null);
|
|
71
68
|
if (content === null)
|
|
72
69
|
return ctx.status = const_1.HTTP_NOT_FOUND;
|
|
73
70
|
if (!serveApp)
|
|
@@ -94,7 +91,7 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
94
91
|
ctx.set('etag', '');
|
|
95
92
|
ctx.set('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
96
93
|
ctx.type = 'html';
|
|
97
|
-
const isFrontend = filesUri === const_1.FRONTEND_URI;
|
|
94
|
+
const isFrontend = filesUri === const_1.FRONTEND_URI ? ' ' : ''; // as a string will allow neater code later
|
|
98
95
|
const pub = ctx.state.revProxyPath + const_1.PLUGINS_PUB_URI;
|
|
99
96
|
// expose plugins' configs that are declared with 'frontend' attribute
|
|
100
97
|
const plugins = Object.fromEntries((0, misc_1.onlyTruthy)((0, plugins_1.mapPlugins)((pl, name) => {
|
|
@@ -117,7 +114,7 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
117
114
|
const isOpen = !isClose;
|
|
118
115
|
if (isHead && isOpen)
|
|
119
116
|
return all + `
|
|
120
|
-
${
|
|
117
|
+
${isFrontend && `
|
|
121
118
|
<title>${adminApis_1.title.get()}</title>
|
|
122
119
|
<link rel="shortcut icon" href="/favicon.ico?${timestamp}" />
|
|
123
120
|
` + (0, customHtml_1.getSection)('htmlHead')}
|
|
@@ -128,6 +125,7 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
128
125
|
SPECIAL_URI: const_1.SPECIAL_URI, PLUGINS_PUB_URI: const_1.PLUGINS_PUB_URI, FRONTEND_URI: const_1.FRONTEND_URI,
|
|
129
126
|
session: session instanceof apiMiddleware_1.ApiError ? null : session,
|
|
130
127
|
plugins,
|
|
128
|
+
loadScripts: Object.fromEntries((0, plugins_1.mapPlugins)((p, id) => { var _a; return [id, (_a = p.frontend_js) === null || _a === void 0 ? void 0 : _a.map(f => f.includes('//') ? f : pub + id + '/' + f)]; })),
|
|
131
129
|
prefixUrl: ctx.state.revProxyPath,
|
|
132
130
|
dontOverwriteUploading: upload_1.dontOverwriteUploading.get(),
|
|
133
131
|
customHtml: lodash_1.default.omit(Object.fromEntries(customHtml_1.customHtmlState.sections), ['top', 'bottom']),
|
|
@@ -139,20 +137,16 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
139
137
|
`;
|
|
140
138
|
if (isBody && isOpen)
|
|
141
139
|
return all + `
|
|
142
|
-
${
|
|
140
|
+
${isFrontend && (0, customHtml_1.getSection)('top')}
|
|
143
141
|
<style>
|
|
144
142
|
:root {
|
|
145
|
-
${lodash_1.default.map(plugins, (configs, pluginName) =>
|
|
143
|
+
${lodash_1.default.map(plugins, (configs, pluginName) => // make plugin configs accessible via css
|
|
144
|
+
lodash_1.default.map(configs, (v, k) => `--${pluginName}-${k}: ${serializeCss(v)};`).join('\n')).join('')}
|
|
146
145
|
}
|
|
147
146
|
</style>
|
|
148
|
-
${
|
|
147
|
+
${isFrontend && (0, plugins_1.mapPlugins)((plug, id) => {
|
|
149
148
|
var _a;
|
|
150
149
|
return (_a = plug.frontend_css) === null || _a === void 0 ? void 0 : _a.map(f => `<link rel='stylesheet' type='text/css' href='${f.includes('//') ? f : pub + id + '/' + f}' plugin=${JSON.stringify(id)}/>`);
|
|
151
|
-
})
|
|
152
|
-
.flat().filter(Boolean).join('\n')}
|
|
153
|
-
${!isFrontend ? '' : (0, plugins_1.mapPlugins)((plug, id) => {
|
|
154
|
-
var _a;
|
|
155
|
-
return (_a = plug.frontend_js) === null || _a === void 0 ? void 0 : _a.map(f => `<script defer plugin=${JSON.stringify(id)} src='${f.includes('//') ? f : pub + id + '/' + f}'></script>`);
|
|
156
150
|
})
|
|
157
151
|
.flat().filter(Boolean).join('\n')}
|
|
158
152
|
`;
|
package/src/throttler.js
CHANGED
|
@@ -18,7 +18,13 @@ const ip2group = {};
|
|
|
18
18
|
const SymThrStr = Symbol('stream');
|
|
19
19
|
const SymTimeout = Symbol('timeout');
|
|
20
20
|
const maxKbpsPerIp = (0, config_1.defineConfig)('max_kbps_per_ip', Infinity);
|
|
21
|
+
maxKbpsPerIp.sub(v => {
|
|
22
|
+
for (const [ip, { group }] of Object.entries(ip2group))
|
|
23
|
+
if (ip) // empty-string = unlimited group
|
|
24
|
+
group.updateLimit(v);
|
|
25
|
+
});
|
|
21
26
|
const throttler = async (ctx, next) => {
|
|
27
|
+
var _a;
|
|
22
28
|
await next();
|
|
23
29
|
let { body } = ctx;
|
|
24
30
|
const downloadTotal = ctx.response.length;
|
|
@@ -27,13 +33,11 @@ const throttler = async (ctx, next) => {
|
|
|
27
33
|
if (!body || !(body instanceof stream_1.Readable))
|
|
28
34
|
return;
|
|
29
35
|
// we wrap the stream also for unlimited connections to get speed and other features
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return { group, count: 0, destroy: unsub };
|
|
36
|
-
});
|
|
36
|
+
const noLimit = ((_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.ignore_limits) || (0, misc_1.isLocalHost)(ctx);
|
|
37
|
+
const ipGroup = (0, misc_1.getOrSet)(ip2group, noLimit ? '' : ctx.ip, () => ({
|
|
38
|
+
count: 0,
|
|
39
|
+
group: new ThrottledStream_1.ThrottleGroup(noLimit ? Infinity : maxKbpsPerIp.get(), noLimit ? undefined : mainThrottleGroup),
|
|
40
|
+
}));
|
|
37
41
|
const conn = (0, connections_1.getConnection)(ctx);
|
|
38
42
|
if (!conn)
|
|
39
43
|
throw 'assert throttler connection';
|
|
@@ -58,12 +62,10 @@ const throttler = async (ctx, next) => {
|
|
|
58
62
|
});
|
|
59
63
|
++ipGroup.count;
|
|
60
64
|
ts.on('close', () => {
|
|
61
|
-
var _a;
|
|
62
65
|
update.flush();
|
|
63
66
|
closed = true;
|
|
64
67
|
if (--ipGroup.count)
|
|
65
68
|
return; // any left?
|
|
66
|
-
(_a = ipGroup.destroy) === null || _a === void 0 ? void 0 : _a.call(ipGroup);
|
|
67
69
|
delete ip2group[ctx.ip];
|
|
68
70
|
});
|
|
69
71
|
ctx.state.originalStream = body;
|