hfs 0.29.1 → 0.30.1
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/admin/assets/index-57af5a8c.js +414 -0
- package/admin/assets/index-6f45486d.css +1 -0
- package/{frontend/assets/sha512-2c2fa926.js → admin/assets/sha512-15452202.js} +1 -1
- package/admin/{logo.svg → cup.svg} +0 -0
- package/admin/hfs-logo-icon.svg +46 -0
- package/admin/hfs-logo.svg +43 -0
- package/admin/index.html +3 -3
- package/frontend/assets/index-0b590f44.css +1 -0
- package/frontend/assets/index-0d285a11.js +85 -0
- package/{admin/assets/sha512-3273321f.js → frontend/assets/sha512-4eeed44e.js} +1 -1
- package/frontend/index.html +2 -2
- package/package.json +2 -1
- package/src/adminApis.js +8 -2
- package/src/api.accounts.js +3 -1
- package/src/api.vfs.js +4 -2
- package/src/apiMiddleware.js +2 -5
- package/src/const.js +1 -1
- package/src/index.js +1 -2
- package/src/middlewares.js +21 -2
- package/src/watchLoad.js +13 -19
- package/admin/assets/index-cbb42a0e.js +0 -415
- package/admin/assets/index-f8049da8.css +0 -1
- package/frontend/assets/index-72e96bb2.js +0 -85
- package/frontend/assets/index-cbcc6ac5.css +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{c as SF}from"./index-
|
|
1
|
+
import{c as SF}from"./index-0d285a11.js";function OF(iF,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 iF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(iF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(iF,Symbol.toStringTag,{value:"Module"}))}var EF={},UF={get exports(){return EF},set exports(iF){EF=iF}};/*
|
|
2
2
|
* [js-sha512]{@link https://github.com/emn178/js-sha512}
|
|
3
3
|
*
|
|
4
4
|
* @version 0.8.0
|
package/frontend/index.html
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<link href="/fontello.css" rel="stylesheet" />
|
|
7
7
|
<script>SESSION = _HFS_SESSION_</script>
|
|
8
8
|
<title>File Server</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="stylesheet" href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-0d285a11.js"></script>
|
|
10
|
+
<link rel="stylesheet" href="/assets/index-0b590f44.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div hidden>_HFS_PLUGINS_</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hfs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.1",
|
|
4
4
|
"description": "HTTP File Server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"file server",
|
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
"@types/unzipper": "^0.10.5",
|
|
97
97
|
"axios": "^0.24.0",
|
|
98
98
|
"axios-cookiejar-support": "^4.0.1",
|
|
99
|
+
"cross-env": "^7.0.3",
|
|
99
100
|
"koa-better-http-proxy": "^0.2.9",
|
|
100
101
|
"mocha": "^9.1.3",
|
|
101
102
|
"nm-prune": "^5.0.0",
|
package/src/adminApis.js
CHANGED
|
@@ -150,8 +150,14 @@ exports.adminApis = {
|
|
|
150
150
|
},
|
|
151
151
|
};
|
|
152
152
|
for (const [k, was] of Object.entries(exports.adminApis))
|
|
153
|
-
exports.adminApis[k] = (params, ctx) =>
|
|
154
|
-
|
|
153
|
+
exports.adminApis[k] = (params, ctx) => {
|
|
154
|
+
if (ctxAdminAccess(ctx))
|
|
155
|
+
return was(params, ctx);
|
|
156
|
+
const props = { any: (0, perm_1.anyAccountCanLoginAdmin)() };
|
|
157
|
+
return ctx.headers.accept === 'text/event-stream'
|
|
158
|
+
? new apiMiddleware_1.SendListReadable({ doAtStart: x => x.error(const_1.HTTP_UNAUTHORIZED, true, props) })
|
|
159
|
+
: new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED, props);
|
|
160
|
+
};
|
|
155
161
|
exports.localhostAdmin = (0, config_1.defineConfig)('localhost_admin', true);
|
|
156
162
|
function ctxAdminAccess(ctx) {
|
|
157
163
|
return !ctx.state.proxiedFor // we consider localhost_admin only if no proxy is detected
|
package/src/api.accounts.js
CHANGED
|
@@ -31,13 +31,15 @@ const apis = {
|
|
|
31
31
|
get_admins() {
|
|
32
32
|
return { list: lodash_1.default.filter(perm_1.accountsConfig.get(), perm_1.accountCanLoginAdmin).map(ac => ac.username) };
|
|
33
33
|
},
|
|
34
|
-
set_account({ username, changes }) {
|
|
34
|
+
set_account({ username, changes }, ctx) {
|
|
35
35
|
const { admin } = changes;
|
|
36
36
|
if (admin === null)
|
|
37
37
|
changes.admin = undefined;
|
|
38
38
|
else if (admin !== undefined && typeof admin !== 'boolean')
|
|
39
39
|
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, "invalid admin");
|
|
40
40
|
const acc = (0, perm_1.setAccount)(username, changes);
|
|
41
|
+
if (changes.username && ctx.session)
|
|
42
|
+
ctx.session.username = changes.username;
|
|
41
43
|
return acc ? lodash_1.default.pick(acc, 'username') : new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
|
|
42
44
|
},
|
|
43
45
|
add_account({ username, ...rest }) {
|
package/src/api.vfs.js
CHANGED
|
@@ -84,13 +84,15 @@ const apis = {
|
|
|
84
84
|
errors: await Promise.all(uris.map(async (uri) => {
|
|
85
85
|
if (typeof uri !== 'string')
|
|
86
86
|
return const_1.HTTP_BAD_REQUEST;
|
|
87
|
+
if (uri === '/')
|
|
88
|
+
return const_1.HTTP_NOT_ACCEPTABLE;
|
|
87
89
|
const node = await urlToNodeOriginal(uri);
|
|
88
90
|
if (!node)
|
|
89
91
|
return const_1.HTTP_NOT_FOUND;
|
|
90
92
|
const parent = (0, path_1.dirname)(uri);
|
|
91
93
|
const parentNode = await urlToNodeOriginal(parent);
|
|
92
|
-
if (!parentNode)
|
|
93
|
-
return const_1.
|
|
94
|
+
if (!parentNode) // shouldn't happen
|
|
95
|
+
return const_1.HTTP_SERVER_ERROR;
|
|
94
96
|
const { children } = parentNode;
|
|
95
97
|
if (!children) // shouldn't happen
|
|
96
98
|
return const_1.HTTP_SERVER_ERROR;
|
package/src/apiMiddleware.js
CHANGED
|
@@ -27,9 +27,6 @@ function apiMiddleware(apis) {
|
|
|
27
27
|
ctx.body = 'invalid api';
|
|
28
28
|
return ctx.status = const_1.HTTP_NOT_FOUND;
|
|
29
29
|
}
|
|
30
|
-
ctx.params = ctx.method === 'POST' ? (0, misc_1.tryJson)(await (0, misc_1.stream2string)(ctx.req))
|
|
31
|
-
: (0, misc_1.objSameKeys)(ctx.query, x => Array.isArray(x) ? x : (0, misc_1.tryJson)(x));
|
|
32
|
-
console.debug('API', ctx.method, ctx.path, { ...ctx.params });
|
|
33
30
|
const csrf = ctx.cookies.get('csrf');
|
|
34
31
|
// we don't rely on SameSite cookie option because it's https-only
|
|
35
32
|
let res = csrf && csrf !== ctx.params.csrf ? new ApiError(const_1.HTTP_UNAUTHORIZED, 'csrf')
|
|
@@ -102,8 +99,8 @@ class SendListReadable extends stream_1.Readable {
|
|
|
102
99
|
props(props) {
|
|
103
100
|
this._push({ props });
|
|
104
101
|
}
|
|
105
|
-
error(msg, close = false) {
|
|
106
|
-
this._push({ error: msg });
|
|
102
|
+
error(msg, close = false, props) {
|
|
103
|
+
this._push({ error: msg, ...props });
|
|
107
104
|
this.lastError = msg;
|
|
108
105
|
if (close)
|
|
109
106
|
this.close();
|
package/src/const.js
CHANGED
|
@@ -38,7 +38,7 @@ exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
|
|
|
38
38
|
exports.ORIGINAL_CWD = process.cwd();
|
|
39
39
|
exports.HFS_STARTED = new Date();
|
|
40
40
|
const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
|
|
41
|
-
exports.BUILD_TIMESTAMP = "2023-
|
|
41
|
+
exports.BUILD_TIMESTAMP = "2023-02-05T21:56:10.539Z";
|
|
42
42
|
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
|
|
43
43
|
exports.VERSION = pkg.version;
|
|
44
44
|
exports.DAY = 86400000;
|
package/src/index.js
CHANGED
|
@@ -23,7 +23,6 @@ const config_1 = require("./config");
|
|
|
23
23
|
const assert_1 = require("assert");
|
|
24
24
|
const lodash_1 = __importDefault(require("lodash"));
|
|
25
25
|
const misc_1 = require("./misc");
|
|
26
|
-
//import body from 'koa-better-body'
|
|
27
26
|
(0, assert_1.ok)(lodash_1.default.intersection(Object.keys(frontEndApis_1.frontEndApis), Object.keys(adminApis_1.adminApis)).length === 0); // they share same endpoints
|
|
28
27
|
const keys = ((_a = process.env.COOKIE_SIGN_KEYS) === null || _a === void 0 ? void 0 : _a.split(',')) || [(0, misc_1.randomId)(30)];
|
|
29
28
|
exports.app = new koa_1.default({ keys });
|
|
@@ -34,9 +33,9 @@ exports.app.use(middlewares_1.someSecurity)
|
|
|
34
33
|
.use((0, log_1.log)())
|
|
35
34
|
.use(throttler_1.throttler)
|
|
36
35
|
.use(middlewares_1.gzipper)
|
|
36
|
+
.use(middlewares_1.paramsDecoder) // must be done before plugins, so they can manipulate params
|
|
37
37
|
.use((0, plugins_1.pluginsMiddleware)())
|
|
38
38
|
.use((0, koa_mount_1.default)(const_1.API_URI, (0, apiMiddleware_1.apiMiddleware)({ ...frontEndApis_1.frontEndApis, ...adminApis_1.adminApis })))
|
|
39
|
-
//.use(body({ multipart: false }))
|
|
40
39
|
.use(middlewares_1.serveGuiAndSharedFiles)
|
|
41
40
|
.on('error', errorHandler);
|
|
42
41
|
function errorHandler(err) {
|
package/src/middlewares.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.prepareState = exports.getProxyDetected = exports.someSecurity = exports.serveGuiAndSharedFiles = exports.sessions = exports.headRequests = exports.gzipper = void 0;
|
|
7
|
+
exports.paramsDecoder = exports.prepareState = exports.getProxyDetected = exports.someSecurity = exports.serveGuiAndSharedFiles = exports.sessions = exports.headRequests = exports.gzipper = void 0;
|
|
8
8
|
const koa_compress_1 = __importDefault(require("koa-compress"));
|
|
9
9
|
const koa_session_1 = __importDefault(require("koa-session"));
|
|
10
10
|
const const_1 = require("./const");
|
|
@@ -59,9 +59,21 @@ const sessions = (app) => (0, koa_session_1.default)({
|
|
|
59
59
|
exports.sessions = sessions;
|
|
60
60
|
const serveFrontendFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.FRONTEND_PROXY, const_2.FRONTEND_URI);
|
|
61
61
|
const serveFrontendPrefixed = (0, koa_mount_1.default)(const_2.FRONTEND_URI.slice(0, -1), serveFrontendFiles);
|
|
62
|
-
const
|
|
62
|
+
const serveAdminFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.ADMIN_PROXY, const_1.ADMIN_URI);
|
|
63
|
+
const serveAdminPrefixed = (0, koa_mount_1.default)(const_1.ADMIN_URI.slice(0, -1), serveAdminFiles);
|
|
63
64
|
const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
64
65
|
const { path } = ctx;
|
|
66
|
+
// dynamic import on frontend|admin (used for non-https login) while developing (vite4) is not producing a relative path
|
|
67
|
+
if (const_1.DEV && path.startsWith('/node_modules/')) {
|
|
68
|
+
let { referer } = ctx.headers;
|
|
69
|
+
referer && (referer = new URL(referer).pathname);
|
|
70
|
+
if (referer) {
|
|
71
|
+
if (referer.startsWith(const_1.ADMIN_URI))
|
|
72
|
+
return serveAdminFiles(ctx, next);
|
|
73
|
+
if (referer.startsWith(const_2.FRONTEND_URI))
|
|
74
|
+
return serveFrontendFiles(ctx, next);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
65
77
|
if (ctx.body)
|
|
66
78
|
return next();
|
|
67
79
|
if (path.startsWith(const_2.FRONTEND_URI))
|
|
@@ -179,3 +191,10 @@ async function srpCheck(username, password) {
|
|
|
179
191
|
const clientRes2 = await clientRes1.step2(BigInt(salt), BigInt(pubKey));
|
|
180
192
|
return await step1.step2(clientRes2.A, clientRes2.M1).then(() => true, () => false);
|
|
181
193
|
}
|
|
194
|
+
// unify get/post parameters, with JSON decoding to not be limited to strings
|
|
195
|
+
const paramsDecoder = async (ctx, next) => {
|
|
196
|
+
ctx.params = ctx.method === 'POST' ? (0, misc_1.tryJson)(await (0, misc_1.stream2string)(ctx.req))
|
|
197
|
+
: (0, misc_1.objSameKeys)(ctx.query, x => Array.isArray(x) ? x : (0, misc_1.tryJson)(x));
|
|
198
|
+
await next();
|
|
199
|
+
};
|
|
200
|
+
exports.paramsDecoder = paramsDecoder;
|
package/src/watchLoad.js
CHANGED
|
@@ -12,10 +12,10 @@ const misc_1 = require("./misc");
|
|
|
12
12
|
function watchLoad(path, parser, { failedOnFirstAttempt } = {}) {
|
|
13
13
|
let doing = false;
|
|
14
14
|
let watcher;
|
|
15
|
-
const debounced = (0, misc_1.debounceAsync)(load, 500, {
|
|
15
|
+
const debounced = (0, misc_1.debounceAsync)(load, 500, { maxWait: 1000 });
|
|
16
16
|
let retry;
|
|
17
17
|
let saving;
|
|
18
|
-
let
|
|
18
|
+
let last;
|
|
19
19
|
init().then(ok => ok || (failedOnFirstAttempt === null || failedOnFirstAttempt === void 0 ? void 0 : failedOnFirstAttempt()));
|
|
20
20
|
return {
|
|
21
21
|
unwatch() {
|
|
@@ -46,24 +46,18 @@ function watchLoad(path, parser, { failedOnFirstAttempt } = {}) {
|
|
|
46
46
|
if (doing)
|
|
47
47
|
return;
|
|
48
48
|
doing = true;
|
|
49
|
-
let data;
|
|
50
49
|
try {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return; // ignore read errors
|
|
63
|
-
}
|
|
64
|
-
if (path.endsWith('.yaml'))
|
|
65
|
-
data = yaml_1.default.parse(data);
|
|
66
|
-
await parser(data);
|
|
50
|
+
const text = await (0, misc_1.readFileBusy)(path);
|
|
51
|
+
if (text === last)
|
|
52
|
+
return;
|
|
53
|
+
last = text;
|
|
54
|
+
console.debug('loaded', path);
|
|
55
|
+
const parsed = path.endsWith('.yaml') ? yaml_1.default.parse(text) : text;
|
|
56
|
+
await parser(parsed);
|
|
57
|
+
}
|
|
58
|
+
catch (e) { // ignore read errors
|
|
59
|
+
if (e.code === 'EPERM')
|
|
60
|
+
console.error("missing permissions on file", path); // warn user, who could be clueless about this problem
|
|
67
61
|
}
|
|
68
62
|
finally {
|
|
69
63
|
doing = false;
|