hfs 0.49.2 → 0.50.0-alpha3

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/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.isWindowsDrive = exports.isIP = exports.isPrimitive = exports.formatTimestamp = exports.repeat = exports.asyncGeneratorToArray = exports.filterMapGenerator = exports.throw_ = exports.hasProp = exports.typedEntries = exports.typedKeys = exports.objRenameKey = exports.randomId = exports.getOrSet = exports.waitFor = exports.newObj = exports.removeStarting = exports.findDefined = exports.isOrderedEqual = exports.swap = exports.tryJson = exports.basename = exports.pendingPromise = exports._dbg = exports._log = exports.wantArray = exports.formatPerc = exports.with_ = exports.try_ = exports.setHidden = exports.onlyTruthy = exports.truthy = exports.enforceFinal = exports.objSameKeys = exports.haveTimeout = exports.wait = exports.prefix = exports.formatBytes = exports.isWhoObject = exports.PERM_KEYS = exports.defaultPerms = exports.WHO_ANY_ACCOUNT = exports.WHO_NO_ONE = exports.WHO_ANYONE = exports.MAX_TILES_SIZE = exports.DAY = exports.HOUR = exports.MINUTE = exports.WIKI_URL = exports.REPO_URL = void 0;
21
- exports.runAt = exports.promiseBestEffort = exports.escapeHTML = exports.ipForUrl = exports.ipLocalHost = exports.xlate = exports.isEqualLax = void 0;
20
+ exports.filterMapGenerator = exports.throw_ = exports.hasProp = exports.typedEntries = exports.typedKeys = exports.objRenameKey = exports.randomId = exports.getOrSet = exports.waitFor = exports.newObj = exports.removeStarting = exports.findDefined = exports.isOrderedEqual = exports.swap = exports.tryJson = 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.enforceFinal = exports.objSameKeys = exports.haveTimeout = exports.wait = exports.prefix = 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.runAt = exports.promiseBestEffort = exports.escapeHTML = exports.ipForUrl = exports.ipLocalHost = exports.xlate = exports.isEqualLax = exports.isWindowsDrive = exports.isIP = exports.isPrimitive = exports.formatTimestamp = exports.repeat = exports.asyncGeneratorToArray = 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"));
@@ -28,7 +28,20 @@ exports.WIKI_URL = exports.REPO_URL + 'wiki/';
28
28
  exports.MINUTE = 60000;
29
29
  exports.HOUR = 60 * exports.MINUTE;
30
30
  exports.DAY = 24 * exports.HOUR;
31
- exports.MAX_TILES_SIZE = 10;
31
+ exports.MAX_TILE_SIZE = 10;
32
+ exports.FRONTEND_OPTIONS = {
33
+ file_menu_on_link: true,
34
+ tile_size: 0,
35
+ sort_by: 'name',
36
+ invert_order: false,
37
+ folders_first: true,
38
+ sort_numerics: false,
39
+ theme: '',
40
+ };
41
+ exports.SORT_BY_OPTIONS = ['name', 'extension', 'size', 'time'];
42
+ exports.THEME_OPTIONS = { auto: '', light: 'light', dark: 'dark' };
43
+ exports.CFG = constMap(['geo_enable', 'geo_allow', 'geo_list', 'geo_allow_unknown']);
44
+ exports.LIST = { add: '+', remove: '-', update: '=', props: 'props', ready: 'ready', error: 'e' };
32
45
  exports.WHO_ANYONE = true;
33
46
  exports.WHO_NO_ONE = false;
34
47
  exports.WHO_ANY_ACCOUNT = '*';
@@ -41,6 +54,9 @@ exports.defaultPerms = {
41
54
  can_archive: 'can_read'
42
55
  };
43
56
  exports.PERM_KEYS = typedKeys(exports.defaultPerms);
57
+ function constMap(a) {
58
+ return Object.fromEntries(a.map(x => [x, x]));
59
+ }
44
60
  function isWhoObject(v) {
45
61
  return v !== null && typeof v === 'object' && !Array.isArray(v);
46
62
  }
@@ -76,6 +92,13 @@ function enforceFinal(sub, s, evenEmpty = false) {
76
92
  return !evenEmpty && !s || s.endsWith(sub) ? s : s + sub;
77
93
  }
78
94
  exports.enforceFinal = enforceFinal;
