hfs 0.49.2 → 0.50.0-alpha1

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.
@@ -1,4 +1,4 @@
1
- import{g as OF,c as UF}from"./index-79c48481.js";function gF(sF,hF){for(var eF=0;eF<hF.length;eF++){const tF=hF[eF];if(typeof tF!="string"&&!Array.isArray(tF)){for(const w in tF)if(w!=="default"&&!(w in sF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(sF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(sF,Symbol.toStringTag,{value:"Module"}))}var dF={exports:{}};/*
1
+ import{g as OF,c as UF}from"./index-83cd0389.js";function gF(sF,hF){for(var eF=0;eF<hF.length;eF++){const tF=hF[eF];if(typeof tF!="string"&&!Array.isArray(tF)){for(const w in tF)if(w!=="default"&&!(w in sF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(sF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(sF,Symbol.toStringTag,{value:"Module"}))}var dF={exports:{}};/*
2
2
  * [js-sha512]{@link https://github.com/emn178/js-sha512}
3
3
  *
4
4
  * @version 0.8.0
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0" />
6
6
  <link href="/fontello.css" rel="stylesheet" />
7
7
 
8
- <script type="module" crossorigin src="/assets/index-5f1c6cb8.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-83cd0389.js"></script>
9
9
  <link rel="stylesheet" href="/assets/index-b50efd06.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hfs",
3
- "version": "0.49.2",
3
+ "version": "0.50.0-alpha1",
4
4
  "description": "HTTP File Server",
5
5
  "keywords": [
6
6
  "file server",
@@ -13,7 +13,7 @@ exports.configDialog = {
13
13
  const byIp = {}
14
14
 
15
15
  exports.init = api => {
16
- const LOGIN_URI = api.const.API_URI + 'loginSrp1'
16
+ const LOGIN_URI = api.Const.API_URI + 'loginSrp1'
17
17
  const { getOrSet } = api.require('./misc')
18
18
  return {
19
19
  async middleware(ctx) {
@@ -26,11 +26,11 @@ exports.init = api => {
26
26
  return {
27
27
  middleware(ctx) {
28
28
  let params // undefined if we are not going to work on api parameters
29
- if (ctx.path.startsWith(api.const.SPECIAL_URI)) { // special uris should be excluded...
30
- if (!ctx.path.startsWith(api.const.API_URI)) return // ...unless it's an api
29
+ if (ctx.path.startsWith(api.Const.SPECIAL_URI)) { // special uris should be excluded...
30
+ if (!ctx.path.startsWith(api.Const.API_URI)) return // ...unless it's an api
31
31
  let { referer } = ctx.headers
32
32
  referer &&= new URL(referer).pathname
33
- if (referer?.startsWith(ctx.state.revProxyPath + api.const.ADMIN_URI)) return // exclude apis for admin-panel
33
+ if (referer?.startsWith(ctx.state.revProxyPath + api.Const.ADMIN_URI)) return // exclude apis for admin-panel
34
34
  params = ctx.params || ctx.query // for api we'll translate params
35
35
  }
36
36
 
package/src/adminApis.js CHANGED
@@ -62,19 +62,18 @@ exports.adminApis = {
62
62
  ...api_monitor_1.default,
63
63
  ...api_lang_1.default,
64
64
  ...api_net_1.default,
65
- async set_config({ values: v }) {
65
+ async set_config({ values }) {
66
66
  var _a;
67
- if (v) {
68
- await (0, config_1.setConfig)(v);
69
- if (v.port === 0 || v.https_port === 0)
70
- return (_a = await (0, misc_1.waitFor)(async () => {
71
- const st = await (0, listen_1.getServerStatus)();
72
- // wait for all random ports to be done, so we communicate new numbers
73
- if ((v.port !== 0 || st.http.listening)
74
- && (v.https_port !== 0 || st.https.listening))
75
- return st;
76
- }, { timeout: 1000 })) !== null && _a !== void 0 ? _a : new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, "something went wrong changing ports");
77
- }
67
+ (0, misc_1.apiAssertTypes)({ object: { values } });
68
+ (0, config_1.setConfig)(values);
69
+ if (values.port === 0 || values.https_port === 0)
70
+ return (_a = await (0, misc_1.waitFor)(async () => {
71
+ const st = await (0, listen_1.getServerStatus)();
72
+ // wait for all random ports to be done, so we communicate new numbers
73
+ if ((values.port !== 0 || st.http.listening)
74
+ && (values.https_port !== 0 || st.https.listening))
75
+ return st;
76
+ }, { timeout: 1000 })) !== null && _a !== void 0 ? _a : new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, "something went wrong changing ports");
78
77
  return {};
79
78
  },
80
79
  get_config: config_1.getWholeConfig,
@@ -9,12 +9,14 @@ const apiMiddleware_1 = require("./apiMiddleware");
9
9
  const perm_1 = require("./perm");
10
10
  const lodash_1 = __importDefault(require("lodash"));
11
11
  const const_1 = require("./const");
12
+ const auth_1 = require("./auth");
12
13
  function prepareAccount(ac) {
13
14
  return ac && {
14
15
  ...lodash_1.default.omit(ac, ['password', 'hashed_password', 'srp']),
15
16
  username: ac.username,
16
17
  hasPassword: (0, perm_1.accountHasPassword)(ac),
17
18
  adminActualAccess: (0, perm_1.accountCanLoginAdmin)(ac),
19
+ invalidated: auth_1.invalidSessions.has(ac.username),
18
20
  };
19
21
  }
20
22
  const apis = {
@@ -22,7 +24,7 @@ const apis = {
22
24
  return { list: Object.keys(perm_1.accountsConfig.get()) };
23
25
  },
24
26
  get_account({ username }, ctx) {
25
- return prepareAccount((0, perm_1.getAccount)(username || (0, perm_1.getCurrentUsername)(ctx)))
27
+ return prepareAccount((0, perm_1.getAccount)(username || (0, auth_1.getCurrentUsername)(ctx)))
26
28
  || new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
27
29
  },
28
30
  get_accounts() {
@@ -51,6 +53,10 @@ const apis = {
51
53
  del_account({ username }) {
52
54
  return (0, perm_1.delAccount)(username) ? {} : new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
53
55
  },
56
+ invalidate_sessions({ username }) {
57
+ auth_1.invalidSessions.add(username);
58
+ return {};
59
+ },
54
60
  async change_password_others({ username, newPassword }) {
55
61
  const a = (0, perm_1.getAccount)(username);
56
62
  return a ? (0, api_helpers_1.changePasswordHelper)(a, newPassword)
package/src/api.auth.js CHANGED
@@ -1,31 +1,18 @@
1
1
  "use strict";
2
2
  // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.change_srp = exports.change_password = exports.refresh_session = exports.logout = exports.loginSrp2 = exports.srpStep1 = exports.loginSrp1 = exports.login = void 0;
4
+ exports.change_srp = exports.change_password = exports.refresh_session = exports.logout = exports.loginSrp2 = exports.loginSrp1 = exports.login = void 0;
5
5
  const perm_1 = require("./perm");
6
6
  const crypt_1 = require("./crypt");
7
7
  const apiMiddleware_1 = require("./apiMiddleware");
8
- const tssrp6a_1 = require("tssrp6a");
9
8
  const const_1 = require("./const");
10
9
  const api_helpers_1 = require("./api.helpers");
11
10
  const adminApis_1 = require("./adminApis");
12
11
  const middlewares_1 = require("./middlewares");
12
+ const auth_1 = require("./auth");
13
13
  const config_1 = require("./config");
14
- const srp6aNimbusRoutines = new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters());
15
14
  const ongoingLogins = {}; // store data that doesn't fit session object
16
15
  const keepSessionAlive = (0, config_1.defineConfig)('keep_session_alive', true);
17
- // centralized log-in state
18
- async function loggedIn(ctx, username) {
19
- const s = ctx.session;
20
- if (!s)
21
- return ctx.throw(const_1.HTTP_SERVER_ERROR, 'session');
22
- if (username === false) {
23
- delete s.username;
24
- return;
25
- }
26
- s.username = (0, perm_1.normalizeUsername)(username);
27
- await (0, middlewares_1.prepareState)(ctx, async () => { }); // updating the state is necessary to send complete session data so that frontend shows admin button
28
- }
29
16
  function makeExp() {
30
17
  return !keepSessionAlive.get() ? undefined
31
18
  : { exp: new Date(Date.now() + middlewares_1.sessionDuration.compiled()) };
@@ -42,7 +29,7 @@ const login = async ({ username, password }, ctx) => {
42
29
  return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
43
30
  if (!ctx.session)
44
31
  return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
45
- await loggedIn(ctx, username);
32
+ await (0, auth_1.loggedIn)(ctx, username);
46
33
  return { ...makeExp(), redirect: account.redirect };
47
34
  };
48
35
  exports.login = login;
@@ -55,7 +42,7 @@ const loginSrp1 = async ({ username }, ctx) => {
55
42
  if (!account || !(0, perm_1.accountCanLogin)(account)) // TODO simulate fake account to prevent knowing valid usernames
56
43
  return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
57
44
  try {
58
- const { step1, ...rest } = await srpStep1(account);
45
+ const { step1, ...rest } = await (0, auth_1.srpStep1)(account);
59
46
  const sid = Math.random();
60
47
  ongoingLogins[sid] = step1;
61
48
  setTimeout(() => delete ongoingLogins[sid], 60000);
@@ -67,17 +54,6 @@ const loginSrp1 = async ({ username }, ctx) => {
67
54
  }
68
55
  };
69
56
  exports.loginSrp1 = loginSrp1;
70
- async function srpStep1(account) {
71
- if (!account.srp)
72
- throw const_1.HTTP_NOT_ACCEPTABLE;
73
- const [salt, verifier] = account.srp.split('|');
74
- if (!salt || !verifier)
75
- throw Error("malformed account");
76
- const srpSession = new tssrp6a_1.SRPServerSession(srp6aNimbusRoutines);
77
- const step1 = await srpSession.step1(account.username, BigInt(salt), BigInt(verifier));
78
- return { step1, salt, pubKey: String(step1.B) }; // cast to string cause bigint can't be jsonized
79
- }
80
- exports.srpStep1 = srpStep1;
81
57
  const loginSrp2 = async ({ pubKey, proof }, ctx) => {
82
58
  var _a;
83
59
  if (!ctx.session)
@@ -90,7 +66,7 @@ const loginSrp2 = async ({ pubKey, proof }, ctx) => {
90
66
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
91
67
  try {
92
68
  const M2 = await step1.step2(BigInt(pubKey), BigInt(proof));
93
- await loggedIn(ctx, username);
69
+ await (0, auth_1.loggedIn)(ctx, username);
94
70
  delete ctx.session.loggingIn;
95
71
  return {
96
72
  proof: String(M2),
@@ -109,14 +85,14 @@ exports.loginSrp2 = loginSrp2;
109
85
  const logout = async ({}, ctx) => {
110
86
  if (!ctx.session)
111
87
  return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
112
- await loggedIn(ctx, false);
88
+ await (0, auth_1.loggedIn)(ctx, false);
113
89
  // 401 is a convenient code for OK: the browser clears a possible http authentication (hopefully), and Admin automatically triggers login dialog
114
90
  return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
115
91
  };
116
92
  exports.logout = logout;
117
93
  const refresh_session = async ({}, ctx) => {
118
94
  return !ctx.session ? new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR) : {
119
- username: (0, perm_1.getCurrentUsername)(ctx),
95
+ username: (0, auth_1.getCurrentUsername)(ctx),
120
96
  adminUrl: (0, adminApis_1.ctxAdminAccess)(ctx) ? ctx.state.revProxyPath + const_1.ADMIN_URI : undefined,
121
97
  canChangePassword: canChangePassword(ctx.state.account),
122
98
  ...makeExp(),
@@ -88,8 +88,10 @@ const get_file_list = async ({ uri, offset, limit, search, c }, ctx) => {
88
88
  return node.default && await (0, vfs_1.urlToNode)(node.default, ctx, node);
89
89
  }
90
90
  async function nodeToDirEntry(ctx, node) {
91
- let { source } = node;
91
+ let { source, url } = node;
92
92
  const name = (0, vfs_1.getNodeName)(node);
93
+ if (url)
94
+ return name ? { n: name, url } : null;
93
95
  if (!source)
94
96
  return name ? { n: name + '/' } : null;
95
97
  if (node.isFolder && await hasDefaultFile(node))
@@ -9,7 +9,7 @@ const connections_1 = require("./connections");
9
9
  const misc_1 = require("./misc");
10
10
  const apiMiddleware_1 = require("./apiMiddleware");
11
11
  const throttler_1 = require("./throttler");
12
- const perm_1 = require("./perm");
12
+ const auth_1 = require("./auth");
13
13
  const apis = {
14
14
  async disconnect({ ip, port, wait }) {
15
15
  var _a, _b;
@@ -83,7 +83,7 @@ const apis = {
83
83
  }
84
84
  function fromCtx(ctx) {
85
85
  return ctx && {
86
- user: (0, perm_1.getCurrentUsername)(ctx),
86
+ user: (0, auth_1.getCurrentUsername)(ctx),
87
87
  agent: getBrowser(ctx.get('user-agent')),
88
88
  archive: ctx.state.archive,
89
89
  upload: ctx.state.uploadProgress,
package/src/api.vfs.js CHANGED
@@ -21,6 +21,7 @@ async function urlToNodeOriginal(uri) {
21
21
  const n = await (0, vfs_1.urlToNode)(uri);
22
22
  return (n === null || n === void 0 ? void 0 : n.isTemp) ? n.original : n;
23
23
  }
24
+ const ALLOWED_KEYS = ['name', 'source', 'masks', 'default', 'accept', 'rename', 'mime', 'url', ...misc_1.PERM_KEYS];
24
25
  const apis = {
25
26
  async get_vfs() {
26
27
  return { root: await recur() };
@@ -28,7 +29,7 @@ const apis = {
28
29
  var _a;
29
30
  const { source } = node;
30
31
  const stats = Boolean(source) && await (0, promises_1.stat)(source).catch(() => false);
31
- const isDir = !source || stats && stats.isDirectory();
32
+ const isDir = !(0, vfs_1.nodeIsLink)(node) && (!source || stats && stats.isDirectory());
32
33
  const copyStats = stats ? lodash_1.default.pick(stats, ['size', 'ctime', 'mtime'])
33
34
  : { size: source ? -1 : undefined };
34
35
  if (copyStats.mtime && Number(copyStats.mtime) === Number(copyStats.ctime))
@@ -81,7 +82,7 @@ const apis = {
81
82
  const n = await urlToNodeOriginal(uri);
82
83
  if (!n)
83
84
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'path not found');
84
- props = pickProps(props, ['name', 'source', 'masks', 'default', 'accept', ...misc_1.PERM_KEYS]); // sanitize
85
+ props = pickProps(props, ALLOWED_KEYS); // sanitize
85
86
  if (props.name && props.name !== (0, vfs_1.getNodeName)(n)) {
86
87
  const parent = await urlToNodeOriginal((0, path_1.dirname)(uri));
87
88
  if ((_a = parent === null || parent === void 0 ? void 0 : parent.children) === null || _a === void 0 ? void 0 : _a.find(x => (0, vfs_1.getNodeName)(x) === props.name))
@@ -94,7 +95,7 @@ const apis = {
94
95
  (0, vfs_1.saveVfs)();
95
96
  return n;
96
97
  },
97
- async add_vfs({ parent, source, name }) {
98
+ async add_vfs({ parent, source, name, ...rest }) {
98
99
  var _a;
99
100
  if (!source && !name)
100
101
  return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'name or source required');
@@ -108,7 +109,7 @@ const apis = {
108
109
  const isDir = source && await (0, misc_1.isDirectory)(source);
109
110
  if (source && isDir === undefined)
110
111
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'source not found');
111
- const child = { source, name };
112
+ const child = { source, name, ...pickProps(rest, ALLOWED_KEYS) };
112
113
  name = (0, vfs_1.getNodeName)(child); // could be not given as input
113
114
  const ext = (0, path_1.extname)(name);
114
115
  const noExt = ext ? name.slice(0, -ext.length) : name;
@@ -119,7 +120,7 @@ const apis = {
119
120
  simplifyName(child);
120
121
  (parentNode.children || (parentNode.children = [])).unshift(child);
121
122
  (0, vfs_1.saveVfs)();
122
- const link = (0, listen_1.getBaseUrlOrDefault)()
123
+ const link = rest.url ? undefined : (0, listen_1.getBaseUrlOrDefault)()
123
124
  + (parent ? (0, misc_1.enforceFinal)('/', parent) : '/')
124
125
  + encodeURIComponent((0, vfs_1.getNodeName)(child))
125
126
  + (isDir ? '/' : '');
@@ -239,9 +240,6 @@ function simplifyName(node) {
239
240
  delete node.name;
240
241
  }
241
242
  const WINDOWS_REG_KEY = 'HKCU\\Software\\Classes\\*\\shell\\AddToHFS3';
242
- if (const_1.IS_WINDOWS) // legacy 0.49.0-beta7 2023-10-27. Remove in 0.50
243
- for (const k of ['*', 'Directory'])
244
- reg('delete', `HKCR\\${k}\\shell\\AddToHFS3`, '/f').catch(() => { });
245
243
  async function windowsIntegration() {
246
244
  const status = await (0, listen_1.getServerStatus)();
247
245
  const url = 'http://localhost:' + status.http.port;
package/src/auth.js ADDED
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.invalidSessions = exports.loggedIn = exports.getCurrentUsername = exports.srpCheck = exports.srpStep1 = void 0;
4
+ const perm_1 = require("./perm");
5
+ const cross_const_1 = require("./cross-const");
6
+ const tssrp6a_1 = require("tssrp6a");
7
+ const middlewares_1 = require("./middlewares");
8
+ const srp_1 = require("./srp");
9
+ const srp6aNimbusRoutines = new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters());
10
+ async function srpStep1(account) {
11
+ if (!account.srp)
12
+ throw cross_const_1.HTTP_NOT_ACCEPTABLE;
13
+ const [salt, verifier] = account.srp.split('|');
14
+ if (!salt || !verifier)
15
+ throw Error("malformed account");
16
+ const srpSession = new tssrp6a_1.SRPServerSession(srp6aNimbusRoutines);
17
+ const step1 = await srpSession.step1(account.username, BigInt(salt), BigInt(verifier));
18
+ return { step1, salt, pubKey: String(step1.B) }; // cast to string cause bigint can't be jsonized
19
+ }
20
+ exports.srpStep1 = srpStep1;
21
+ async function srpCheck(username, password) {
22
+ const account = (0, perm_1.getAccount)(username);
23
+ if (!(account === null || account === void 0 ? void 0 : account.srp) || !password)
24
+ return;
25
+ const { step1, salt, pubKey } = await srpStep1(account);
26
+ const client = await (0, srp_1.srpClientPart)(username, password, salt, pubKey);
27
+ return await step1.step2(client.A, client.M1).then(() => account, () => { });
28
+ }
29
+ exports.srpCheck = srpCheck;
30
+ function getCurrentUsername(ctx) {
31
+ var _a;
32
+ return ((_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.username) || '';
33
+ }
34
+ exports.getCurrentUsername = getCurrentUsername;
35
+ // centralized log-in state
36
+ async function loggedIn(ctx, username) {
37
+ const s = ctx.session;
38
+ if (!s)
39
+ return ctx.throw(cross_const_1.HTTP_SERVER_ERROR, 'session');
40
+ if (username === false) {
41
+ delete s.username;
42
+ return;
43
+ }
44
+ exports.invalidSessions.delete(username);
45
+ s.username = (0, perm_1.normalizeUsername)(username);
46
+ await (0, middlewares_1.prepareState)(ctx, async () => { }); // updating the state is necessary to send complete session data so that frontend shows admin button
47
+ }
48
+ exports.loggedIn = loggedIn;
49
+ exports.invalidSessions = new Set(); // since session are currently stored in cookies, we need to memorize this until we meet again
package/src/const.js CHANGED
@@ -42,7 +42,7 @@ exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
42
42
  exports.ORIGINAL_CWD = process.cwd();
43
43
  exports.HFS_STARTED = new Date();
44
44
  const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
45
- exports.BUILD_TIMESTAMP = "2023-11-06T09:23:00.007Z";
45
+ exports.BUILD_TIMESTAMP = "2023-11-06T09:52:20.484Z";
46
46
  const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
47
47
  exports.VERSION = pkg.version;
48
48
  exports.RUNNING_BETA = exports.VERSION.includes('-');
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.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.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.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 = 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,18 @@ 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' };
32
43
  exports.WHO_ANYONE = true;
33
44
  exports.WHO_NO_ONE = false;
34
45
  exports.WHO_ANY_ACCOUNT = '*';
@@ -76,6 +87,13 @@ function enforceFinal(sub, s, evenEmpty = false) {
76
87
  return !evenEmpty && !s || s.endsWith(sub) ? s : s + sub;
77
88
  }
78
89
  exports.enforceFinal = enforceFinal;
90
+ function splitAt(sub, all) {
91
+ if (typeof sub === 'number')
92
+ return [all.slice(0, sub), all.slice(sub + 1)];
93
+ const i = all.indexOf(sub);
94
+ return i < 0 ? [all, ''] : [all.slice(0, i), all.slice(i + sub.length)];
95
+ }
96
+ exports.splitAt = splitAt;
79
97
  function truthy(value) {
80
98
  return Boolean(value);
81
99
  }
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,11 +201,14 @@ 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);
@@ -214,24 +216,24 @@ const prepareState = async (ctx, next) => {
214
216
  if (conn)
215
217
  (0, connections_1.updateConnection)(conn, { ctx, op: undefined });
216
218
  await next();
219
+ async function urlLogin() {
220
+ const { login } = ctx.query;
221
+ if (!login)
222
+ return;
223
+ const [u, p] = (0, misc_1.splitAt)(':', String(login));
224
+ const a = await (0, auth_1.srpCheck)(u, p);
225
+ if (a) {
226
+ ctx.session.username = a.username;
227
+ ctx.redirect(ctx.originalUrl.slice(0, -ctx.querystring.length - 1));
228
+ }
229
+ return a;
230
+ }
231
+ async function getHttpAccount() {
232
+ const credentials = (0, basic_auth_1.default)(ctx.req);
233
+ return (0, auth_1.srpCheck)((credentials === null || credentials === void 0 ? void 0 : credentials.name) || '', (credentials === null || credentials === void 0 ? void 0 : credentials.pass) || '');
234
+ }
217
235
  };
218
236
  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
237
  const paramsDecoder = async (ctx, next) => {
236
238
  ctx.params = ctx.method === 'POST' && ctx.originalUrl.startsWith(const_1.API_URI)
237
239
  && ((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
@@ -120,7 +120,6 @@ exports.getPluginConfigFields = getPluginConfigFields;
120
120
  async function initPlugin(pl, more) {
121
121
  var _a;
122
122
  return Object.assign(pl, await ((_a = pl.init) === null || _a === void 0 ? void 0 : _a.call(pl, {
123
- const: Const,
124
123
  Const,
125
124
  require,
126
125
  getConnections: connections_1.getConnections,
@@ -44,6 +44,7 @@ const config_1 = require("./config");
44
44
  const lang_1 = require("./lang");
45
45
  const vfs_1 = require("./vfs");
46
46
  const logGui = (0, config_1.defineConfig)('log_gui', false);
47
+ lodash_1.default.each(misc_1.FRONTEND_OPTIONS, (v, k) => (0, config_1.defineConfig)(k, v)); // define default values
47
48
  // in case of dev env we have our static files within the 'dist' folder'
48
49
  const DEV_STATIC = process.env.DEV ? 'dist/' : '';
49
50
  function serveStatic(uri) {
@@ -119,8 +120,7 @@ async function treatIndex(ctx, filesUri, body) {
119
120
  plugins,
120
121
  prefixUrl: ctx.state.revProxyPath,
121
122
  customHtml: lodash_1.default.omit(Object.fromEntries(customHtml_1.customHtmlState.sections), ['top', 'bottom']),
122
- fileMenuOnLink: fileMenuOnLink.get(),
123
- tilesSize: tilesSize.get(),
123
+ ...(0, misc_1.newObj)(misc_1.FRONTEND_OPTIONS, (v, k) => (0, config_1.getConfig)(k)),
124
124
  lang
125
125
  }, null, 4)
126
126
  .replace(/<(\/script)/g, '<"+"$1') /*avoid breaking our script container*/}
@@ -175,5 +175,3 @@ function serveGuiFiles(proxyPort, uri) {
175
175
  return serveProxied(proxyPort, uri) || serveStatic(uri);
176
176
  }
177
177
  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
  }