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.
Files changed (40) hide show
  1. package/admin/assets/index-V0udjUIR.js +808 -0
  2. package/admin/assets/{index-49472c6b.css → index-rD-EYdVL.css} +1 -1
  3. package/{frontend/assets/sha512-l_aqBm0S.js → admin/assets/sha512-kdrIq7UN.js} +1 -1
  4. package/admin/index.html +2 -3
  5. package/frontend/assets/index-EdGoLuWB.css +1 -0
  6. package/frontend/assets/index-r8i8c_PX.js +101 -0
  7. package/{admin/assets/sha512-c9d3d113.js → frontend/assets/sha512-4UUUqqOn.js} +1 -1
  8. package/frontend/index.html +2 -2
  9. package/package.json +9 -7
  10. package/src/adminApis.js +4 -86
  11. package/src/api.accounts.js +12 -15
  12. package/src/api.auth.js +9 -35
  13. package/src/api.log.js +74 -0
  14. package/src/api.monitor.js +5 -12
  15. package/src/api.net.js +1 -1
  16. package/src/api.vfs.js +4 -4
  17. package/src/apiMiddleware.js +1 -1
  18. package/src/auth.js +17 -8
  19. package/src/block.js +1 -1
  20. package/src/commands.js +1 -3
  21. package/src/const.js +1 -1
  22. package/src/cross.js +25 -7
  23. package/src/customHtml.js +1 -1
  24. package/src/ddns.js +53 -0
  25. package/src/github.js +2 -2
  26. package/src/langs/hfs-lang-it.json +10 -1
  27. package/src/log.js +9 -7
  28. package/src/middlewares.js +14 -10
  29. package/src/misc.js +4 -0
  30. package/src/nat.js +4 -8
  31. package/src/perm.js +28 -34
  32. package/src/plugins.js +3 -3
  33. package/src/serveGuiFiles.js +2 -1
  34. package/src/util-http.js +12 -8
  35. package/admin/assets/index-30c70443.js +0 -808
  36. package/frontend/assets/index-Bsm0eh8l.js +0 -101
  37. package/frontend/assets/index-tdvGCFPn.css +0 -1
  38. package/src/api.helpers.js +0 -28
  39. package/src/crypt.js +0 -21
  40. 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.hasProp = 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.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.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(every, cb) {
300
- return Promise.allSettled([cb()]).then(() => setTimeout(() => repeat(every, cb), every));
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.DISABLING_POSTFIX;
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.DISABLING_POSTFIX, main) // we are good now, restore name
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('log');
73
- const accessErrorLog = new Logger('error_log');
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)('log_rotation', 'weekly');
85
- const dontLogNet = (0, config_1.defineConfig)('dont_log_net', '127.0.0.1|::1', v => (0, misc_1.makeNetMatcher)(v));
86
- const logUA = (0, config_1.defineConfig)('log_ua', false);
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 postfix = last.getFullYear() + '-' + doubleDigit(last.getMonth() + 1) + '-' + doubleDigit(last.getDate());
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, path + '-' + postfix);
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));
@@ -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)((_a = ctx.session) === null || _a === void 0 ? void 0 : _a.username, false);
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
- async function urlLogin() {
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.session.username = a.username;
121
- ctx.redirect(ctx.originalUrl.slice(0, -ctx.querystring.length - 1));
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.upnpClient = exports.getBaseUrlOrDefault = exports.defaultBaseUrl = void 0;
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
- const getPublicIps = (0, debounceAsync_1.debounceAsync)(async () => {
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.setAccount = exports.addAccount = exports.renameAccount = exports.normalizeUsername = exports.accountsConfig = exports.updateAccount = exports.createAdmin = exports.allowClearTextLogin = exports.saveSrpInfo = exports.getAccount = exports.expandUsername = void 0;
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(pass, username = 'admin') {
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, acc => { acc.password = pass; });
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, changer) {
58
- const was = JSON.stringify(account);
59
- await (changer === null || changer === void 0 ? void 0 : changer(account));
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
- if (was !== JSON.stringify(account))
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 password fields
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 filteredProps = lodash_1.default.pickBy(lodash_1.default.pick(props, assignableProps), Boolean);
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.hashed_password || account.srp);
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 || ((_a = account.belongs) === null || _a === void 0 ? void 0 : _a.map(u => getAccount(u, false)).every(a => a && allDisabled(a))));
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.DISABLING_POSTFIX = exports.PATH = void 0;
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.DISABLING_POSTFIX = '-disabled';
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.DISABLING_POSTFIX))
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);
@@ -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)('log_gui', false);
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
- const res = await httpStream(url, options);
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 httpStream(url, { body, jar, noRedirect, ...options } = {}) {
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));