95
+ function splitAt(sub, all) {
96
+ if (typeof sub === 'number')
97
+ return [all.slice(0, sub), all.slice(sub + 1)];
98
+ const i = all.indexOf(sub);
99
+ return i < 0 ? [all, ''] : [all.slice(0, i), all.slice(i + sub.length)];
100
+ }
101
+ exports.splitAt = splitAt;
79
102
  function truthy(value) {
80
103
  return Boolean(value);
81
104
  }
@@ -312,6 +335,7 @@ async function promiseBestEffort(promises) {
312
335
  return res.filter(x => x.status === 'fulfilled').map((x) => x.value);
313
336
  }
314
337
  exports.promiseBestEffort = promiseBestEffort;
338
+ // run at a specific point in time, also solving the limit of setTimeout, which doesn't work with +32bit delays
315
339
  function runAt(ts, cb) {
316
340
  let cancel = false;
317
341
  let t;
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'];
11
+ 'top', 'bottom', 'afterEntryName', 'beforeLogin', 'unauthorized'];
12
12
  exports.customHtmlState = (0, vanilla_1.proxy)({
13
13
  sections: watchLoadCustomHtml().state
14
14
  });
package/src/geo.js ADDED
@@ -0,0 +1,61 @@
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.geoFilter = exports.ip2country = void 0;
7
+ const config_1 = require("./config");
8
+ const misc_1 = require("./misc");
9
+ const promises_1 = require("node:fs/promises");
10
+ const ip2location_nodejs_1 = require("ip2location-nodejs");
11
+ const lodash_1 = __importDefault(require("lodash"));
12
+ const connections_1 = require("./connections");
13
+ const ip2location = new ip2location_nodejs_1.IP2Location();
14
+ const enabled = (0, config_1.defineConfig)(misc_1.CFG.geo_enable, false);
15
+ const allow = (0, config_1.defineConfig)(misc_1.CFG.geo_allow, null);
16
+ const list = (0, config_1.defineConfig)(misc_1.CFG.geo_list, []);
17
+ const allowUnknown = (0, config_1.defineConfig)(misc_1.CFG.geo_allow_unknown, false);
18
+ enabled.sub(checkFiles);
19
+ setInterval(checkFiles, misc_1.DAY); // keep updated at run-time
20
+ exports.ip2country = lodash_1.default.memoize((ip) => ip2location.getCountryShortAsync(ip).then(v => v === '-' ? '' : v, () => ''));
21
+ const geoFilter = async (ctx, next) => {
22
+ var _a;
23
+ if (allow.get() !== null && !(0, misc_1.isLocalHost)(ctx)) {
24
+ const { connection } = ctx.state;
25
+ const country = (_a = connection.country) !== null && _a !== void 0 ? _a : (connection.country = await (0, exports.ip2country)(ctx.ip));
26
+ (0, connections_1.updateConnection)(connection, { country });
27
+ if (country ? list.get().includes(country) !== allow.get() : !allowUnknown.get())
28
+ return ctx.socket.destroy();
29
+ }
30
+ return next();
31
+ };
32
+ exports.geoFilter = geoFilter;
33
+ function isOpen() {
34
+ return Boolean(ip2location.getPackageVersion());
35
+ }
36
+ async function checkFiles() {
37
+ var _a, _b;
38
+ if (!enabled.get())
39
+ return;
40
+ const ZIP_FILE = 'IP2LOCATION-LITE-DB1.IPV6.BIN';
41
+ const URL = `https://download.ip2location.com/lite/${ZIP_FILE}.ZIP`;
42
+ const LOCAL_FILE = 'geo_ip.bin';
43
+ const TEMP = LOCAL_FILE + '.downloading';
44
+ const { mtime = 0 } = await (0, promises_1.stat)(LOCAL_FILE).catch(() => ({ mtime: 0 }));
45
+ const now = Date.now();
46
+ if (mtime < now - 31 * misc_1.DAY) { // month-old or non-existing
47
+ console.log('downloading geo-ip db');
48
+ await (0, misc_1.unzip)(await (0, misc_1.httpStream)(URL), path => path.toUpperCase().endsWith(ZIP_FILE) && TEMP);
49
+ if (await (0, promises_1.stat)(TEMP))
50
+ if (isOpen())
51
+ ip2location.close();
52
+ await (0, promises_1.unlink)(LOCAL_FILE).catch(() => { });
53
+ await (0, promises_1.rename)(TEMP, LOCAL_FILE);
54
+ (_b = (_a = exports.ip2country.cache).clear) === null || _b === void 0 ? void 0 : _b.call(_a);
55
+ console.log('download geo-ip db completed');
56
+ }
57
+ else if (isOpen())
58
+ return;
59
+ console.debug('loading geo-ip db');
60
+ ip2location.open(LOCAL_FILE); // using openAsync causes a DEP0137 error within 10 seconds
61
+ }
package/src/index.js CHANGED
@@ -27,16 +27,19 @@ const misc_1 = require("./misc");
27
27
  const koa_session_1 = __importDefault(require("koa-session"));
