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.
- package/README.md +1 -0
- package/admin/assets/{index-79c48481.js → index-c618ba7d.js} +104 -103
- package/admin/assets/{index-1c2b890b.css → index-f3fd8783.css} +1 -1
- package/{frontend/assets/sha512-da48f0d4.js → admin/assets/sha512-1ee27408.js} +1 -1
- package/admin/index.html +2 -2
- package/frontend/assets/index-83cd0389.js +94 -0
- package/{admin/assets/sha512-bdc531ce.js → frontend/assets/sha512-90bb0108.js} +1 -1
- package/frontend/index.html +1 -1
- package/package.json +1 -1
- package/plugins/antibrute/plugin.js +1 -1
- package/plugins/vhosting/plugin.js +3 -3
- package/src/adminApis.js +11 -12
- package/src/api.accounts.js +7 -1
- package/src/api.auth.js +7 -31
- package/src/api.file_list.js +3 -1
- package/src/api.monitor.js +2 -2
- package/src/api.vfs.js +6 -8
- package/src/auth.js +49 -0
- package/src/const.js +1 -1
- package/src/cross.js +21 -3
- package/src/log.js +2 -2
- package/src/middlewares.js +23 -21
- package/src/perm.js +1 -6
- package/src/plugins.js +0 -1
- package/src/serveGuiFiles.js +2 -4
- package/src/srp.js +22 -0
- package/src/upload.js +2 -2
- package/src/vfs.js +10 -14
- package/src/zip.js +2 -0
- package/frontend/assets/index-5f1c6cb8.js +0 -94
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{g as OF,c as UF}from"./index-
|
|
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
|
package/frontend/index.html
CHANGED
|
@@ -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-
|
|
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
|
@@ -13,7 +13,7 @@ exports.configDialog = {
|
|
|
13
13
|
const byIp = {}
|
|
14
14
|
|
|
15
15
|
exports.init = api => {
|
|
16
|
-
const LOGIN_URI = api.
|
|
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.
|
|
30
|
-
if (!ctx.path.startsWith(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.
|
|
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
|
|
65
|
+
async set_config({ values }) {
|
|
66
66
|
var _a;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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,
|
package/src/api.accounts.js
CHANGED
|
@@ -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,
|
|
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.
|
|
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,
|
|
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(),
|
package/src/api.file_list.js
CHANGED
|
@@ -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))
|
package/src/api.monitor.js
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
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:
|
|
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.
|
|
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.
|
|
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
|
|
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,
|
|
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 }
|
package/src/middlewares.js
CHANGED
|
@@ -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
|
|
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
|
|
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 = (
|
|
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 =
|
|
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,
|
package/src/serveGuiFiles.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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,
|
|
30
|
+
username: (0, auth_1.getCurrentUsername)(ctx) || undefined,
|
|
31
31
|
ip: ctx.ip,
|
|
32
32
|
});
|
|
33
33
|
}
|