hfs 0.26.8 → 0.27.2
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 +15 -2
- package/admin/assets/index-509bb1d6.js +415 -0
- package/admin/assets/index-60a380a7.css +1 -0
- package/admin/assets/sha512-738f0943.js +8 -0
- package/admin/index.html +3 -1
- package/admin/{public/logo.svg → logo.svg} +0 -0
- package/frontend/assets/index-6e178dfd.css +1 -0
- package/frontend/assets/index-aea7654e.js +85 -0
- package/frontend/assets/sha512-bf915587.js +8 -0
- package/frontend/{public/fontello.css → fontello.css} +0 -0
- package/frontend/{public/fontello.woff2 → fontello.woff2} +0 -0
- package/frontend/index.html +4 -2
- package/package.json +2 -6
- package/plugins/vhosting/plugin.js +23 -20
- package/src/QuickZipStream.js +285 -0
- package/src/ThrottledStream.js +93 -0
- package/src/adminApis.js +169 -0
- package/src/api.accounts.js +59 -0
- package/src/api.auth.js +128 -0
- package/src/api.file_list.js +110 -0
- package/src/api.helpers.js +32 -0
- package/src/api.monitor.js +104 -0
- package/src/api.plugins.js +128 -0
- package/src/api.vfs.js +167 -0
- package/src/apiMiddleware.js +123 -0
- package/src/block.js +34 -0
- package/src/commands.js +125 -0
- package/src/config.js +168 -0
- package/src/connections.js +57 -0
- package/src/const.js +94 -0
- package/src/crypt.js +21 -0
- package/src/debounceAsync.js +49 -0
- package/src/events.js +9 -0
- package/src/frontEndApis.js +38 -0
- package/src/github.js +104 -0
- package/src/index.js +57 -0
- package/src/listen.js +235 -0
- package/src/log.js +137 -0
- package/src/middlewares.js +195 -0
- package/src/misc.js +160 -0
- package/src/pbkdf2.js +74 -0
- package/src/perm.js +183 -0
- package/src/plugins.js +343 -0
- package/src/serveFile.js +105 -0
- package/src/serveGuiFiles.js +113 -0
- package/src/sse.js +30 -0
- package/src/throttler.js +91 -0
- package/src/update.js +70 -0
- package/src/util-files.js +163 -0
- package/src/util-generators.js +31 -0
- package/src/util-http.js +32 -0
- package/src/vfs.js +232 -0
- package/src/watchLoad.js +73 -0
- package/src/zip.js +73 -0
- package/admin/.DS_Store +0 -0
- package/admin/.eslintrc +0 -8
- package/admin/.gitignore +0 -23
- package/admin/package.json +0 -67
- package/admin/src/AccountForm.ts +0 -92
- package/admin/src/AccountsPage.ts +0 -143
- package/admin/src/App.ts +0 -83
- package/admin/src/ArrayField.ts +0 -84
- package/admin/src/ConfigPage.ts +0 -279
- package/admin/src/FileField.ts +0 -52
- package/admin/src/FileForm.ts +0 -148
- package/admin/src/FilePicker.ts +0 -166
- package/admin/src/HomePage.ts +0 -96
- package/admin/src/InstalledPlugins.ts +0 -158
- package/admin/src/LoginRequired.ts +0 -75
- package/admin/src/LogoutPage.ts +0 -27
- package/admin/src/LogsPage.ts +0 -75
- package/admin/src/MainMenu.ts +0 -74
- package/admin/src/MenuButton.ts +0 -38
- package/admin/src/MonitorPage.ts +0 -200
- package/admin/src/OnlinePlugins.ts +0 -101
- package/admin/src/PermField.ts +0 -80
- package/admin/src/PluginsPage.ts +0 -27
- package/admin/src/VfsMenuBar.ts +0 -58
- package/admin/src/VfsPage.ts +0 -124
- package/admin/src/VfsTree.ts +0 -95
- package/admin/src/addFiles.ts +0 -59
- package/admin/src/api.ts +0 -246
- package/admin/src/dialog.ts +0 -203
- package/admin/src/index.css +0 -21
- package/admin/src/index.ts +0 -10
- package/admin/src/md.ts +0 -31
- package/admin/src/misc.ts +0 -141
- package/admin/src/react-app-env.d.ts +0 -1
- package/admin/src/reportWebVitals.ts +0 -15
- package/admin/src/setupTests.ts +0 -5
- package/admin/src/state.ts +0 -40
- package/admin/src/theme.ts +0 -37
- package/admin/tsconfig.json +0 -26
- package/admin/vite.config.ts +0 -32
- package/frontend/.DS_Store +0 -0
- package/frontend/.eslintrc +0 -8
- package/frontend/.gitignore +0 -23
- package/frontend/package.json +0 -51
- package/frontend/src/App.ts +0 -25
- package/frontend/src/Breadcrumbs.ts +0 -43
- package/frontend/src/BrowseFiles.ts +0 -141
- package/frontend/src/Head.ts +0 -45
- package/frontend/src/UserPanel.ts +0 -52
- package/frontend/src/api.ts +0 -78
- package/frontend/src/components.ts +0 -54
- package/frontend/src/dialog.css +0 -76
- package/frontend/src/dialog.ts +0 -105
- package/frontend/src/icons.ts +0 -46
- package/frontend/src/index.scss +0 -307
- package/frontend/src/index.ts +0 -10
- package/frontend/src/login.ts +0 -50
- package/frontend/src/menu.ts +0 -188
- package/frontend/src/misc.ts +0 -54
- package/frontend/src/options.ts +0 -52
- package/frontend/src/react-app-env.d.ts +0 -1
- package/frontend/src/reportWebVitals.ts +0 -15
- package/frontend/src/setupTests.ts +0 -5
- package/frontend/src/state.ts +0 -82
- package/frontend/src/useAuthorized.ts +0 -17
- package/frontend/src/useFetchList.ts +0 -144
- package/frontend/src/useTheme.ts +0 -23
- package/frontend/tsconfig.json +0 -26
- package/frontend/vite.config.ts +0 -21
- package/src/QuickZipStream.ts +0 -279
- package/src/ThrottledStream.ts +0 -98
- package/src/adminApis.ts +0 -161
- package/src/api.accounts.ts +0 -78
- package/src/api.auth.ts +0 -131
- package/src/api.file_list.ts +0 -102
- package/src/api.helpers.ts +0 -30
- package/src/api.monitor.ts +0 -106
- package/src/api.plugins.ts +0 -139
- package/src/api.vfs.ts +0 -182
- package/src/apiMiddleware.ts +0 -124
- package/src/block.ts +0 -35
- package/src/commands.ts +0 -122
- package/src/config.ts +0 -166
- package/src/connections.ts +0 -60
- package/src/const.ts +0 -57
- package/src/crypt.ts +0 -16
- package/src/debounceAsync.ts +0 -51
- package/src/events.ts +0 -6
- package/src/frontEndApis.ts +0 -17
- package/src/github.ts +0 -102
- package/src/index.ts +0 -53
- package/src/listen.ts +0 -220
- package/src/log.ts +0 -128
- package/src/middlewares.ts +0 -176
- package/src/misc.ts +0 -149
- package/src/pbkdf2.ts +0 -83
- package/src/perm.ts +0 -194
- package/src/plugins.ts +0 -342
- package/src/serveFile.ts +0 -104
- package/src/serveGuiFiles.ts +0 -95
- package/src/sse.ts +0 -29
- package/src/throttler.ts +0 -106
- package/src/update.ts +0 -67
- package/src/util-files.ts +0 -137
- package/src/util-generators.ts +0 -29
- package/src/util-http.ts +0 -29
- package/src/vfs.ts +0 -258
- package/src/watchLoad.ts +0 -75
- package/src/zip.ts +0 -69
package/src/adminApis.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
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
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
|
+
if (mod && mod.__esModule) return mod;
|
|
21
|
+
var result = {};
|
|
22
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
23
|
+
__setModuleDefault(result, mod);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
|
+
};
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.ctxAdminAccess = exports.localhostAdmin = exports.adminApis = void 0;
|
|
31
|
+
const apiMiddleware_1 = require("./apiMiddleware");
|
|
32
|
+
const config_1 = require("./config");
|
|
33
|
+
const listen_1 = require("./listen");
|
|
34
|
+
const const_1 = require("./const");
|
|
35
|
+
const api_vfs_1 = __importDefault(require("./api.vfs"));
|
|
36
|
+
const api_accounts_1 = __importDefault(require("./api.accounts"));
|
|
37
|
+
const api_plugins_1 = __importDefault(require("./api.plugins"));
|
|
38
|
+
const api_monitor_1 = __importDefault(require("./api.monitor"));
|
|
39
|
+
const connections_1 = require("./connections");
|
|
40
|
+
const misc_1 = require("./misc");
|
|
41
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
42
|
+
const events_1 = __importDefault(require("./events"));
|
|
43
|
+
const perm_1 = require("./perm");
|
|
44
|
+
const middlewares_1 = require("./middlewares");
|
|
45
|
+
const promises_1 = require("fs/promises");
|
|
46
|
+
const fs_1 = require("fs");
|
|
47
|
+
const readline = __importStar(require("readline"));
|
|
48
|
+
const log_1 = require("./log");
|
|
49
|
+
const child_process_1 = require("child_process");
|
|
50
|
+
const util_1 = require("util");
|
|
51
|
+
exports.adminApis = {
|
|
52
|
+
...api_vfs_1.default,
|
|
53
|
+
...api_accounts_1.default,
|
|
54
|
+
...api_plugins_1.default,
|
|
55
|
+
...api_monitor_1.default,
|
|
56
|
+
async set_config({ values: v }) {
|
|
57
|
+
var _a, _b;
|
|
58
|
+
if (v) {
|
|
59
|
+
const st = (0, listen_1.getStatus)();
|
|
60
|
+
const noHttp = ((_a = v.port) !== null && _a !== void 0 ? _a : listen_1.portCfg.get()) < 0 || !st.httpSrv.listening;
|
|
61
|
+
const noHttps = ((_b = v.https_port) !== null && _b !== void 0 ? _b : listen_1.httpsPortCfg.get()) < 0 || !st.httpsSrv.listening;
|
|
62
|
+
if (noHttp && noHttps)
|
|
63
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_FORBIDDEN, "You cannot switch off both http and https ports");
|
|
64
|
+
await (0, config_1.setConfig)(v);
|
|
65
|
+
}
|
|
66
|
+
return {};
|
|
67
|
+
},
|
|
68
|
+
get_config: config_1.getWholeConfig,
|
|
69
|
+
async get_status() {
|
|
70
|
+
const st = (0, listen_1.getStatus)();
|
|
71
|
+
return {
|
|
72
|
+
started: const_1.HFS_STARTED,
|
|
73
|
+
build: const_1.BUILD_TIMESTAMP,
|
|
74
|
+
version: const_1.VERSION,
|
|
75
|
+
apiVersion: const_1.API_VERSION,
|
|
76
|
+
compatibleApiVersion: const_1.COMPATIBLE_API_VERSION,
|
|
77
|
+
http: await serverStatus(st.httpSrv, listen_1.portCfg.get()),
|
|
78
|
+
https: await serverStatus(st.httpsSrv, listen_1.httpsPortCfg.get()),
|
|
79
|
+
urls: (0, listen_1.getUrls)(),
|
|
80
|
+
proxyDetected: (0, middlewares_1.getProxyDetected)(),
|
|
81
|
+
frpDetected: exports.localhostAdmin.get() && !(0, middlewares_1.getProxyDetected)()
|
|
82
|
+
&& (0, connections_1.getConnections)().every(misc_1.isLocalHost)
|
|
83
|
+
&& await frpDebounced(),
|
|
84
|
+
};
|
|
85
|
+
async function serverStatus(h, configuredPort) {
|
|
86
|
+
var _a;
|
|
87
|
+
const busy = await h.busy;
|
|
88
|
+
await (0, misc_1.wait)(0); // simple trick to wait for also .error to be updated. If this trickery becomes necessary elsewhere, then we should make also error a Promise.
|
|
89
|
+
return {
|
|
90
|
+
...lodash_1.default.pick(h, ['listening', 'error']),
|
|
91
|
+
busy,
|
|
92
|
+
port: ((_a = h === null || h === void 0 ? void 0 : h.address()) === null || _a === void 0 ? void 0 : _a.port) || configuredPort,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
async save_pem({ cert, private_key, name = 'self' }) {
|
|
97
|
+
if (!cert || !private_key)
|
|
98
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
|
|
99
|
+
const files = { cert: name + '.cert', private_key: name + '.key' };
|
|
100
|
+
await (0, promises_1.writeFile)(files.private_key, private_key);
|
|
101
|
+
await (0, promises_1.writeFile)(files.cert, cert);
|
|
102
|
+
return files;
|
|
103
|
+
},
|
|
104
|
+
async get_log({ file = 'log' }, ctx) {
|
|
105
|
+
return new apiMiddleware_1.SendListReadable({
|
|
106
|
+
bufferTime: 10,
|
|
107
|
+
doAtStart(list) {
|
|
108
|
+
const logger = log_1.loggers.find(l => l.name === file);
|
|
109
|
+
if (!logger)
|
|
110
|
+
return list.error(const_1.HTTP_NOT_FOUND, true);
|
|
111
|
+
const input = (0, fs_1.createReadStream)(logger.path);
|
|
112
|
+
input.on('error', async (e) => {
|
|
113
|
+
if (e.code === 'ENOENT') // ignore ENOENT, consider it an empty log
|
|
114
|
+
return list.ready();
|
|
115
|
+
list.error(e.code || e.message);
|
|
116
|
+
});
|
|
117
|
+
input.on('end', () => list.ready());
|
|
118
|
+
input.on('ready', () => {
|
|
119
|
+
readline.createInterface({ input }).on('line', line => {
|
|
120
|
+
if (ctx.aborted)
|
|
121
|
+
return input.close();
|
|
122
|
+
const obj = parse(line);
|
|
123
|
+
if (obj)
|
|
124
|
+
list.add(obj);
|
|
125
|
+
}).on('close', () => {
|
|
126
|
+
ctx.res.once('close', (0, misc_1.onOff)(events_1.default, {
|
|
127
|
+
[logger.name](entry) {
|
|
128
|
+
list.add(entry);
|
|
129
|
+
}
|
|
130
|
+
}));
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
function parse(line) {
|
|
136
|
+
const m = /^(.+?) (.+?) (.+?) \[(.{11}):(.{14})] "(\w+) ([^"]+) HTTP\/\d.\d" (\d+) (-|\d+)/.exec(line);
|
|
137
|
+
if (!m)
|
|
138
|
+
return;
|
|
139
|
+
const [, ip, , user, date, time, method, uri, status, length] = m;
|
|
140
|
+
return {
|
|
141
|
+
ip,
|
|
142
|
+
user: user === '-' ? undefined : user,
|
|
143
|
+
ts: new Date(date + ' ' + time),
|
|
144
|
+
method,
|
|
145
|
+
uri,
|
|
146
|
+
status: Number(status),
|
|
147
|
+
length: length === '-' ? undefined : Number(length),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
for (const k in exports.adminApis) {
|
|
153
|
+
const was = exports.adminApis[k];
|
|
154
|
+
exports.adminApis[k] = (params, ctx) => ctxAdminAccess(ctx) ? was(params, ctx)
|
|
155
|
+
: new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
|
|
156
|
+
}
|
|
157
|
+
exports.localhostAdmin = (0, config_1.defineConfig)('localhost_admin', true);
|
|
158
|
+
function ctxAdminAccess(ctx) {
|
|
159
|
+
return !ctx.state.proxiedFor // we consider localhost_admin only if no proxy is detected
|
|
160
|
+
&& exports.localhostAdmin.get() && (0, misc_1.isLocalHost)(ctx)
|
|
161
|
+
|| (0, perm_1.getFromAccount)(ctx.state.account, a => a.admin);
|
|
162
|
+
}
|
|
163
|
+
exports.ctxAdminAccess = ctxAdminAccess;
|
|
164
|
+
const frpDebounced = (0, misc_1.debounceAsync)(async () => {
|
|
165
|
+
if (!const_1.IS_WINDOWS)
|
|
166
|
+
return false;
|
|
167
|
+
const { stdout } = await (0, util_1.promisify)(child_process_1.execFile)('tasklist', ['/fi', 'imagename eq frpc.exe', '/nh']);
|
|
168
|
+
return stdout.includes('frpc');
|
|
169
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const api_helpers_1 = require("./api.helpers");
|
|
8
|
+
const apiMiddleware_1 = require("./apiMiddleware");
|
|
9
|
+
const perm_1 = require("./perm");
|
|
10
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
11
|
+
const const_1 = require("./const");
|
|
12
|
+
function prepareAccount(ac) {
|
|
13
|
+
return ac && {
|
|
14
|
+
...lodash_1.default.omit(ac, ['password', 'hashed_password', 'srp']),
|
|
15
|
+
username: ac.username,
|
|
16
|
+
hasPassword: (0, perm_1.accountHasPassword)(ac),
|
|
17
|
+
adminActualAccess: (0, perm_1.accountCanLoginAdmin)(ac),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const apis = {
|
|
21
|
+
get_usernames() {
|
|
22
|
+
return { list: Object.keys(perm_1.accountsConfig.get()) };
|
|
23
|
+
},
|
|
24
|
+
get_account({ username }, ctx) {
|
|
25
|
+
return prepareAccount((0, perm_1.getAccount)(username || (0, perm_1.getCurrentUsername)(ctx)))
|
|
26
|
+
|| new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
|
|
27
|
+
},
|
|
28
|
+
get_accounts() {
|
|
29
|
+
return { list: Object.values(perm_1.accountsConfig.get()).map(prepareAccount) };
|
|
30
|
+
},
|
|
31
|
+
get_admins() {
|
|
32
|
+
return { list: lodash_1.default.filter(perm_1.accountsConfig.get(), perm_1.accountCanLoginAdmin).map(ac => ac.username) };
|
|
33
|
+
},
|
|
34
|
+
set_account({ username, changes }) {
|
|
35
|
+
const { admin } = changes;
|
|
36
|
+
if (admin === null)
|
|
37
|
+
changes.admin = undefined;
|
|
38
|
+
else if (admin !== undefined && typeof admin !== 'boolean')
|
|
39
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, "invalid admin");
|
|
40
|
+
const acc = (0, perm_1.setAccount)(username, changes);
|
|
41
|
+
return acc ? lodash_1.default.pick(acc, 'username') : new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
|
|
42
|
+
},
|
|
43
|
+
add_account({ username, ...rest }) {
|
|
44
|
+
if ((0, perm_1.getAccount)(username))
|
|
45
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_FORBIDDEN);
|
|
46
|
+
const acc = (0, perm_1.addAccount)(username, rest);
|
|
47
|
+
return acc ? lodash_1.default.pick(acc, 'username') : new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
|
|
48
|
+
},
|
|
49
|
+
del_account({ username }) {
|
|
50
|
+
return (0, perm_1.delAccount)(username) ? {} : new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
|
|
51
|
+
},
|
|
52
|
+
async change_password_others({ username, newPassword }) {
|
|
53
|
+
return (0, api_helpers_1.changePasswordHelper)((0, perm_1.getAccount)(username), newPassword);
|
|
54
|
+
},
|
|
55
|
+
async change_srp_others({ username, salt, verifier }) {
|
|
56
|
+
return (0, api_helpers_1.changeSrpHelper)((0, perm_1.getAccount)(username), salt, verifier);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
exports.default = apis;
|
package/src/api.auth.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
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.change_srp = exports.change_password = exports.refresh_session = exports.logout = exports.loginSrp2 = exports.srpStep1 = exports.loginSrp1 = exports.login = void 0;
|
|
5
|
+
const perm_1 = require("./perm");
|
|
6
|
+
const crypt_1 = require("./crypt");
|
|
7
|
+
const apiMiddleware_1 = require("./apiMiddleware");
|
|
8
|
+
const tssrp6a_1 = require("tssrp6a");
|
|
9
|
+
const const_1 = require("./const");
|
|
10
|
+
const misc_1 = require("./misc");
|
|
11
|
+
const api_helpers_1 = require("./api.helpers");
|
|
12
|
+
const adminApis_1 = require("./adminApis");
|
|
13
|
+
const middlewares_1 = require("./middlewares");
|
|
14
|
+
const srp6aNimbusRoutines = new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters());
|
|
15
|
+
const ongoingLogins = {}; // store data that doesn't fit session object
|
|
16
|
+
// centralized log-in state
|
|
17
|
+
async function loggedIn(ctx, username) {
|
|
18
|
+
const s = ctx.session;
|
|
19
|
+
if (!s)
|
|
20
|
+
return ctx.throw(const_1.HTTP_SERVER_ERROR, 'session');
|
|
21
|
+
if (username === false) {
|
|
22
|
+
delete s.username;
|
|
23
|
+
ctx.cookies.set('csrf', '');
|
|
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
|
+
delete s.login;
|
|
29
|
+
ctx.cookies.set('csrf', (0, misc_1.randomId)(), { signed: false, httpOnly: false });
|
|
30
|
+
}
|
|
31
|
+
function makeExp() {
|
|
32
|
+
return { exp: new Date(Date.now() + const_1.SESSION_DURATION) };
|
|
33
|
+
}
|
|
34
|
+
const login = async ({ username, password }, ctx) => {
|
|
35
|
+
if (!username || !password) // some validation
|
|
36
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
|
|
37
|
+
const acc = (0, perm_1.getAccount)(username);
|
|
38
|
+
if (!acc)
|
|
39
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
|
|
40
|
+
if (!acc.hashed_password)
|
|
41
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_ACCEPTABLE);
|
|
42
|
+
if (!await (0, crypt_1.verifyPassword)(acc.hashed_password, password))
|
|
43
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
|
|
44
|
+
if (!ctx.session)
|
|
45
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
|
|
46
|
+
await loggedIn(ctx, username);
|
|
47
|
+
return { ...makeExp(), redirect: acc.redirect };
|
|
48
|
+
};
|
|
49
|
+
exports.login = login;
|
|
50
|
+
const loginSrp1 = async ({ username }, ctx) => {
|
|
51
|
+
if (!username)
|
|
52
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
|
|
53
|
+
const account = (0, perm_1.getAccount)(username);
|
|
54
|
+
if (!ctx.session)
|
|
55
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
|
|
56
|
+
if (!account) // TODO simulate fake account to prevent knowing valid usernames
|
|
57
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
|
|
58
|
+
try {
|
|
59
|
+
const { step1, ...rest } = await srpStep1(account);
|
|
60
|
+
const sid = Math.random();
|
|
61
|
+
ongoingLogins[sid] = step1;
|
|
62
|
+
setTimeout(() => delete ongoingLogins[sid], 60000);
|
|
63
|
+
ctx.session.login = { username, sid };
|
|
64
|
+
return rest;
|
|
65
|
+
}
|
|
66
|
+
catch (code) {
|
|
67
|
+
return new apiMiddleware_1.ApiError(code);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
exports.loginSrp1 = loginSrp1;
|
|
71
|
+
async function srpStep1(account) {
|
|
72
|
+
if (!account.srp)
|
|
73
|
+
throw const_1.HTTP_NOT_ACCEPTABLE;
|
|
74
|
+
const [salt, verifier] = account.srp.split('|');
|
|
75
|
+
const srpSession = new tssrp6a_1.SRPServerSession(srp6aNimbusRoutines);
|
|
76
|
+
const step1 = await srpSession.step1(account.username, BigInt(salt), BigInt(verifier));
|
|
77
|
+
return { step1, salt, pubKey: String(step1.B) }; // cast to string cause bigint can't be jsonized
|
|
78
|
+
}
|
|
79
|
+
exports.srpStep1 = srpStep1;
|
|
80
|
+
const loginSrp2 = async ({ pubKey, proof }, ctx) => {
|
|
81
|
+
var _a;
|
|
82
|
+
if (!ctx.session)
|
|
83
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
|
|
84
|
+
if (!ctx.session.login)
|
|
85
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT);
|
|
86
|
+
const { username, sid } = ctx.session.login;
|
|
87
|
+
const step1 = ongoingLogins[sid];
|
|
88
|
+
try {
|
|
89
|
+
const M2 = await step1.step2(BigInt(pubKey), BigInt(proof));
|
|
90
|
+
await loggedIn(ctx, username);
|
|
91
|
+
return {
|
|
92
|
+
proof: String(M2),
|
|
93
|
+
redirect: (_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.redirect,
|
|
94
|
+
...await (0, exports.refresh_session)({}, ctx)
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED, String(e));
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
delete ongoingLogins[sid];
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
exports.loginSrp2 = loginSrp2;
|
|
105
|
+
const logout = async ({}, ctx) => {
|
|
106
|
+
if (!ctx.session)
|
|
107
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
|
|
108
|
+
await loggedIn(ctx, false);
|
|
109
|
+
// 401 is a convenient code for OK: the browser clears a possible http authentication (hopefully), and Admin automatically triggers login dialog
|
|
110
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
|
|
111
|
+
};
|
|
112
|
+
exports.logout = logout;
|
|
113
|
+
const refresh_session = async ({}, ctx) => {
|
|
114
|
+
return !ctx.session ? new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR) : {
|
|
115
|
+
username: (0, perm_1.getCurrentUsername)(ctx),
|
|
116
|
+
adminUrl: (0, adminApis_1.ctxAdminAccess)(ctx) ? const_1.ADMIN_URI : undefined,
|
|
117
|
+
...makeExp(),
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
exports.refresh_session = refresh_session;
|
|
121
|
+
const change_password = async ({ newPassword }, ctx) => {
|
|
122
|
+
return (0, api_helpers_1.changePasswordHelper)(ctx.state.account, newPassword);
|
|
123
|
+
};
|
|
124
|
+
exports.change_password = change_password;
|
|
125
|
+
const change_srp = async ({ salt, verifier }, ctx) => {
|
|
126
|
+
return (0, api_helpers_1.changeSrpHelper)(ctx.state.account, salt, verifier);
|
|
127
|
+
};
|
|
128
|
+
exports.change_srp = change_srp;
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.file_list = void 0;
|
|
8
|
+
const vfs_1 = require("./vfs");
|
|
9
|
+
const apiMiddleware_1 = require("./apiMiddleware");
|
|
10
|
+
const promises_1 = require("fs/promises");
|
|
11
|
+
const plugins_1 = require("./plugins");
|
|
12
|
+
const misc_1 = require("./misc");
|
|
13
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
14
|
+
const const_1 = require("./const");
|
|
15
|
+
const file_list = async ({ path, offset, limit, search, omit, sse }, ctx) => {
|
|
16
|
+
let node = await (0, vfs_1.urlToNode)(path || '/', ctx);
|
|
17
|
+
const list = new apiMiddleware_1.SendListReadable();
|
|
18
|
+
if (!node)
|
|
19
|
+
return fail(const_1.HTTP_NOT_FOUND);
|
|
20
|
+
if (!(0, vfs_1.hasPermission)(node, 'can_read', ctx))
|
|
21
|
+
return fail((0, vfs_1.cantReadStatusCode)(node));
|
|
22
|
+
if ((0, misc_1.dirTraversal)(search))
|
|
23
|
+
return fail(const_1.HTTP_FOOL);
|
|
24
|
+
if (node.default)
|
|
25
|
+
return (sse ? list.custom : lodash_1.default.identity)({ redirect: path }); // sse will wrap the object in a 'custom' message, otherwise we plainly return the object
|
|
26
|
+
if (!await (0, vfs_1.nodeIsDirectory)(node))
|
|
27
|
+
return fail(const_1.HTTP_METHOD_NOT_ALLOWED);
|
|
28
|
+
offset = Number(offset);
|
|
29
|
+
limit = Number(limit);
|
|
30
|
+
const filter = (0, misc_1.pattern2filter)(search);
|
|
31
|
+
const walker = (0, vfs_1.walkNode)(node, ctx, search ? Infinity : 0);
|
|
32
|
+
const onDirEntryHandlers = (0, plugins_1.mapPlugins)(plug => plug.onDirEntry);
|
|
33
|
+
const can_upload = (0, vfs_1.hasPermission)(node, 'can_upload', ctx);
|
|
34
|
+
if (!sse)
|
|
35
|
+
return {
|
|
36
|
+
can_upload,
|
|
37
|
+
list: await (0, misc_1.asyncGeneratorToArray)(produceEntries())
|
|
38
|
+
};
|
|
39
|
+
setTimeout(async () => {
|
|
40
|
+
if (can_upload)
|
|
41
|
+
list.custom({ props: { can_upload } });
|
|
42
|
+
for await (const entry of produceEntries())
|
|
43
|
+
list.add(entry);
|
|
44
|
+
list.close();
|
|
45
|
+
});
|
|
46
|
+
return list;
|
|
47
|
+
function fail(code) {
|
|
48
|
+
if (!sse)
|
|
49
|
+
return new apiMiddleware_1.ApiError(code);
|
|
50
|
+
list.error(code);
|
|
51
|
+
list.close();
|
|
52
|
+
return list;
|
|
53
|
+
}
|
|
54
|
+
async function* produceEntries() {
|
|
55
|
+
for await (const sub of walker) {
|
|
56
|
+
if (ctx.aborted)
|
|
57
|
+
break;
|
|
58
|
+
if (!filter((0, vfs_1.getNodeName)(sub)))
|
|
59
|
+
continue;
|
|
60
|
+
const entry = await nodeToDirEntry(sub);
|
|
61
|
+
if (!entry)
|
|
62
|
+
continue;
|
|
63
|
+
const cbParams = { entry, ctx, listPath: path, node: sub };
|
|
64
|
+
try {
|
|
65
|
+
if (onDirEntryHandlers.some(cb => cb(cbParams) === false))
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
console.log("a plugin with onDirEntry is causing problems:", e);
|
|
70
|
+
}
|
|
71
|
+
if (offset) {
|
|
72
|
+
--offset;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (omit) {
|
|
76
|
+
if (omit !== 'c')
|
|
77
|
+
ctx.throw(const_1.HTTP_BAD_REQUEST, 'omit');
|
|
78
|
+
if (!entry.m)
|
|
79
|
+
entry.m = entry.c;
|
|
80
|
+
delete entry.c;
|
|
81
|
+
}
|
|
82
|
+
yield entry;
|
|
83
|
+
if (limit && !--limit)
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
exports.file_list = file_list;
|
|
89
|
+
async function nodeToDirEntry(node) {
|
|
90
|
+
let { source, default: def } = node;
|
|
91
|
+
const name = (0, vfs_1.getNodeName)(node);
|
|
92
|
+
if (!source)
|
|
93
|
+
return name ? { n: name + '/' } : null;
|
|
94
|
+
if (def)
|
|
95
|
+
return { n: name };
|
|
96
|
+
try {
|
|
97
|
+
const st = await (0, promises_1.stat)(source);
|
|
98
|
+
const folder = st.isDirectory();
|
|
99
|
+
const { ctime, mtime } = st;
|
|
100
|
+
return {
|
|
101
|
+
n: name + (folder ? '/' : ''),
|
|
102
|
+
c: ctime,
|
|
103
|
+
m: Math.abs(+mtime - +ctime) < 1000 ? undefined : mtime,
|
|
104
|
+
s: folder ? undefined : st.size,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (_a) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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.changeSrpHelper = exports.changePasswordHelper = void 0;
|
|
5
|
+
const perm_1 = require("./perm");
|
|
6
|
+
const apiMiddleware_1 = require("./apiMiddleware");
|
|
7
|
+
const const_1 = require("./const");
|
|
8
|
+
async function changePasswordHelper(account, newPassword) {
|
|
9
|
+
if (!newPassword) // clear text version
|
|
10
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'missing parameters');
|
|
11
|
+
if (!account)
|
|
12
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
|
|
13
|
+
await (0, perm_1.updateAccount)(account, account => {
|
|
14
|
+
account.password = newPassword;
|
|
15
|
+
});
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
exports.changePasswordHelper = changePasswordHelper;
|
|
19
|
+
async function changeSrpHelper(account, salt, verifier) {
|
|
20
|
+
if (perm_1.allowClearTextLogin.get())
|
|
21
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_ACCEPTABLE);
|
|
22
|
+
if (!salt || !verifier)
|
|
23
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'missing parameters');
|
|
24
|
+
if (!account)
|
|
25
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
|
|
26
|
+
await (0, perm_1.updateAccount)(account, account => {
|
|
27
|
+
(0, perm_1.saveSrpInfo)(account, salt, verifier);
|
|
28
|
+
delete account.hashed_password; // remove leftovers
|
|
29
|
+
});
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
exports.changeSrpHelper = changeSrpHelper;
|
|
@@ -0,0 +1,104 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
8
|
+
const connections_1 = require("./connections");
|
|
9
|
+
const misc_1 = require("./misc");
|
|
10
|
+
const apiMiddleware_1 = require("./apiMiddleware");
|
|
11
|
+
const throttler_1 = require("./throttler");
|
|
12
|
+
const perm_1 = require("./perm");
|
|
13
|
+
const apis = {
|
|
14
|
+
async disconnect({ ip, port, wait }) {
|
|
15
|
+
const match = lodash_1.default.matches({ ip, port });
|
|
16
|
+
const c = (0, connections_1.getConnections)().find(c => match(getConnAddress(c)));
|
|
17
|
+
const waiter = (0, misc_1.pendingPromise)();
|
|
18
|
+
c === null || c === void 0 ? void 0 : c.socket.end(waiter.resolve);
|
|
19
|
+
if (wait)
|
|
20
|
+
await waiter;
|
|
21
|
+
return { result: Boolean(c) };
|
|
22
|
+
},
|
|
23
|
+
get_connections({}, ctx) {
|
|
24
|
+
const list = new apiMiddleware_1.SendListReadable({ addAtStart: (0, connections_1.getConnections)().map(c => serializeConnection(c)) });
|
|
25
|
+
const throttledUpdate = lodash_1.default.throttle(update, 1000 / 20); // try to avoid clogging with updates
|
|
26
|
+
const state = Symbol('state'); // undefined=added, Timeout=add-pending, false=removed
|
|
27
|
+
list.props({ you: ctx.ip });
|
|
28
|
+
return list.events(ctx, {
|
|
29
|
+
connection(conn) {
|
|
30
|
+
conn[state] = setTimeout(() => add(conn), 100);
|
|
31
|
+
},
|
|
32
|
+
connectionClosed(conn) {
|
|
33
|
+
if (cancel(conn))
|
|
34
|
+
return;
|
|
35
|
+
list.remove(serializeConnection(conn, true));
|
|
36
|
+
conn[state] = false;
|
|
37
|
+
},
|
|
38
|
+
connectionUpdated(conn, change) {
|
|
39
|
+
if (!change.ctx)
|
|
40
|
+
return throttledUpdate(conn, change);
|
|
41
|
+
Object.assign(change, fromCtx(change.ctx));
|
|
42
|
+
change.ctx = undefined;
|
|
43
|
+
if (!add(conn))
|
|
44
|
+
throttledUpdate(conn, change);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
function add(conn) {
|
|
48
|
+
if (!cancel(conn))
|
|
49
|
+
return;
|
|
50
|
+
list.add(serializeConnection(conn));
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
function cancel(conn) {
|
|
54
|
+
if (!conn[state])
|
|
55
|
+
return;
|
|
56
|
+
clearTimeout(conn[state]);
|
|
57
|
+
conn[state] = undefined;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
function update(conn, change) {
|
|
61
|
+
if (conn[state] === false)
|
|
62
|
+
return;
|
|
63
|
+
list.update(serializeConnection(conn, true), change);
|
|
64
|
+
}
|
|
65
|
+
function serializeConnection(conn, minimal) {
|
|
66
|
+
var _a;
|
|
67
|
+
const { socket, started, secure } = conn;
|
|
68
|
+
return Object.assign(getConnAddress(conn), !minimal && {
|
|
69
|
+
v: (((_a = socket.remoteFamily) === null || _a === void 0 ? void 0 : _a.endsWith('6')) ? 6 : 4),
|
|
70
|
+
got: socket.bytesRead,
|
|
71
|
+
sent: socket.bytesWritten,
|
|
72
|
+
started,
|
|
73
|
+
secure: (secure || undefined),
|
|
74
|
+
...fromCtx(conn.ctx),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function fromCtx(ctx) {
|
|
78
|
+
return ctx && {
|
|
79
|
+
user: (0, perm_1.getCurrentUsername)(ctx),
|
|
80
|
+
archive: ctx.state.archive,
|
|
81
|
+
path: (ctx.fileSource || ctx.state.archive) && ctx.path // only for downloading files
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
async *get_connection_stats() {
|
|
86
|
+
while (1) {
|
|
87
|
+
yield {
|
|
88
|
+
outSpeed: throttler_1.totalOutSpeed,
|
|
89
|
+
inSpeed: throttler_1.totalInSpeed,
|
|
90
|
+
got: throttler_1.totalGot,
|
|
91
|
+
sent: throttler_1.totalSent,
|
|
92
|
+
connections: (0, connections_1.getConnections)().length
|
|
93
|
+
};
|
|
94
|
+
await (0, misc_1.wait)(1000);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
exports.default = apis;
|
|
99
|
+
function getConnAddress(conn) {
|
|
100
|
+
return {
|
|
101
|
+
ip: conn.ip,
|
|
102
|
+
port: conn.socket.remotePort,
|
|
103
|
+
};
|
|
104
|
+
}
|