28
28
  const selfCheck_1 = require("./selfCheck");
29
29
  const acme_1 = require("./acme");
30
+ require("./geo");
31
+ const geo_1 = require("./geo");
30
32
  (0, assert_1.ok)(lodash_1.default.intersection(Object.keys(frontEndApis_1.frontEndApis), Object.keys(adminApis_1.adminApis)).length === 0); // they share same endpoints, don't clash
31
33
  process.title = 'HFS ' + const_1.VERSION;
32
34
  const keys = ((_a = process.env.COOKIE_SIGN_KEYS) === null || _a === void 0 ? void 0 : _a.split(','))
33
35
  || [(0, misc_1.randomId)(30)]; // randomness at start gives some extra security, btu also invalidates existing sessions
34
36
  exports.app = new koa_1.default({ keys });
35
37
  exports.app.use(middlewares_1.someSecurity)
36
- .use(selfCheck_1.selfCheckMiddleware)
37
38
  .use(acme_1.acmeMiddleware)
38
39
  .use((0, koa_session_1.default)({ key: 'hfs_$id', signed: true, rolling: true, sameSite: 'lax' }, exports.app))
39
40
  .use(middlewares_1.prepareState)
41
+ .use(geo_1.geoFilter)
42
+ .use(selfCheck_1.selfCheckMiddleware)
40
43
  .use(middlewares_1.gzipper)
41
44
  .use(middlewares_1.paramsDecoder) // must be done before plugins, so they can manipulate params
42
45
  .use(plugins_1.pluginsMiddleware)
package/src/log.js CHANGED
@@ -35,7 +35,7 @@ const util = __importStar(require("util"));
35
35
  const promises_1 = require("fs/promises");
36
36
  const lodash_1 = __importDefault(require("lodash"));
37
37
  const util_files_1 = require("./util-files");
38
- const perm_1 = require("./perm");
38
+ const auth_1 = require("./auth");
39
39
  const misc_1 = require("./misc");
40
40
  const events_1 = __importDefault(require("./events"));
