hfs 0.51.1 → 0.52.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-V0udjUIR.js +808 -0
- package/admin/assets/{index-49472c6b.css → index-rD-EYdVL.css} +1 -1
- package/{frontend/assets/sha512-l_aqBm0S.js → admin/assets/sha512-kdrIq7UN.js} +1 -1
- package/admin/index.html +2 -3
- package/frontend/assets/index-EdGoLuWB.css +1 -0
- package/frontend/assets/index-r8i8c_PX.js +101 -0
- package/{admin/assets/sha512-c9d3d113.js → frontend/assets/sha512-4UUUqqOn.js} +1 -1
- package/frontend/index.html +2 -2
- package/package.json +9 -7
- package/src/adminApis.js +4 -86
- package/src/api.accounts.js +12 -15
- package/src/api.auth.js +9 -35
- package/src/api.log.js +74 -0
- package/src/api.monitor.js +5 -12
- package/src/api.net.js +1 -1
- package/src/api.vfs.js +4 -4
- package/src/apiMiddleware.js +1 -1
- package/src/auth.js +17 -8
- package/src/block.js +1 -1
- package/src/commands.js +1 -3
- package/src/const.js +1 -1
- package/src/cross.js +25 -7
- package/src/customHtml.js +1 -1
- package/src/ddns.js +53 -0
- package/src/github.js +2 -2
- package/src/langs/hfs-lang-it.json +10 -1
- package/src/log.js +9 -7
- package/src/middlewares.js +14 -10
- package/src/misc.js +4 -0
- package/src/nat.js +4 -8
- package/src/perm.js +28 -34
- package/src/plugins.js +3 -3
- package/src/serveGuiFiles.js +2 -1
- package/src/util-http.js +12 -8
- package/admin/assets/index-30c70443.js +0 -808
- package/frontend/assets/index-Bsm0eh8l.js +0 -101
- package/frontend/assets/index-tdvGCFPn.css +0 -1
- package/src/api.helpers.js +0 -28
- package/src/crypt.js +0 -21
- package/src/pbkdf2.js +0 -74
package/src/cross.js
CHANGED
|
@@ -17,8 +17,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.
|
|
21
|
-
exports.shortenAgent = exports.matches = exports.makeMatcher = exports.runAt = exports.pathEncode = exports.promiseBestEffort = exports.escapeHTML = exports.ipForUrl = exports.isIpLan = exports.isIpLocalHost = exports.xlate = exports.isEqualLax = exports.isWindowsDrive = exports.isIP = exports.isPrimitive = exports.formatTimestamp = exports.repeat = exports.asyncGeneratorToArray = exports.filterMapGenerator = exports.throw_ = void 0;
|
|
20
|
+
exports.typedEntries = exports.typedKeys = exports.objRenameKey = exports.randomId = exports.getOrSet = exports.waitFor = exports.newObj = exports.findDefined = exports.isOrderedEqual = exports.swap = exports.tryJson = exports.dirname = exports.basename = exports.pendingPromise = exports._dbg = exports._log = exports.wantArray = exports.formatPerc = exports.with_ = exports.try_ = exports.setHidden = exports.onlyTruthy = exports.truthy = exports.splitAt = exports.strinsert = exports.removeStarting = exports.enforceFinal = exports.objSameKeys = exports.haveTimeout = exports.wait = exports.prefix = exports.formatSpeed = exports.formatBytes = exports.isWhoObject = exports.PERM_KEYS = exports.defaultPerms = exports.WHO_ANY_ACCOUNT = exports.WHO_NO_ONE = exports.WHO_ANYONE = exports.LIST = exports.CFG = exports.THEME_OPTIONS = exports.SORT_BY_OPTIONS = exports.FRONTEND_OPTIONS = exports.MAX_TILE_SIZE = exports.DAY = exports.HOUR = exports.MINUTE = exports.WIKI_URL = exports.REPO_URL = void 0;
|
|
21
|
+
exports.shortenAgent = exports.replace = exports.matches = exports.makeMatcher = exports.runAt = exports.pathEncode = exports.promiseBestEffort = exports.escapeHTML = exports.ipForUrl = exports.isIpLan = exports.isIpLocalHost = exports.xlate = exports.isEqualLax = exports.isWindowsDrive = exports.isIP = exports.isPrimitive = exports.formatTimestamp = exports.repeat = exports.asyncGeneratorToArray = exports.filterMapGenerator = exports.throw_ = exports.hasProp = void 0;
|
|
22
22
|
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
23
23
|
// all content here is shared between client and server
|
|
24
24
|
const lodash_1 = __importDefault(require("lodash"));
|
|
@@ -38,10 +38,12 @@ exports.FRONTEND_OPTIONS = {
|
|
|
38
38
|
folders_first: true,
|
|
39
39
|
sort_numerics: false,
|
|
40
40
|
theme: '',
|
|
41
|
+
auto_play_seconds: 5,
|
|
41
42
|
};
|
|
42
43
|
exports.SORT_BY_OPTIONS = ['name', 'extension', 'size', 'time'];
|
|
43
44
|
exports.THEME_OPTIONS = { auto: '', light: 'light', dark: 'dark' };
|
|
44
|
-
exports.CFG = constMap(['geo_enable', 'geo_allow', 'geo_list', 'geo_allow_unknown',
|
|
45
|
+
exports.CFG = constMap(['geo_enable', 'geo_allow', 'geo_list', 'geo_allow_unknown', 'dynamic_dns_url',
|
|
46
|
+
'log', 'error_log', 'log_rotation', 'dont_log_net', 'log_gui', 'log_api', 'log_ua',
|
|
45
47
|
'max_downloads', 'max_downloads_per_ip', 'max_downloads_per_account', 'roots', 'roots_mandatory']);
|
|
46
48
|
exports.LIST = { add: '+', remove: '-', update: '=', props: 'props', ready: 'ready', error: 'e' };
|
|
47
49
|
exports.WHO_ANYONE = true;
|
|
@@ -103,6 +105,10 @@ function removeStarting(sub, s) {
|
|
|
103
105
|
return s.startsWith(sub) ? s.slice(sub.length) : s;
|
|
104
106
|
}
|
|
105
107
|
exports.removeStarting = removeStarting;
|
|
108
|
+
function strinsert(s, at, insert, remove = 0) {
|
|
109
|
+
return s.slice(0, at) + insert + s.slice(at + remove);
|
|
110
|
+
}
|
|
111
|
+
exports.strinsert = strinsert;
|
|
106
112
|
function splitAt(sub, all) {
|
|
107
113
|
if (typeof sub === 'number')
|
|
108
114
|
return [all.slice(0, sub), all.slice(sub + 1)];
|
|
@@ -168,7 +174,7 @@ function basename(path) {
|
|
|
168
174
|
}
|
|
169
175
|
exports.basename = basename;
|
|
170
176
|
function dirname(path) {
|
|
171
|
-
return path.slice(0, path.lastIndexOf('/', path.length - 1));
|
|
177
|
+
return path.slice(0, Math.max(0, path.lastIndexOf('/', path.length - 1)));
|
|
172
178
|
}
|
|
173
179
|
exports.dirname = dirname;
|
|
174
180
|
function tryJson(s, except) {
|
|
@@ -296,12 +302,17 @@ async function asyncGeneratorToArray(generator) {
|
|
|
296
302
|
return ret;
|
|
297
303
|
}
|
|
298
304
|
exports.asyncGeneratorToArray = asyncGeneratorToArray;
|
|
299
|
-
function repeat(
|
|
300
|
-
|
|
305
|
+
function repeat(everyMs, cb) {
|
|
306
|
+
let stop = false;
|
|
307
|
+
setTimeout(async () => {
|
|
308
|
+
while (!stop && await Promise.allSettled([cb()]))
|
|
309
|
+
await wait(everyMs);
|
|
310
|
+
});
|
|
311
|
+
return () => stop = true;
|
|
301
312
|
}
|
|
302
313
|
exports.repeat = repeat;
|
|
303
314
|
function formatTimestamp(x) {
|
|
304
|
-
return x ? new Date(x).toLocaleString()
|
|
315
|
+
return !x ? '-' : (x instanceof Date ? x : new Date(x)).toLocaleString();
|
|
305
316
|
}
|
|
306
317
|
exports.formatTimestamp = formatTimestamp;
|
|
307
318
|
function isPrimitive(x) {
|
|
@@ -387,6 +398,13 @@ function matches(s, mask, emptyMaskReturns = false) {
|
|
|
387
398
|
return makeMatcher(mask, emptyMaskReturns)(s); // adding () will allow us to use the pipe at root level
|
|
388
399
|
}
|
|
389
400
|
exports.matches = matches;
|
|
401
|
+
function replace(s, symbols, delimiter = '') {
|
|
402
|
+
const [open, close] = splitAt(' ', delimiter);
|
|
403
|
+
for (const [k, v] of typedEntries(symbols))
|
|
404
|
+
s = s.replace(open + k + close, lodash_1.default.isFunction(v) ? v() : v);
|
|
405
|
+
return s;
|
|
406
|
+
}
|
|
407
|
+
exports.replace = replace;
|
|
390
408
|
function shortenAgent(agent) {
|
|
391
409
|
var _a;
|
|
392
410
|
return lodash_1.default.findKey(BROWSERS, re => re.test(agent))
|
package/src/customHtml.js
CHANGED
|
@@ -8,7 +8,7 @@ const promises_1 = require("fs/promises");
|
|
|
8
8
|
const plugins_1 = require("./plugins");
|
|
9
9
|
const FILE = 'custom.html';
|
|
10
10
|
exports.customHtmlSections = ['beforeHeader', 'afterHeader', 'afterMenuBar', 'afterList',
|
|
11
|
-
'top', 'bottom', 'afterEntryName', 'beforeLogin', 'unauthorized'];
|
|
11
|
+
'top', 'bottom', 'afterEntryName', 'beforeLogin', 'unauthorized', 'htmlHead'];
|
|
12
12
|
exports.customHtmlState = (0, vanilla_1.proxy)({
|
|
13
13
|
sections: watchLoadCustomHtml().state
|
|
14
14
|
});
|
package/src/ddns.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.get_dynamic_dns_error = void 0;
|
|
7
|
+
const config_1 = require("./config");
|
|
8
|
+
const cross_1 = require("./cross");
|
|
9
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
10
|
+
const util_http_1 = require("./util-http");
|
|
11
|
+
const node_net_1 = require("node:net");
|
|
12
|
+
const net_1 = require("net");
|
|
13
|
+
const const_1 = require("./const");
|
|
14
|
+
const events_1 = __importDefault(require("./events"));
|
|
15
|
+
const stream_1 = require("stream");
|
|
16
|
+
const nat_1 = require("./nat");
|
|
17
|
+
// optionally you can append '>' and a regular expression to determine what body is considered successful
|
|
18
|
+
const dynamicDnsUrl = (0, config_1.defineConfig)(cross_1.CFG.dynamic_dns_url, '');
|
|
19
|
+
let stop;
|
|
20
|
+
let last;
|
|
21
|
+
dynamicDnsUrl.sub(v => {
|
|
22
|
+
stop === null || stop === void 0 ? void 0 : stop();
|
|
23
|
+
if (!v)
|
|
24
|
+
return;
|
|
25
|
+
let lastIps;
|
|
26
|
+
const [templateUrl, re] = (0, cross_1.splitAt)('>', v);
|
|
27
|
+
stop = (0, cross_1.repeat)(cross_1.HOUR, async () => {
|
|
28
|
+
const ips = await (0, nat_1.getPublicIps)();
|
|
29
|
+
if (lodash_1.default.isEqual(lastIps, ips))
|
|
30
|
+
return;
|
|
31
|
+
lastIps = ips;
|
|
32
|
+
const url = (0, cross_1.replace)(templateUrl, {
|
|
33
|
+
IPX: ips[0] || '',
|
|
34
|
+
IP4: lodash_1.default.find(ips, node_net_1.isIPv4) || '',
|
|
35
|
+
IP6: lodash_1.default.find(ips, net_1.isIPv6) || '',
|
|
36
|
+
}, '$');
|
|
37
|
+
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
|
|
38
|
+
.then(async (res) => {
|
|
39
|
+
const str = String(res.body).trim();
|
|
40
|
+
return (re ? str.match(re) : res.ok) ? '' : (str || res.statusMessage);
|
|
41
|
+
}, (err) => err.code || err.message || String(err));
|
|
42
|
+
last = { ts: new Date().toJSON(), error, url };
|
|
43
|
+
events_1.default.emit('dynamicDnsError', last);
|
|
44
|
+
console.log('dynamic dns update', error || 'ok');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
async function* get_dynamic_dns_error() {
|
|
48
|
+
while (1) {
|
|
49
|
+
yield last;
|
|
50
|
+
await (0, stream_1.once)(events_1.default, 'dynamicDnsError');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.get_dynamic_dns_error = get_dynamic_dns_error;
|
package/src/github.js
CHANGED
|
@@ -78,12 +78,12 @@ async function downloadPlugin(repo, { branch = '', overwrite = false } = {}) {
|
|
|
78
78
|
return false;
|
|
79
79
|
let dest = path.slice(folder.length);
|
|
80
80
|
if (dest === MAIN) // avoid being possibly loaded before the download is complete
|
|
81
|
-
dest += plugins_1.
|
|
81
|
+
dest += plugins_1.DISABLING_SUFFIX;
|
|
82
82
|
dest = (0, path_1.join)(installPath, dest);
|
|
83
83
|
return (0, promises_1.rm)(dest, { force: true }).then(() => dest, () => false);
|
|
84
84
|
});
|
|
85
85
|
const main = (0, path_1.join)(installPath, MAIN);
|
|
86
|
-
await (0, promises_1.rename)(main + plugins_1.
|
|
86
|
+
await (0, promises_1.rename)(main + plugins_1.DISABLING_SUFFIX, main) // we are good now, restore name
|
|
87
87
|
.catch(e => { throw e.code !== 'ENOENT' ? e : new apiMiddleware_1.ApiError(const_1.HTTP_NOT_ACCEPTABLE, "missing main file"); });
|
|
88
88
|
return folder;
|
|
89
89
|
}
|
|
@@ -132,6 +132,15 @@
|
|
|
132
132
|
"Cancel clipboard": "Annulla appunti",
|
|
133
133
|
"to_clipboard_source": "Torna all'origine'",
|
|
134
134
|
"Paste": "Incolla",
|
|
135
|
-
"clipboard_list": "Elementi negli appunti"
|
|
135
|
+
"clipboard_list": "Elementi negli appunti",
|
|
136
|
+
"Close": "Chiudi",
|
|
137
|
+
"Folder": "Cartella",
|
|
138
|
+
"Web page": "Pagina web",
|
|
139
|
+
"Link": "Link",
|
|
140
|
+
"Auto-play": "Auto-play",
|
|
141
|
+
"autoplay_seconds": "Secondi di attesa mostrando immagini",
|
|
142
|
+
"Select all": "Seleziona tutto",
|
|
143
|
+
"go_first": "Vai all'inizio",
|
|
144
|
+
"go_last": "Vai alla fine"
|
|
136
145
|
}
|
|
137
146
|
}
|
package/src/log.js
CHANGED
|
@@ -37,6 +37,7 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
37
37
|
const util_files_1 = require("./util-files");
|
|
38
38
|
const auth_1 = require("./auth");
|
|
39
39
|
const misc_1 = require("./misc");
|
|
40
|
+
const path_1 = require("path");
|
|
40
41
|
const events_1 = __importDefault(require("./events"));
|
|
41
42
|
const connections_1 = require("./connections");
|
|
42
43
|
const index_1 = require("./index");
|
|
@@ -69,8 +70,8 @@ class Logger {
|
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
// we'll have names same as config keys. These are used also by the get_log api.
|
|
72
|
-
const accessLogger = new Logger(
|
|
73
|
-
const accessErrorLog = new Logger(
|
|
73
|
+
const accessLogger = new Logger(misc_1.CFG.log);
|
|
74
|
+
const accessErrorLog = new Logger(misc_1.CFG.error_log);
|
|
74
75
|
exports.loggers = [accessLogger, accessErrorLog];
|
|
75
76
|
(0, config_1.defineConfig)(accessLogger.name, 'logs/access.log').sub(path => {
|
|
76
77
|
console.debug('access log file: ' + (path || 'disabled'));
|
|
@@ -81,9 +82,9 @@ errorLogFile.sub(path => {
|
|
|
81
82
|
console.debug('access error log: ' + (path || 'disabled'));
|
|
82
83
|
accessErrorLog.setPath(path);
|
|
83
84
|
});
|
|
84
|
-
const logRotation = (0, config_1.defineConfig)(
|
|
85
|
-
const dontLogNet = (0, config_1.defineConfig)(
|
|
86
|
-
const logUA = (0, config_1.defineConfig)(
|
|
85
|
+
const logRotation = (0, config_1.defineConfig)(misc_1.CFG.log_rotation, 'weekly');
|
|
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
|
+
const logUA = (0, config_1.defineConfig)(misc_1.CFG.log_ua, false);
|
|
87
88
|
const debounce = lodash_1.default.debounce(cb => cb(), 1000);
|
|
88
89
|
const logMw = async (ctx, next) => {
|
|
89
90
|
const now = new Date();
|
|
@@ -113,9 +114,10 @@ const logMw = async (ctx, next) => {
|
|
|
113
114
|
|| rotate === 'd' && (passed >= misc_1.DAY || now.getDate() !== last.getDate()) // checking passed will solve the case when the day of the month is the same but a month has passed
|
|
114
115
|
|| rotate === 'w' && (passed >= 7 * misc_1.DAY || now.getDay() < last.getDay())) {
|
|
115
116
|
stream.end();
|
|
116
|
-
const
|
|
117
|
+
const suffix = '-' + last.getFullYear() + '-' + doubleDigit(last.getMonth() + 1) + '-' + doubleDigit(last.getDate());
|
|
118
|
+
const newPath = (0, misc_1.strinsert)(path, path.length - (0, path_1.extname)(path).length, suffix);
|
|
117
119
|
try { // other logging requests shouldn't happen while we are renaming. Since this is very infrequent we can tolerate solving this by making it sync.
|
|
118
|
-
(0, fs_1.renameSync)(path,
|
|
120
|
+
(0, fs_1.renameSync)(path, newPath);
|
|
119
121
|
}
|
|
120
122
|
catch (e) { // ok, rename failed, but this doesn't mean we ain't gonna log
|
|
121
123
|
console.error(String(e || e.message));
|
package/src/middlewares.js
CHANGED
|
@@ -96,36 +96,40 @@ function getProxyDetected() {
|
|
|
96
96
|
}
|
|
97
97
|
exports.getProxyDetected = getProxyDetected;
|
|
98
98
|
const prepareState = async (ctx, next) => {
|
|
99
|
-
var _a;
|
|
100
|
-
if (ctx.session) {
|
|
99
|
+
var _a, _b;
|
|
100
|
+
if ((_a = ctx.session) === null || _a === void 0 ? void 0 : _a.username) {
|
|
101
101
|
if (auth_1.invalidSessions.delete(ctx.session.username))
|
|
102
102
|
delete ctx.session.username;
|
|
103
103
|
ctx.session.maxAge = exports.sessionDuration.compiled();
|
|
104
104
|
}
|
|
105
105
|
// calculate these once and for all
|
|
106
106
|
ctx.state.connection = (0, connections_1.socket2connection)(ctx.socket);
|
|
107
|
-
const a = ctx.state.account = await urlLogin() || await getHttpAccount() || (0, perm_1.getAccount)((
|
|
107
|
+
const a = ctx.state.account = await urlLogin() || await getHttpAccount() || (0, perm_1.getAccount)((_b = ctx.session) === null || _b === void 0 ? void 0 : _b.username, false);
|
|
108
108
|
if (a && !(0, perm_1.accountCanLogin)(a))
|
|
109
109
|
ctx.state.account = undefined;
|
|
110
110
|
ctx.state.revProxyPath = ctx.get('x-forwarded-prefix');
|
|
111
111
|
(0, connections_1.updateConnectionForCtx)(ctx);
|
|
112
112
|
await next();
|
|
113
|
-
|
|
113
|
+
function urlLogin() {
|
|
114
114
|
const { login } = ctx.query;
|
|
115
115
|
if (!login)
|
|
116
116
|
return;
|
|
117
117
|
const [u, p] = (0, misc_1.splitAt)(':', String(login));
|
|
118
|
+
ctx.redirect(ctx.originalUrl.slice(0, -ctx.querystring.length - 1)); // redirect to hide credentials
|
|
119
|
+
return doLogin(u, p);
|
|
120
|
+
}
|
|
121
|
+
function getHttpAccount() {
|
|
122
|
+
const credentials = (0, basic_auth_1.default)(ctx.req);
|
|
123
|
+
return doLogin((credentials === null || credentials === void 0 ? void 0 : credentials.name) || '', (credentials === null || credentials === void 0 ? void 0 : credentials.pass) || '');
|
|
124
|
+
}
|
|
125
|
+
async function doLogin(u, p) {
|
|
118
126
|
const a = await (0, auth_1.srpCheck)(u, p);
|
|
119
127
|
if (a) {
|
|
120
|
-
ctx
|
|
121
|
-
ctx.
|
|
128
|
+
(0, auth_1.setLoggedIn)(ctx, a.username);
|
|
129
|
+
ctx.headers['x-username'] = a.username; // give an easier way to determine if the login was successful
|
|
122
130
|
}
|
|
123
131
|
return a;
|
|
124
132
|
}
|
|
125
|
-
async function getHttpAccount() {
|
|
126
|
-
const credentials = (0, basic_auth_1.default)(ctx.req);
|
|
127
|
-
return (0, auth_1.srpCheck)((credentials === null || credentials === void 0 ? void 0 : credentials.name) || '', (credentials === null || credentials === void 0 ? void 0 : credentials.pass) || '');
|
|
128
|
-
}
|
|
129
133
|
};
|
|
130
134
|
exports.prepareState = prepareState;
|
|
131
135
|
const paramsDecoder = async (ctx, next) => {
|
package/src/misc.js
CHANGED
|
@@ -118,6 +118,10 @@ function asyncGeneratorToReadable(generator) {
|
|
|
118
118
|
const iterator = generator[Symbol.asyncIterator]();
|
|
119
119
|
return new stream_1.Readable({
|
|
120
120
|
objectMode: true,
|
|
121
|
+
destroy() {
|
|
122
|
+
var _a;
|
|
123
|
+
(_a = iterator.return) === null || _a === void 0 ? void 0 : _a.call(iterator);
|
|
124
|
+
},
|
|
121
125
|
read() {
|
|
122
126
|
iterator.next().then(it => this.push(it.done ? null : it.value));
|
|
123
127
|
}
|
package/src/nat.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getNatInfo = exports.
|
|
6
|
+
exports.getNatInfo = exports.getPublicIps = exports.upnpClient = exports.defaultBaseUrl = void 0;
|
|
7
7
|
const valtio_1 = require("valtio");
|
|
8
8
|
const nat_upnp_ts_1 = require("nat-upnp-ts");
|
|
9
9
|
const debounceAsync_1 = require("./debounceAsync");
|
|
@@ -27,10 +27,6 @@ exports.defaultBaseUrl = (0, valtio_1.proxy)({
|
|
|
27
27
|
return `${this.proto}://${this.publicIps[0] || this.externalIp || this.localIp}${!this.port || this.port === defPort ? '' : ':' + this.port}`;
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
|
-
function getBaseUrlOrDefault() {
|
|
31
|
-
return listen_1.baseUrl.get() || exports.defaultBaseUrl.get();
|
|
32
|
-
}
|
|
33
|
-
exports.getBaseUrlOrDefault = getBaseUrlOrDefault;
|
|
34
30
|
exports.upnpClient = new nat_upnp_ts_1.Client({ timeout: 4000 });
|
|
35
31
|
const originalMethod = exports.upnpClient.getGateway;
|
|
36
32
|
// other client methods call getGateway too, so this will ensure they reuse this same result
|
|
@@ -42,10 +38,10 @@ exports.upnpClient.getGateway().then(res => {
|
|
|
42
38
|
(0, cross_1.repeat)(10 * cross_1.MINUTE, () => exports.upnpClient.getPublicIp().then(v => {
|
|
43
39
|
if (v === exports.defaultBaseUrl.externalIp)
|
|
44
40
|
return;
|
|
45
|
-
getPublicIps.clearRetain();
|
|
41
|
+
exports.getPublicIps.clearRetain();
|
|
46
42
|
return exports.defaultBaseUrl.externalIp = v;
|
|
47
43
|
}));
|
|
48
|
-
|
|
44
|
+
exports.getPublicIps = (0, debounceAsync_1.debounceAsync)(async () => {
|
|
49
45
|
const res = await (0, github_1.getProjectInfo)();
|
|
50
46
|
const groupedByVersion = Object.values(lodash_1.default.groupBy(res.publicIpServices, x => { var _a; return (_a = x.v) !== null && _a !== void 0 ? _a : 4; }));
|
|
51
47
|
const ips = await (0, cross_1.promiseBestEffort)(groupedByVersion.map(singleVersion => Promise.any(singleVersion.map(async (svc) => {
|
|
@@ -69,7 +65,7 @@ const getPublicIps = (0, debounceAsync_1.debounceAsync)(async () => {
|
|
|
69
65
|
}, 0, { retain: 10 * cross_1.MINUTE });
|
|
70
66
|
exports.getNatInfo = (0, debounceAsync_1.debounceAsync)(async () => {
|
|
71
67
|
var _a, _b, _c, _d;
|
|
72
|
-
const gettingIps = getPublicIps(); // don't wait, do it in parallel
|
|
68
|
+
const gettingIps = (0, exports.getPublicIps)(); // don't wait, do it in parallel
|
|
73
69
|
const res = await exports.upnpClient.getGateway().catch(() => null);
|
|
74
70
|
const status = await (0, listen_1.getServerStatus)();
|
|
75
71
|
const mappings = res && await (0, cross_1.haveTimeout)(5000, exports.upnpClient.getMappings()).catch(() => null);
|
package/src/perm.js
CHANGED
|
@@ -4,13 +4,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.accountCanLoginAdmin = exports.accountCanLogin = exports.accountHasPassword = exports.getFromAccount = exports.delAccount = exports.
|
|
7
|
+
exports.changeSrpHelper = exports.accountCanLoginAdmin = exports.accountCanLogin = exports.accountHasPassword = exports.getFromAccount = exports.delAccount = exports.addAccount = exports.renameAccount = exports.normalizeUsername = exports.accountsConfig = exports.updateAccount = exports.createAdmin = exports.saveSrpInfo = exports.getAccount = exports.expandUsername = void 0;
|
|
8
8
|
const lodash_1 = __importDefault(require("lodash"));
|
|
9
|
-
const crypt_1 = require("./crypt");
|
|
10
9
|
const misc_1 = require("./misc");
|
|
11
10
|
const config_1 = require("./config");
|
|
12
11
|
const tssrp6a_1 = require("tssrp6a");
|
|
13
12
|
const events_1 = __importDefault(require("./events"));
|
|
13
|
+
const apiMiddleware_1 = require("./apiMiddleware");
|
|
14
14
|
let accounts = {};
|
|
15
15
|
// provides the username and all other usernames it inherits based on the 'belongs' attribute. Useful to check permissions
|
|
16
16
|
function expandUsername(who) {
|
|
@@ -37,7 +37,6 @@ function saveSrpInfo(account, salt, verifier) {
|
|
|
37
37
|
account.srp = String(salt) + '|' + String(verifier);
|
|
38
38
|
}
|
|
39
39
|
exports.saveSrpInfo = saveSrpInfo;
|
|
40
|
-
exports.allowClearTextLogin = (0, config_1.defineConfig)('allow_clear_text_login', false);
|
|
41
40
|
const createAdminConfig = (0, config_1.defineConfig)('create-admin', '');
|
|
42
41
|
createAdminConfig.sub(v => {
|
|
43
42
|
if (!v)
|
|
@@ -45,31 +44,29 @@ createAdminConfig.sub(v => {
|
|
|
45
44
|
createAdmin(v);
|
|
46
45
|
createAdminConfig.set('');
|
|
47
46
|
});
|
|
48
|
-
function createAdmin(
|
|
47
|
+
function createAdmin(password, username = 'admin') {
|
|
49
48
|
const acc = addAccount(username, { admin: true });
|
|
50
49
|
if (!acc)
|
|
51
50
|
return console.log("cannot create, already exists");
|
|
52
|
-
updateAccount(acc,
|
|
51
|
+
updateAccount(acc, { password });
|
|
53
52
|
console.log("account admin created");
|
|
54
53
|
}
|
|
55
54
|
exports.createAdmin = createAdmin;
|
|
56
55
|
const srp6aNimbusRoutines = new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters());
|
|
57
|
-
async function updateAccount(account,
|
|
58
|
-
const
|
|
59
|
-
|
|
56
|
+
async function updateAccount(account, change) {
|
|
57
|
+
const jsonWas = JSON.stringify(account);
|
|
58
|
+
const { username: usernameWas } = account;
|
|
59
|
+
if (typeof change === 'function')
|
|
60
|
+
await (change === null || change === void 0 ? void 0 : change(account));
|
|
61
|
+
else
|
|
62
|
+
Object.assign(account, (0, misc_1.objSameKeys)(change, x => x || undefined));
|
|
60
63
|
const { username } = account;
|
|
61
64
|
if (account.password) {
|
|
62
65
|
console.debug('hashing password for', username);
|
|
63
|
-
if (exports.allowClearTextLogin.get())
|
|
64
|
-
account.hashed_password = await (0, crypt_1.hashPassword)(account.password);
|
|
65
66
|
const res = await (0, tssrp6a_1.createVerifierAndSalt)(srp6aNimbusRoutines, username, account.password);
|
|
66
67
|
saveSrpInfo(account, res.s, res.v);
|
|
67
68
|
delete account.password;
|
|
68
69
|
}
|
|
69
|
-
else if (!account.srp && account.hashed_password) {
|
|
70
|
-
console.log('please reset password for account', username);
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
70
|
if (account.belongs) {
|
|
74
71
|
account.belongs = (0, misc_1.wantArray)(account.belongs);
|
|
75
72
|
lodash_1.default.remove(account.belongs, b => {
|
|
@@ -79,7 +76,10 @@ async function updateAccount(account, changer) {
|
|
|
79
76
|
return true;
|
|
80
77
|
});
|
|
81
78
|
}
|
|
82
|
-
|
|
79
|
+
account.expire && (account.expire = new Date(account.expire));
|
|
80
|
+
if (username !== usernameWas)
|
|
81
|
+
renameAccount(usernameWas, username);
|
|
82
|
+
if (jsonWas !== JSON.stringify(account))
|
|
83
83
|
saveAccountsAsap();
|
|
84
84
|
}
|
|
85
85
|
exports.updateAccount = updateAccount;
|
|
@@ -96,7 +96,7 @@ exports.accountsConfig.sub(obj => {
|
|
|
96
96
|
saveAccountsAsap();
|
|
97
97
|
(0, misc_1.setHidden)(rec, { username: norm });
|
|
98
98
|
}
|
|
99
|
-
updateAccount(rec).then(); // work
|
|
99
|
+
updateAccount(rec, {}).then(); // work fields
|
|
100
100
|
});
|
|
101
101
|
});
|
|
102
102
|
function normalizeUsername(username) {
|
|
@@ -126,30 +126,15 @@ function renameAccount(from, to) {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
exports.renameAccount = renameAccount;
|
|
129
|
-
// we consider all the following fields, when falsy, as equivalent to be missing. If this changes in the future, please adjust addAccount and setAccount
|
|
130
|
-
const assignableProps = ['redirect', 'ignore_limits', 'belongs', 'admin', 'disabled', 'disable_password_change'];
|
|
131
129
|
function addAccount(username, props) {
|
|
132
130
|
username = normalizeUsername(username);
|
|
133
131
|
if (!username || getAccount(username, false))
|
|
134
132
|
return;
|
|
135
|
-
const
|
|
136
|
-
const copy = (0, misc_1.setHidden)(filteredProps, { username }); // have the field in the object but hidden so that stringification won't include it
|
|
133
|
+
const copy = (0, misc_1.setHidden)(lodash_1.default.pickBy(props, Boolean), { username }); // have the field in the object but hidden so that stringification won't include it
|
|
137
134
|
exports.accountsConfig.set(accounts => Object.assign(accounts, { [username]: copy }));
|
|
138
135
|
return copy;
|
|
139
136
|
}
|
|
140
137
|
exports.addAccount = addAccount;
|
|
141
|
-
function setAccount(acc, changes) {
|
|
142
|
-
const rest = lodash_1.default.pick(changes, assignableProps);
|
|
143
|
-
for (const [k, v] of Object.entries(rest))
|
|
144
|
-
if (!v)
|
|
145
|
-
rest[k] = undefined;
|
|
146
|
-
Object.assign(acc, rest);
|
|
147
|
-
if (changes.username)
|
|
148
|
-
renameAccount(acc.username, changes.username);
|
|
149
|
-
saveAccountsAsap();
|
|
150
|
-
return acc;
|
|
151
|
-
}
|
|
152
|
-
exports.setAccount = setAccount;
|
|
153
138
|
function delAccount(username) {
|
|
154
139
|
if (!getAccount(username))
|
|
155
140
|
return false;
|
|
@@ -174,7 +159,7 @@ function getFromAccount(account, getter) {
|
|
|
174
159
|
}
|
|
175
160
|
exports.getFromAccount = getFromAccount;
|
|
176
161
|
function accountHasPassword(account) {
|
|
177
|
-
return Boolean(account.password || account.
|
|
162
|
+
return Boolean(account.password || account.srp);
|
|
178
163
|
}
|
|
179
164
|
exports.accountHasPassword = accountHasPassword;
|
|
180
165
|
function accountCanLogin(account) {
|
|
@@ -183,9 +168,18 @@ function accountCanLogin(account) {
|
|
|
183
168
|
exports.accountCanLogin = accountCanLogin;
|
|
184
169
|
function allDisabled(account) {
|
|
185
170
|
var _a;
|
|
186
|
-
return Boolean(account.disabled
|
|
171
|
+
return Boolean(account.disabled
|
|
172
|
+
|| account.expire < Date.now()
|
|
173
|
+
|| ((_a = account.belongs) === null || _a === void 0 ? void 0 : _a.map(u => getAccount(u, false)).every(a => a && allDisabled(a))));
|
|
187
174
|
}
|
|
188
175
|
function accountCanLoginAdmin(account) {
|
|
189
176
|
return accountCanLogin(account) && Boolean(getFromAccount(account, a => a.admin));
|
|
190
177
|
}
|
|
191
178
|
exports.accountCanLoginAdmin = accountCanLoginAdmin;
|
|
179
|
+
async function changeSrpHelper(account, salt, verifier) {
|
|
180
|
+
if (!salt || !verifier)
|
|
181
|
+
return new apiMiddleware_1.ApiError(misc_1.HTTP_BAD_REQUEST, 'missing parameters');
|
|
182
|
+
await updateAccount(account, account => saveSrpInfo(account, salt, verifier));
|
|
183
|
+
return {};
|
|
184
|
+
}
|
|
185
|
+
exports.changeSrpHelper = changeSrpHelper;
|
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.
|
|
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;
|
|
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"));
|
|
@@ -43,7 +43,7 @@ const connections_1 = require("./connections");
|
|
|
43
43
|
const path_1 = require("path");
|
|
44
44
|
const customHtml_1 = require("./customHtml");
|
|
45
45
|
exports.PATH = 'plugins';
|
|
46
|
-
exports.
|
|
46
|
+
exports.DISABLING_SUFFIX = '-disabled';
|
|
47
47
|
exports.STORAGE_FOLDER = 'storage';
|
|
48
48
|
const plugins = {};
|
|
49
49
|
function isPluginRunning(id) {
|
|
@@ -294,7 +294,7 @@ async function rescan() {
|
|
|
294
294
|
patterns.push((0, misc_1.adjustStaticPathForGlob)(const_1.APP_PATH) + '/' + patterns[0]);
|
|
295
295
|
const met = [];
|
|
296
296
|
for (const { path, dirent } of await (0, fast_glob_1.default)(patterns, { onlyFiles: false, suppressErrors: true, objectMode: true })) {
|
|
297
|
-
if (!dirent.isDirectory() || path.endsWith(exports.
|
|
297
|
+
if (!dirent.isDirectory() || path.endsWith(exports.DISABLING_SUFFIX))
|
|
298
298
|
continue;
|
|
299
299
|
const id = path.split('/').slice(-1)[0];
|
|
300
300
|
met.push(id);
|
package/src/serveGuiFiles.js
CHANGED
|
@@ -42,7 +42,7 @@ const customHtml_1 = require("./customHtml");
|
|
|
42
42
|
const lodash_1 = __importDefault(require("lodash"));
|
|
43
43
|
const config_1 = require("./config");
|
|
44
44
|
const lang_1 = require("./lang");
|
|
45
|
-
exports.logGui = (0, config_1.defineConfig)(
|
|
45
|
+
exports.logGui = (0, config_1.defineConfig)(misc_1.CFG.log_gui, false);
|
|
46
46
|
lodash_1.default.each(misc_1.FRONTEND_OPTIONS, (v, k) => (0, config_1.defineConfig)(k, v)); // define default values
|
|
47
47
|
// in case of dev env we have our static files within the 'dist' folder'
|
|
48
48
|
const DEV_STATIC = process.env.DEV ? 'dist/' : '';
|
|
@@ -143,6 +143,7 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
143
143
|
`);
|
|
144
144
|
if (isFrontend)
|
|
145
145
|
ret = ret
|
|
146
|
+
.replace('<head>', '<head>' + (0, customHtml_1.getSection)('htmlHead'))
|
|
146
147
|
.replace('<body>', '<body>' + (0, customHtml_1.getSection)('top'))
|
|
147
148
|
.replace('</body>', (0, customHtml_1.getSection)('bottom') + '</body>');
|
|
148
149
|
return ret;
|
package/src/util-http.js
CHANGED
|
@@ -4,22 +4,26 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.httpStream = exports.httpString = exports.stream2string = void 0;
|
|
7
|
+
exports.httpStream = exports.httpWithBody = exports.httpString = exports.stream2string = void 0;
|
|
8
8
|
const node_https_1 = __importDefault(require("node:https"));
|
|
9
9
|
const node_http_1 = __importDefault(require("node:http"));
|
|
10
10
|
const node_stream_1 = require("node:stream");
|
|
11
11
|
const lodash_1 = __importDefault(require("lodash"));
|
|
12
12
|
const consumers_1 = require("node:stream/consumers");
|
|
13
13
|
Object.defineProperty(exports, "stream2string", { enumerable: true, get: function () { return consumers_1.text; } });
|
|
14
|
-
// in case the response is not 2xx, it will throw and the error object is the Response object
|
|
15
14
|
async function httpString(url, options) {
|
|
16
|
-
|
|
17
|
-
if (!lodash_1.default.inRange(res.statusCode, 200, 299))
|
|
18
|
-
throw res;
|
|
19
|
-
return await (0, consumers_1.text)(res);
|
|
15
|
+
return await (0, consumers_1.text)(await httpStream(url, options));
|
|
20
16
|
}
|
|
21
17
|
exports.httpString = httpString;
|
|
22
|
-
function
|
|
18
|
+
async function httpWithBody(url, options) {
|
|
19
|
+
const req = await httpStream(url, options);
|
|
20
|
+
return Object.assign(req, {
|
|
21
|
+
ok: lodash_1.default.inRange(req.statusCode, 200, 300),
|
|
22
|
+
body: req.statusCode ? await (0, consumers_1.buffer)(req) : undefined,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
exports.httpWithBody = httpWithBody;
|
|
26
|
+
function httpStream(url, { body, jar, noRedirect, httpThrow, ...options } = {}) {
|
|
23
27
|
return new Promise((resolve, reject) => {
|
|
24
28
|
var _a, _b;
|
|
25
29
|
var _c;
|
|
@@ -45,7 +49,7 @@ function httpStream(url, { body, jar, noRedirect, ...options } = {}) {
|
|
|
45
49
|
else
|
|
46
50
|
delete jar[k];
|
|
47
51
|
}
|
|
48
|
-
if (!res.statusCode || res.statusCode >= 400)
|
|
52
|
+
if (!res.statusCode || (httpThrow !== null && httpThrow !== void 0 ? httpThrow : true) && res.statusCode >= 400)
|
|
49
53
|
return reject(new Error(String(res.statusCode), { cause: res }));
|
|
50
54
|
if (res.headers.location && !noRedirect)
|
|
51
55
|
return resolve(httpStream(res.headers.location, options));
|