41
41
  class Logger {
@@ -122,7 +122,7 @@ const logMw = async (ctx, next) => {
122
122
  const format = '%s - %s [%s] "%s %s HTTP/%s" %d %s %s\n'; // Apache's Common Log Format
123
123
  const a = now.toString().split(' ');
124
124
  const date = a[2] + '/' + a[1] + '/' + a[3] + ':' + a[4] + ' ' + ((_b = a[5]) === null || _b === void 0 ? void 0 : _b.slice(3));
125
- const user = (0, perm_1.getCurrentUsername)(ctx);
125
+ const user = (0, auth_1.getCurrentUsername)(ctx);
126
126
  const length = (_c = ctx.state.length) !== null && _c !== void 0 ? _c : ctx.length;
127
127
  const uri = ctx.originalUrl;
128
128
  const extra = ctx.state.includesLastByte && ctx.vfsNode && ctx.res.finished && { dl: 1 }
@@ -19,8 +19,7 @@ const block_1 = require("./block");
19
19
  const perm_1 = require("./perm");
20
20
  const connections_1 = require("./connections");
21
21
  const basic_auth_1 = __importDefault(require("basic-auth"));
22
- const tssrp6a_1 = require("tssrp6a");
23
- const api_auth_1 = require("./api.auth");
22
+ const auth_1 = require("./auth");
24
23
  const path_1 = require("path");
25
24
  const promises_1 = require("stream/promises");
26
25
  const formidable_1 = __importDefault(require("formidable"));
@@ -202,36 +201,40 @@ function getProxyDetected() {
202
201
  }
203
202
  exports.getProxyDetected = getProxyDetected;
204
203
  const prepareState = async (ctx, next) => {
205
- var _a, _b;
206
- if (ctx.session)
204
+ var _a;
205
+ if (ctx.session) {
206
+ if (auth_1.invalidSessions.delete(ctx.session.username))
207
+ delete ctx.session.username;
207
208
  ctx.session.maxAge = exports.sessionDuration.compiled();
209
+ }
208
210
  // calculate these once and for all
209
- const a = ctx.state.account = (_a = await getHttpAccount(ctx)) !== null && _a !== void 0 ? _a : (0, perm_1.getAccount)((_b = ctx.session) === null || _b === void 0 ? void 0 : _b.username, false);
211
+ 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);
210
212
  if (a && !(0, perm_1.accountCanLogin)(a))
211
213
  ctx.state.account = undefined;
212
214
  const conn = ctx.state.connection = (0, connections_1.socket2connection)(ctx.socket);
213
215
  ctx.state.revProxyPath = ctx.get('x-forwarded-prefix');
216
+ ctx.state.browsing = undefined;
214
217
  if (conn)
215
218
  (0, connections_1.updateConnection)(conn, { ctx, op: undefined });
216
219
  await next();
220
+ async function urlLogin() {
221
+ const { login } = ctx.query;
222
+ if (!login)
223
+ return;
224
+ const [u, p] = (0, misc_1.splitAt)(':', String(login));
225
+ const a = await (0, auth_1.srpCheck)(u, p);
226
+ if (a) {
227
+ ctx.session.username = a.username;
228
+ ctx.redirect(ctx.originalUrl.slice(0, -ctx.querystring.length - 1));
229
+ }
230
+ return a;
231
+ }
232
+ async function getHttpAccount() {
233
+ const credentials = (0, basic_auth_1.default)(ctx.req);
234
+ return (0, auth_1.srpCheck)((credentials === null || credentials === void 0 ? void 0 : credentials.name) || '', (credentials === null || credentials === void 0 ? void 0 : credentials.pass) || '');
235
+ }
217
236
  };
218
237
  exports.prepareState = prepareState;
219
- async function getHttpAccount(ctx) {
220
- const credentials = (0, basic_auth_1.default)(ctx.req);
221
- const account = (0, perm_1.getAccount)((credentials === null || credentials === void 0 ? void 0 : credentials.name) || '');
222
- if (account && await srpCheck(account.username, credentials.pass))
223
- return account;
224
- }
225
- async function srpCheck(username, password) {
226
- const account = (0, perm_1.getAccount)(username);
227
- if (!(account === null || account === void 0 ? void 0 : account.srp) || !password)
228
- return false;
229
- const { step1, salt, pubKey } = await (0, api_auth_1.srpStep1)(account);
230
- const client = new tssrp6a_1.SRPClientSession(new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters()));
231
- const clientRes1 = await client.step1(username, password);
232
- const clientRes2 = await clientRes1.step2(BigInt(salt), BigInt(pubKey));
233
- return await step1.step2(clientRes2.A, clientRes2.M1).then(() => true, () => false);
234
- }
235
238
  const paramsDecoder = async (ctx, next) => {
236
239
  ctx.params = ctx.method === 'POST' && ctx.originalUrl.startsWith(const_1.API_URI)
237
240
  && ((0, misc_1.tryJson)(await (0, misc_1.stream2string)(ctx.req)) || {});
package/src/perm.js CHANGED
@@ -4,7 +4,7 @@ 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 = exports.getCurrentUsername = void 0;
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;
8
8
  const lodash_1 = __importDefault(require("lodash"));
9
9
  const crypt_1 = require("./crypt");
10
10
  const misc_1 = require("./misc");
@@ -12,11 +12,6 @@ const config_1 = require("./config");
12
12
  const tssrp6a_1 = require("tssrp6a");
13
13
  const events_1 = __importDefault(require("./events"));
14
14
  let accounts = {};
15
- function getCurrentUsername(ctx) {
16
- var _a;
17
- return ((_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.username) || '';
18
- }
19
- exports.getCurrentUsername = getCurrentUsername;
20
15
  // provides the username and all other usernames it inherits based on the 'belongs' attribute. Useful to check permissions
21
16
  function expandUsername(who) {
22
17
  const ret = [];
package/src/plugins.js CHANGED
@@ -35,7 +35,6 @@ const const_1 = require("./const");
35
35
  const Const = __importStar(require("./const"));
36
36
  const misc_1 = require("./misc");
37
37
  const config_1 = require("./config");
38
- const vfs_1 = require("./vfs");
39
38
  const serveFile_1 = require("./serveFile");
40
39
  const events_1 = __importDefault(require("./events"));
41
40
  const promises_1 = require("fs/promises");
@@ -120,7 +119,6 @@ exports.getPluginConfigFields = getPluginConfigFields;
120
119
  async function initPlugin(pl, more) {
121
120
  var _a;
122
121
  return Object.assign(pl, await ((_a = pl.init) === null || _a === void 0 ? void 0 : _a.call(pl, {
123
- const: Const,
124
122
  Const,
125
123
  require,
126
124
  getConnections: connections_1.getConnections,
@@ -154,7 +152,7 @@ const pluginsMiddleware = async (ctx, next) => {
154
152
  const a = path.substring(const_1.PLUGINS_PUB_URI.length).split('/');
155
153
  const name = a.shift();
156
154
  if (plugins.hasOwnProperty(name)) // do it only if the plugin is loaded
157
- await (0, serveFile_1.serveFile)(ctx, plugins[name].folder + '/public/' + a.join('/'), vfs_1.MIME_AUTO);
155
+ await (0, serveFile_1.serveFile)(ctx, plugins[name].folder + '/public/' + a.join('/'), const_1.MIME_AUTO);
158
156
  return;
159
157
  }
160
158
  await next();
package/src/serveFile.js CHANGED
@@ -38,13 +38,13 @@ function serveFileNode(ctx, node) {
38
38
  }
39
39
  }
40
40
  exports.serveFileNode = serveFileNode;
41
- const mimeCfg = (0, config_1.defineConfig)('mime', { '*': vfs_1.MIME_AUTO });
41
+ const mimeCfg = (0, config_1.defineConfig)('mime', { '*': const_1.MIME_AUTO });
42
42
  async function serveFile(ctx, source, mime, content) {
43
43
  if (!source)
44
44
  return;
45
45
  const fn = path_1.default.basename(source);
46
46
  mime = mime !== null && mime !== void 0 ? mime : lodash_1.default.find(mimeCfg.get(), (v, k) => (0, misc_1.matches)(fn, k));
47
- if (mime === vfs_1.MIME_AUTO)
47
+ if (mime === const_1.MIME_AUTO)
48
48
  mime = mime_types_1.default.lookup(source) || '';
49
49
  if (mime)
50
50
  ctx.type = mime;
@@ -42,8 +42,8 @@ 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
- const vfs_1 = require("./vfs");
46
45
  const logGui = (0, config_1.defineConfig)('log_gui', false);
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/' : '';
49
49
  function serveStatic(uri) {
@@ -70,7 +70,7 @@ function serveStatic(uri) {
70
70
  if (content === null)
71
71
  return ctx.status = const_1.HTTP_NOT_FOUND;
72
72
  if (!serveApp)
73
- return (0, serveFile_1.serveFile)(ctx, fullPath, vfs_1.MIME_AUTO, content);
73
+ return (0, serveFile_1.serveFile)(ctx, fullPath, const_1.MIME_AUTO, content);
74
74
  // we don't cache the index as it's small and may prevent plugins change to apply
75
75
  ctx.body = await treatIndex(ctx, uri, String(content));
76
76
  };
@@ -119,8 +119,7 @@ async function treatIndex(ctx, filesUri, body) {
119
119
  plugins,
120
120
  prefixUrl: ctx.state.revProxyPath,
121
121
  customHtml: lodash_1.default.omit(Object.fromEntries(customHtml_1.customHtmlState.sections), ['top', 'bottom']),
122
- fileMenuOnLink: fileMenuOnLink.get(),
123
- tilesSize: tilesSize.get(),
122
+ ...(0, misc_1.newObj)(misc_1.FRONTEND_OPTIONS, (v, k) => (0, config_1.getConfig)(k)),
124
123
  lang
125
124
  }, null, 4)
126
125
  .replace(/<(\/script)/g, '<"+"$1') /*avoid breaking our script container*/}
@@ -175,5 +174,3 @@ function serveGuiFiles(proxyPort, uri) {
175
174
  return serveProxied(proxyPort, uri) || serveStatic(uri);
176
175
  }
177
176
  exports.serveGuiFiles = serveGuiFiles;
178
- const fileMenuOnLink = (0, config_1.defineConfig)('file_menu_on_link', true);
179
- const tilesSize = (0, config_1.defineConfig)('tiles_size', 0);
package/src/srp.js ADDED
@@ -0,0 +1,22 @@
1
+ "use strict";
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
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.srpClientPart = exports.srpClientSequence = void 0;
5
+ const tssrp6a_1 = require("tssrp6a");
6
+ async function srpClientSequence(username, password, apiCall) {
7
+ const { pubKey, salt } = await apiCall('loginSrp1', { username });
8
+ if (!salt)
9
+ throw Error('salt');
10
+ const client = await srpClientPart(username, password, salt, pubKey);
11
+ const res = await apiCall('loginSrp2', { pubKey: String(client.A), proof: String(client.M1) }); // bigint-s must be cast to string to be json-ed
12
+ await client.step3(BigInt(res.proof)).catch(() => Promise.reject('trust'));
13
+ return res;
14
+ }
15
+ exports.srpClientSequence = srpClientSequence;
16
+ async function srpClientPart(username, password, salt, pubKey) {
17
+ const srp6aNimbusRoutines = new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters());
18
+ const srp = new tssrp6a_1.SRPClientSession(srp6aNimbusRoutines);
19
+ const res = await srp.step1(username, password);
20
+ return await res.step2(BigInt(salt), BigInt(pubKey));
21
+ }
22
+ exports.srpClientPart = srpClientPart;
package/src/upload.js CHANGED
@@ -14,7 +14,7 @@ const config_1 = require("./config");
14
14
  const util_os_1 = require("./util-os");
15
15
  const connections_1 = require("./connections");
16
16
  const throttler_1 = require("./throttler");
17
- const perm_1 = require("./perm");
17
+ const auth_1 = require("./auth");
18
18
  const comments_1 = require("./comments");
19
19
  exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)('delete_unfinished_uploads_after', 86400);
20
20
  exports.minAvailableMb = (0, config_1.defineConfig)('min_available_mb', 100);
@@ -27,7 +27,7 @@ function getUploadMeta(path) {
27
27
  exports.getUploadMeta = getUploadMeta;
28
28
  function setUploadMeta(path, ctx) {
29
29
  return (0, misc_1.storeFileAttr)(path, ATTR_UPLOADER, {
30
- username: (0, perm_1.getCurrentUsername)(ctx) || undefined,
30
+ username: (0, auth_1.getCurrentUsername)(ctx) || undefined,
31
31
  ip: ctx.ip,
32
32
  });
33
33
  }
package/src/vfs.js CHANGED
@@ -4,7 +4,7 @@ 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.parentMaskApplier = exports.masksCouldGivePermission = exports.walkNode = exports.statusCodeForMissingPerm = exports.hasPermission = exports.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.urlToNode = exports.applyParentToChild = exports.isSameFilenameAs = exports.permsFromParent = exports.MIME_AUTO = void 0;
7
+ exports.parentMaskApplier = exports.masksCouldGivePermission = exports.walkNode = exports.statusCodeForMissingPerm = exports.hasPermission = exports.nodeIsLink = exports.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.urlToNode = exports.applyParentToChild = exports.isSameFilenameAs = exports.permsFromParent = void 0;
8
8
  const promises_1 = __importDefault(require("fs/promises"));
9
9
  const path_1 = require("path");
10
10
  const misc_1 = require("./misc");
@@ -13,7 +13,7 @@ const config_1 = require("./config");
13
13
  const const_1 = require("./const");
14
14
  const events_1 = __importDefault(require("./events"));
15
15
  const perm_1 = require("./perm");
16
- exports.MIME_AUTO = 'auto';
16
+ const auth_1 = require("./auth");
17
17
  function permsFromParent(parent, child) {
18
18
  const ret = {};
19
19
  for (const k of misc_1.PERM_KEYS) {
@@ -100,7 +100,7 @@ async function urlToNode(url, ctx, parent = exports.vfs, getRest) {
100
100
  }
101
101
  ret.source = (0, misc_1.enforceFinal)('/', parent.source) + onDisk;
102
102
  if (parent.default)
103
- inheritFromParent({ mime: { '*': exports.MIME_AUTO } }, ret);
103
+ inheritFromParent({ mime: { '*': const_1.MIME_AUTO } }, ret);
104
104
  if (rest)
105
105
  return urlToNode(rest, ctx, ret, getRest);
106
106
  if (ret.source)
@@ -119,12 +119,6 @@ async function urlToNode(url, ctx, parent = exports.vfs, getRest) {
119
119
  exports.urlToNode = urlToNode;
120
120
  exports.vfs = {};
121
121
  (0, config_1.defineConfig)('vfs', {}).sub(data => exports.vfs = (function recur(node) {
122
- if (node.propagate) { // legacy pre-0.47
123
- for (const [k, v] of (0, misc_1.typedEntries)(node.propagate))
124
- if (v === false)
125
- node[k] = { this: node[k] };
126
- delete node.propagate;
127
- }
128
122
  if (node.children)
129
123
  for (const c of node.children)
130
124
  recur(c);
@@ -154,14 +148,15 @@ async function nodeIsDirectory(node) {
154
148
  var _a;
155
149
  if (node.isFolder !== undefined)
156
150
  return node.isFolder;
157
- const isFolder = Boolean(((_a = node.children) === null || _a === void 0 ? void 0 : _a.length) || !node.source || await (0, misc_1.isDirectory)(node.source));
158
- if (node.isTemp)
159
- node.isFolder = isFolder;
160
- else
161
- (0, misc_1.setHidden)(node, { isFolder }); // don't make it to the storage
151
+ const isFolder = Boolean(((_a = node.children) === null || _a === void 0 ? void 0 : _a.length) || !nodeIsLink(node) && (!node.source || await (0, misc_1.isDirectory)(node.source)));
152
+ (0, misc_1.setHidden)(node, { isFolder }); // don't make it to the storage (a node.isTemp doesn't need it to be hidden)
162
153
  return isFolder;
163
154
  }
164
155
  exports.nodeIsDirectory = nodeIsDirectory;
156
+ function nodeIsLink(node) {
157
+ return node.url;
158
+ }
159
+ exports.nodeIsLink = nodeIsLink;
165
160
  function hasPermission(node, perm, ctx) {
166
161
  return !statusCodeForMissingPerm(node, perm, ctx, false);
167
162
  }
@@ -189,12 +184,12 @@ function statusCodeForMissingPerm(node, perm, ctx, assign = true) {
189
184
  if (Array.isArray(who)) {
190
185
  const arr = who; // shut up ts
191
186
  // check if I or any ancestor match `who`, but cache ancestors' usernames inside context state
192
- const some = (0, misc_1.getOrSet)(ctx.state, 'usernames', () => (0, perm_1.expandUsername)((0, perm_1.getCurrentUsername)(ctx)))
187
+ const some = (0, misc_1.getOrSet)(ctx.state, 'usernames', () => (0, perm_1.expandUsername)((0, auth_1.getCurrentUsername)(ctx)))
193
188
  .some((u) => arr.includes(u));
194
189
  return some ? 0 : const_1.HTTP_UNAUTHORIZED;
195
190
  }
196
191
  return typeof who === 'boolean' ? (who ? 0 : const_1.HTTP_FORBIDDEN)
197
- : who === misc_1.WHO_ANY_ACCOUNT ? (ctx.state.account ? 0 : const_1.HTTP_UNAUTHORIZED)
192
+ : who === misc_1.WHO_ANY_ACCOUNT ? ((0, auth_1.getCurrentUsername)(ctx) ? 0 : const_1.HTTP_UNAUTHORIZED)
198
193
  : (0, misc_1.throw_)(Error('invalid permission: ' + JSON.stringify(who)));
199
194
  }
200
195
  }
package/src/zip.js CHANGED
@@ -43,6 +43,8 @@ async function zipStreamFromFolder(node, ctx) {
43
43
  }
44
44
  })();
45
45
  const mappedWalker = (0, misc_1.filterMapGenerator)(walker, async (el) => {
46
+ if ((0, vfs_1.nodeIsLink)(el))
47
+ return;
46
48
  if (!(0, vfs_1.hasPermission)(el, 'can_archive', ctx))
47
49
  return; // the fact you see it doesn't mean you can get it
48
50
  const { source } = el;