hfs 0.26.8 → 0.26.9

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.
Files changed (161) hide show
  1. package/admin/assets/index.bb5198ec.js +281 -0
  2. package/admin/assets/index.dcc78777.css +1 -0
  3. package/admin/assets/sha512.9dfe82e1.js +8 -0
  4. package/admin/index.html +3 -1
  5. package/admin/{public/logo.svg → logo.svg} +0 -0
  6. package/frontend/assets/index.27a78796.js +85 -0
  7. package/frontend/assets/index.93366732.css +1 -0
  8. package/frontend/assets/sha512.6af42937.js +8 -0
  9. package/frontend/{public/fontello.css → fontello.css} +0 -0
  10. package/frontend/{public/fontello.woff2 → fontello.woff2} +0 -0
  11. package/frontend/index.html +3 -1
  12. package/package.json +1 -1
  13. package/src/QuickZipStream.js +285 -0
  14. package/src/ThrottledStream.js +93 -0
  15. package/src/adminApis.js +169 -0
  16. package/src/api.accounts.js +59 -0
  17. package/src/api.auth.js +128 -0
  18. package/src/api.file_list.js +103 -0
  19. package/src/api.helpers.js +32 -0
  20. package/src/api.monitor.js +102 -0
  21. package/src/api.plugins.js +127 -0
  22. package/src/api.vfs.js +164 -0
  23. package/src/apiMiddleware.js +120 -0
  24. package/src/block.js +33 -0
  25. package/src/commands.js +124 -0
  26. package/src/config.js +168 -0
  27. package/src/connections.js +57 -0
  28. package/src/const.js +83 -0
  29. package/src/crypt.js +21 -0
  30. package/src/debounceAsync.js +48 -0
  31. package/src/events.js +9 -0
  32. package/src/frontEndApis.js +38 -0
  33. package/src/github.js +102 -0
  34. package/src/index.js +56 -0
  35. package/src/listen.js +235 -0
  36. package/src/log.js +137 -0
  37. package/src/middlewares.js +175 -0
  38. package/src/misc.js +160 -0
  39. package/src/pbkdf2.js +74 -0
  40. package/src/perm.js +181 -0
  41. package/src/plugins.js +343 -0
  42. package/src/serveFile.js +105 -0
  43. package/src/serveGuiFiles.js +113 -0
  44. package/src/sse.js +29 -0
  45. package/src/throttler.js +91 -0
  46. package/src/update.js +69 -0
  47. package/src/util-files.js +148 -0
  48. package/src/util-generators.js +30 -0
  49. package/src/util-http.js +30 -0
  50. package/src/vfs.js +230 -0
  51. package/src/watchLoad.js +73 -0
  52. package/src/zip.js +72 -0
  53. package/admin/.DS_Store +0 -0
  54. package/admin/.eslintrc +0 -8
  55. package/admin/.gitignore +0 -23
  56. package/admin/package.json +0 -67
  57. package/admin/src/AccountForm.ts +0 -92
  58. package/admin/src/AccountsPage.ts +0 -143
  59. package/admin/src/App.ts +0 -83
  60. package/admin/src/ArrayField.ts +0 -84
  61. package/admin/src/ConfigPage.ts +0 -279
  62. package/admin/src/FileField.ts +0 -52
  63. package/admin/src/FileForm.ts +0 -148
  64. package/admin/src/FilePicker.ts +0 -166
  65. package/admin/src/HomePage.ts +0 -96
  66. package/admin/src/InstalledPlugins.ts +0 -158
  67. package/admin/src/LoginRequired.ts +0 -75
  68. package/admin/src/LogoutPage.ts +0 -27
  69. package/admin/src/LogsPage.ts +0 -75
  70. package/admin/src/MainMenu.ts +0 -74
  71. package/admin/src/MenuButton.ts +0 -38
  72. package/admin/src/MonitorPage.ts +0 -200
  73. package/admin/src/OnlinePlugins.ts +0 -101
  74. package/admin/src/PermField.ts +0 -80
  75. package/admin/src/PluginsPage.ts +0 -27
  76. package/admin/src/VfsMenuBar.ts +0 -58
  77. package/admin/src/VfsPage.ts +0 -124
  78. package/admin/src/VfsTree.ts +0 -95
  79. package/admin/src/addFiles.ts +0 -59
  80. package/admin/src/api.ts +0 -246
  81. package/admin/src/dialog.ts +0 -203
  82. package/admin/src/index.css +0 -21
  83. package/admin/src/index.ts +0 -10
  84. package/admin/src/md.ts +0 -31
  85. package/admin/src/misc.ts +0 -141
  86. package/admin/src/react-app-env.d.ts +0 -1
  87. package/admin/src/reportWebVitals.ts +0 -15
  88. package/admin/src/setupTests.ts +0 -5
  89. package/admin/src/state.ts +0 -40
  90. package/admin/src/theme.ts +0 -37
  91. package/admin/tsconfig.json +0 -26
  92. package/admin/vite.config.ts +0 -32
  93. package/frontend/.DS_Store +0 -0
  94. package/frontend/.eslintrc +0 -8
  95. package/frontend/.gitignore +0 -23
  96. package/frontend/package.json +0 -51
  97. package/frontend/src/App.ts +0 -25
  98. package/frontend/src/Breadcrumbs.ts +0 -43
  99. package/frontend/src/BrowseFiles.ts +0 -141
  100. package/frontend/src/Head.ts +0 -45
  101. package/frontend/src/UserPanel.ts +0 -52
  102. package/frontend/src/api.ts +0 -78
  103. package/frontend/src/components.ts +0 -54
  104. package/frontend/src/dialog.css +0 -76
  105. package/frontend/src/dialog.ts +0 -105
  106. package/frontend/src/icons.ts +0 -46
  107. package/frontend/src/index.scss +0 -307
  108. package/frontend/src/index.ts +0 -10
  109. package/frontend/src/login.ts +0 -50
  110. package/frontend/src/menu.ts +0 -188
  111. package/frontend/src/misc.ts +0 -54
  112. package/frontend/src/options.ts +0 -52
  113. package/frontend/src/react-app-env.d.ts +0 -1
  114. package/frontend/src/reportWebVitals.ts +0 -15
  115. package/frontend/src/setupTests.ts +0 -5
  116. package/frontend/src/state.ts +0 -82
  117. package/frontend/src/useAuthorized.ts +0 -17
  118. package/frontend/src/useFetchList.ts +0 -144
  119. package/frontend/src/useTheme.ts +0 -23
  120. package/frontend/tsconfig.json +0 -26
  121. package/frontend/vite.config.ts +0 -21
  122. package/src/QuickZipStream.ts +0 -279
  123. package/src/ThrottledStream.ts +0 -98
  124. package/src/adminApis.ts +0 -161
  125. package/src/api.accounts.ts +0 -78
  126. package/src/api.auth.ts +0 -131
  127. package/src/api.file_list.ts +0 -102
  128. package/src/api.helpers.ts +0 -30
  129. package/src/api.monitor.ts +0 -106
  130. package/src/api.plugins.ts +0 -139
  131. package/src/api.vfs.ts +0 -182
  132. package/src/apiMiddleware.ts +0 -124
  133. package/src/block.ts +0 -35
  134. package/src/commands.ts +0 -122
  135. package/src/config.ts +0 -166
  136. package/src/connections.ts +0 -60
  137. package/src/const.ts +0 -57
  138. package/src/crypt.ts +0 -16
  139. package/src/debounceAsync.ts +0 -51
  140. package/src/events.ts +0 -6
  141. package/src/frontEndApis.ts +0 -17
  142. package/src/github.ts +0 -102
  143. package/src/index.ts +0 -53
  144. package/src/listen.ts +0 -220
  145. package/src/log.ts +0 -128
  146. package/src/middlewares.ts +0 -176
  147. package/src/misc.ts +0 -149
  148. package/src/pbkdf2.ts +0 -83
  149. package/src/perm.ts +0 -194
  150. package/src/plugins.ts +0 -342
  151. package/src/serveFile.ts +0 -104
  152. package/src/serveGuiFiles.ts +0 -95
  153. package/src/sse.ts +0 -29
  154. package/src/throttler.ts +0 -106
  155. package/src/update.ts +0 -67
  156. package/src/util-files.ts +0 -137
  157. package/src/util-generators.ts +0 -29
  158. package/src/util-http.ts +0 -29
  159. package/src/vfs.ts +0 -258
  160. package/src/watchLoad.ts +0 -75
  161. package/src/zip.ts +0 -69
package/src/vfs.js ADDED
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ // This file is part of HFS - Copyright 2021-2022, 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.cantReadStatusCode = exports.walkNode = exports.hasPermission = exports.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.urlToNode = exports.MIME_AUTO = exports.defaultPerms = void 0;
8
+ const promises_1 = __importDefault(require("fs/promises"));
9
+ const path_1 = require("path");
10
+ const micromatch_1 = require("micromatch");
11
+ const misc_1 = require("./misc");
12
+ const lodash_1 = __importDefault(require("lodash"));
13
+ const config_1 = require("./config");
14
+ const const_1 = require("./const");
15
+ const events_1 = __importDefault(require("./events"));
16
+ const perm_1 = require("./perm");
17
+ const misc_2 = require("./misc");
18
+ const WHO_ANYONE = true;
19
+ const WHO_NO_ONE = false;
20
+ const WHO_ANY_ACCOUNT = '*';
21
+ exports.defaultPerms = {
22
+ can_see: WHO_ANYONE,
23
+ can_read: WHO_ANYONE,
24
+ };
25
+ exports.MIME_AUTO = 'auto';
26
+ function inheritFromParent(parent, child) {
27
+ var _a;
28
+ for (const k of (0, misc_1.typedKeys)(exports.defaultPerms)) {
29
+ const v = parent[k];
30
+ if (v !== undefined)
31
+ (_a = child[k]) !== null && _a !== void 0 ? _a : (child[k] = v);
32
+ }
33
+ if (typeof parent.mime === 'object' && typeof child.mime === 'object')
34
+ lodash_1.default.defaults(child.mime, parent.mime);
35
+ else
36
+ child.mime || (child.mime = parent.mime);
37
+ return child;
38
+ }
39
+ async function urlToNode(url, ctx, parent = exports.vfs) {
40
+ var _a;
41
+ let initialSlashes = 0;
42
+ while (url[initialSlashes] === '/')
43
+ initialSlashes++;
44
+ let nextSlash = url.indexOf('/', initialSlashes);
45
+ const name = decodeURIComponent(url.slice(initialSlashes, nextSlash < 0 ? undefined : nextSlash));
46
+ if (!name)
47
+ return parent;
48
+ const rest = nextSlash < 0 ? '' : url.slice(nextSlash + 1, url.endsWith('/') ? -1 : undefined);
49
+ if ((0, misc_1.dirTraversal)(name) || /[\/]/.test(name)) {
50
+ if (ctx)
51
+ ctx.status = 418;
52
+ return;
53
+ }
54
+ const parents = parent.parents || []; // don't waste time cloning the array, as we won't keep intermediate nodes
55
+ const ret = {
56
+ isTemp: true,
57
+ url: (0, misc_1.enforceFinal)('/', parent.url || '') + name,
58
+ parents,
59
+ };
60
+ parents.push(parent);
61
+ inheritFromParent(parent, ret);
62
+ inheritMasks(ret, parent, name);
63
+ applyMasks(ret, parent, name);
64
+ // does the tree node have a child that goes by this name?
65
+ const sameName = !const_1.IS_WINDOWS ? (x) => x === name // easy
66
+ : (0, misc_2.with_)(name.toLowerCase(), lc => (x) => x.toLowerCase() === lc);
67
+ const child = (_a = parent.children) === null || _a === void 0 ? void 0 : _a.find(x => sameName(getNodeName(x)));
68
+ if (child) // yes
69
+ return urlToNode(rest, ctx, Object.assign(ret, child, { original: child }));
70
+ // not in the tree, we can see consider continuing on the disk
71
+ if (!parent.source)
72
+ return; // but then we need the current node to be linked to the disk, otherwise, we give up
73
+ let onDisk = name;
74
+ if (parent.rename) { // reverse the mapping
75
+ for (const [from, to] of Object.entries(parent.rename))
76
+ if (name === to) {
77
+ onDisk = from;
78
+ break; // found, search no more
79
+ }
80
+ ret.rename = renameUnderPath(parent.rename, name);
81
+ }
82
+ ret.source = (0, misc_1.enforceFinal)('/', parent.source) + onDisk;
83
+ if (parent.default)
84
+ inheritFromParent({ mime: { '*': exports.MIME_AUTO } }, ret);
85
+ if (rest)
86
+ return urlToNode(rest, ctx, ret);
87
+ if (ret.source)
88
+ try {
89
+ await promises_1.default.stat(ret.source);
90
+ } // check existence
91
+ catch (_b) {
92
+ return;
93
+ }
94
+ return ret;
95
+ }
96
+ exports.urlToNode = urlToNode;
97
+ exports.vfs = {};
98
+ (0, config_1.defineConfig)('vfs', {}).sub(data => exports.vfs = data);
99
+ function saveVfs() {
100
+ return (0, config_1.setConfig)({ vfs: lodash_1.default.cloneDeep(exports.vfs) }, true);
101
+ }
102
+ exports.saveVfs = saveVfs;
103
+ function getNodeName(node) {
104
+ return node.name
105
+ || node.source && (/^[a-zA-Z]:\\?$/.test(node.source) && node.source.slice(0, 2)
106
+ || (0, path_1.basename)(node.source)
107
+ || node.source)
108
+ || ''; // should happen only for root
109
+ }
110
+ exports.getNodeName = getNodeName;
111
+ async function nodeIsDirectory(node) {
112
+ return Boolean(!node.source || await (0, misc_1.isDirectory)(node.source));
113
+ }
114
+ exports.nodeIsDirectory = nodeIsDirectory;
115
+ function hasPermission(node, perm, ctx) {
116
+ var _a;
117
+ return matchWho((_a = node[perm]) !== null && _a !== void 0 ? _a : exports.defaultPerms[perm], ctx)
118
+ && (perm !== 'can_see' || hasPermission(node, 'can_read', ctx)); // for can_see you must also can_read
119
+ }
120
+ exports.hasPermission = hasPermission;
121
+ async function* walkNode(parent, ctx, depth = 0, prefixPath = '') {
122
+ var _a;
123
+ const { children, source } = parent;
124
+ if (children)
125
+ for (let idx = 0; idx < children.length; idx++) {
126
+ const child = children[idx];
127
+ yield* workItem({
128
+ ...child,
129
+ name: prefixPath ? (prefixPath + getNodeName(child)) : child.name
130
+ }, depth > 0 && await nodeIsDirectory(child).catch(() => false));
131
+ }
132
+ if (!source)
133
+ return;
134
+ try {
135
+ for await (const path of (0, misc_1.dirStream)(source, depth)) {
136
+ if (ctx === null || ctx === void 0 ? void 0 : ctx.req.aborted)
137
+ return;
138
+ const renamed = (_a = parent.rename) === null || _a === void 0 ? void 0 : _a[path];
139
+ yield* workItem({
140
+ name: prefixPath + (renamed || path),
141
+ source: (0, path_1.join)(source, path),
142
+ rename: renameUnderPath(parent.rename, path),
143
+ });
144
+ }
145
+ }
146
+ catch (e) {
147
+ console.debug('glob', source, e); // ENOTDIR, or lacking permissions
148
+ }
149
+ // item will be changed, so be sure to pass a temp node
150
+ async function* workItem(item, recur = false) {
151
+ const name = getNodeName(item);
152
+ // we basename for depth>0 where we already have the rest of the path in the parent's url, and would be duplicated
153
+ const virtualBasename = (0, path_1.basename)(name);
154
+ const url = (0, misc_1.enforceFinal)('/', parent.url || '') + virtualBasename;
155
+ Object.assign(item, {
156
+ isTemp: true,
157
+ url,
158
+ parents: [...parent.parents || [], parent],
159
+ });
160
+ inheritFromParent(parent, item);
161
+ applyMasks(item, parent, virtualBasename);
162
+ if (ctx && !hasPermission(item, 'can_see', ctx))
163
+ return;
164
+ yield item;
165
+ if (!recur)
166
+ return;
167
+ inheritMasks(item, parent, virtualBasename);
168
+ yield* walkNode(item, ctx, depth - 1, name + '/');
169
+ }
170
+ }
171
+ exports.walkNode = walkNode;
172
+ function applyMasks(item, parent, virtualBasename) {
173
+ const { masks } = parent;
174
+ if (!masks)
175
+ return;
176
+ for (const k in masks)
177
+ if (k.startsWith('**/') && (0, micromatch_1.isMatch)(virtualBasename, k.slice(3))
178
+ || !k.includes('/') && (0, micromatch_1.isMatch)(virtualBasename, k))
179
+ Object.assign(item, masks[k]);
180
+ }
181
+ function inheritMasks(item, parent, virtualBasename) {
182
+ const { masks } = parent;
183
+ if (!masks)
184
+ return;
185
+ const o = {};
186
+ for (const k in masks)
187
+ if (k.startsWith('**/'))
188
+ o[k.slice(3)] = masks[k];
189
+ else if (k.startsWith(virtualBasename + '/'))
190
+ o[k.slice(virtualBasename.length + 1)] = masks[k];
191
+ if (Object.keys(o).length)
192
+ item.masks = o;
193
+ }
194
+ function renameUnderPath(rename, path) {
195
+ if (!rename)
196
+ return rename;
197
+ const match = path + '/';
198
+ rename = Object.fromEntries(Object.entries(rename).map(([k, v]) => [k.startsWith(match) ? k.slice(match.length) : '', v]));
199
+ delete rename[''];
200
+ return lodash_1.default.isEmpty(rename) ? undefined : rename;
201
+ }
202
+ function matchWho(who, ctx) {
203
+ return who === WHO_ANYONE
204
+ || who === WHO_ANY_ACCOUNT && Boolean(ctx.state.account)
205
+ || Array.isArray(who) && (() => // check if I or any ancestor match `who`, but cache ancestors' usernames inside context state
206
+ (0, misc_1.getOrSet)(ctx.state, 'usernames', () => (0, perm_1.getCurrentUsernameExpanded)(ctx)).some((u) => who.includes(u)))();
207
+ }
208
+ function cantReadStatusCode(node) {
209
+ return node.can_read === false ? const_1.FORBIDDEN : const_1.UNAUTHORIZED;
210
+ }
211
+ exports.cantReadStatusCode = cantReadStatusCode;
212
+ events_1.default.on('accountRenamed', (from, to) => {
213
+ recur(exports.vfs);
214
+ saveVfs();
215
+ function recur(n) {
216
+ var _a;
217
+ replace(n.can_see);
218
+ replace(n.can_read);
219
+ if (n.masks)
220
+ Object.values(n.masks).forEach(recur);
221
+ (_a = n.children) === null || _a === void 0 ? void 0 : _a.forEach(recur);
222
+ }
223
+ function replace(a) {
224
+ if (!Array.isArray(a))
225
+ return;
226
+ for (let i = 0; i < a.length; i++)
227
+ if (a[i] === from)
228
+ a[i] = to;
229
+ }
230
+ });
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ // This file is part of HFS - Copyright 2021-2022, 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.watchLoad = void 0;
8
+ const fs_1 = require("fs");
9
+ const promises_1 = __importDefault(require("fs/promises"));
10
+ const yaml_1 = __importDefault(require("yaml"));
11
+ const misc_1 = require("./misc");
12
+ function watchLoad(path, parser, { failedOnFirstAttempt } = {}) {
13
+ let doing = false;
14
+ let watcher;
15
+ const debounced = (0, misc_1.debounceAsync)(load, 500, { leading: true });
16
+ let retry;
17
+ let saving;
18
+ let lastStats;
19
+ init().then(ok => ok || (failedOnFirstAttempt === null || failedOnFirstAttempt === void 0 ? void 0 : failedOnFirstAttempt()));
20
+ return {
21
+ unwatch() {
22
+ watcher === null || watcher === void 0 ? void 0 : watcher.close();
23
+ clearTimeout(retry);
24
+ watcher = undefined;
25
+ },
26
+ save(...args) {
27
+ return Promise.resolve(saving).then(() => // wait in case another is ongoing
28
+ saving = promises_1.default.writeFile(...args).finally(() => // save but also keep track of the current operation
29
+ saving = undefined)); // clear
30
+ }
31
+ };
32
+ async function init() {
33
+ try {
34
+ debounced().then();
35
+ watcher = (0, fs_1.watch)(path, () => {
36
+ if (!saving)
37
+ debounced().then();
38
+ });
39
+ return true; // used actually just by the first invocation
40
+ }
41
+ catch (e) {
42
+ retry = setTimeout(init, 3000); // manual watching until watch is successful
43
+ }
44
+ }
45
+ async function load() {
46
+ if (doing)
47
+ return;
48
+ doing = true;
49
+ let data;
50
+ try {
51
+ try { // I've seen watch() firing 'change' without any change, so we'll check if any change is detectable before going on
52
+ const stats = await promises_1.default.stat(path);
53
+ if (stats.mtimeMs === (lastStats === null || lastStats === void 0 ? void 0 : lastStats.mtimeMs))
54
+ return;
55
+ lastStats = stats;
56
+ data = await (0, misc_1.readFileBusy)(path);
57
+ console.debug('loaded', path);
58
+ }
59
+ catch (e) {
60
+ if (e.code === 'EPERM')
61
+ console.error("missing permissions on file", path); // warn user, who could be clueless about this problem
62
+ return; // ignore read errors
63
+ }
64
+ if (path.endsWith('.yaml'))
65
+ data = yaml_1.default.parse(data);
66
+ await parser(data);
67
+ }
68
+ finally {
69
+ doing = false;
70
+ }
71
+ }
72
+ }
73
+ exports.watchLoad = watchLoad;
package/src/zip.js ADDED
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ // This file is part of HFS - Copyright 2021-2022, 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.zipStreamFromFolder = void 0;
8
+ const vfs_1 = require("./vfs");
9
+ const misc_1 = require("./misc");
10
+ const QuickZipStream_1 = require("./QuickZipStream");
11
+ const fs_1 = require("fs");
12
+ const promises_1 = __importDefault(require("fs/promises"));
13
+ const config_1 = require("./config");
14
+ const path_1 = require("path");
15
+ const serveFile_1 = require("./serveFile");
16
+ async function zipStreamFromFolder(node, ctx) {
17
+ ctx.status = 200;
18
+ ctx.mime = 'zip';
19
+ const name = (0, vfs_1.getNodeName)(node);
20
+ ctx.attachment((name || 'archive') + '.zip');
21
+ const filter = (0, misc_1.pattern2filter)(String(ctx.query.search || ''));
22
+ const { list } = ctx.query;
23
+ const walker = !list ? (0, vfs_1.walkNode)(node, ctx, Infinity)
24
+ : (async function* () {
25
+ for await (const el of String(list).split('*')) { // we are using * as separator because it cannot be used in a file name and doesn't need url encoding
26
+ const subNode = await (0, vfs_1.urlToNode)(el, ctx, node);
27
+ if (!subNode || !(0, vfs_1.hasPermission)(subNode, 'can_read', ctx))
28
+ continue;
29
+ if (await (0, vfs_1.nodeIsDirectory)(subNode)) { // a directory needs to walked
30
+ yield* (0, vfs_1.walkNode)(subNode, ctx, Infinity, el + '/');
31
+ continue;
32
+ }
33
+ let folder = (0, path_1.dirname)(el);
34
+ folder = folder === '.' ? '' : folder + '/';
35
+ yield { ...subNode, name: folder + (0, vfs_1.getNodeName)(subNode) }; // reflect relative path in archive, otherwise way may have name-clashes
36
+ }
37
+ })();
38
+ const mappedWalker = (0, misc_1.filterMapGenerator)(walker, async (el) => {
39
+ const { source } = el;
40
+ const name = (0, vfs_1.getNodeName)(el);
41
+ if (!source || ctx.req.aborted || !filter(name))
42
+ return;
43
+ try {
44
+ const st = await promises_1.default.stat(source);
45
+ if (!st || !st.isFile())
46
+ return;
47
+ return {
48
+ path: name,
49
+ size: st.size,
50
+ ts: st.mtime || st.ctime,
51
+ mode: st.mode,
52
+ sourcePath: source,
53
+ getData: () => (0, fs_1.createReadStream)(source, { start: 0, end: Math.max(0, st.size - 1) })
54
+ };
55
+ }
56
+ catch (_a) { }
57
+ });
58
+ const zip = new QuickZipStream_1.QuickZipStream(mappedWalker);
59
+ const time = 1000 * zipSeconds.get();
60
+ const size = await zip.calculateSize(time);
61
+ ctx.response.length = size;
62
+ const range = (0, serveFile_1.getRange)(ctx, size); // keep var size as ctx.response.length won't preserve a NaN
63
+ if (ctx.status >= 400)
64
+ return;
65
+ if (range)
66
+ zip.applyRange(range.start, range.end);
67
+ ctx.body = zip;
68
+ ctx.req.on('close', () => zip.destroy());
69
+ ctx.state.archive = 'zip';
70
+ }
71
+ exports.zipStreamFromFolder = zipStreamFromFolder;
72
+ const zipSeconds = (0, config_1.defineConfig)('zip_calculate_size_for_seconds', 1);
package/admin/.DS_Store DELETED
Binary file
package/admin/.eslintrc DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "react-app",
3
- "rules": {
4
- "no-mixed-operators": 0,
5
- "no-ex-assign": 0,
6
- "no-throw-literal": 0
7
- }
8
- }
package/admin/.gitignore DELETED
@@ -1,23 +0,0 @@
1
- # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
-
3
- # dependencies
4
- /node_modules
5
- /.pnp
6
- .pnp.js
7
-
8
- # testing
9
- /coverage
10
-
11
- # production
12
- /build
13
-
14
- # misc
15
- .DS_Store
16
- .env.local
17
- .env.development.local
18
- .env.test.local
19
- .env.production.local
20
-
21
- npm-debug.log*
22
- yarn-debug.log*
23
- yarn-error.log*
@@ -1,67 +0,0 @@
1
- {
2
- "name": "@hfs/admin",
3
- "private": true,
4
- "proxy": "http://localhost",
5
- "scripts": {
6
- "start": "vite",
7
- "build": "tsc && vite build",
8
- "preview": "vite preview",
9
- "test-dep": "npm audit --production"
10
- },
11
- "dependencies": {
12
- "@emotion/react": "^11.10.0",
13
- "@hfs/mui-grid-form": "*",
14
- "@hfs/shared": "*",
15
- "@emotion/styled": "^11.10.0",
16
- "@mui/icons-material": "^5.8.4",
17
- "@mui/lab": "^5.0.0-alpha.94",
18
- "@mui/material": "^5.10.0",
19
- "@mui/x-data-grid": "^5.15.1",
20
- "js-sha512": "^0.8.0",
21
- "react": "^18.2.0",
22
- "react-dom": "^18.2.0",
23
- "react-router-dom": "^6.2.1",
24
- "react-window": "^1.8.6",
25
- "tssrp6a": "^3.0.0",
26
- "valtio": "^1.2.9",
27
- "immer": "^9.0.15",
28
- "web-vitals": "^2.1.4"
29
- },
30
- "devDependencies": {
31
- "@types/node": "^16.11.21",
32
- "@types/react": "^18.0.15",
33
- "@types/react-dom": "^18.0.6",
34
- "@types/react-window": "^1.8.5",
35
- "@types/react-virtualized-auto-sizer": "^1.0.1",
36
- "vite": "^3.0.0",
37
- "vite-plugin-babel-import": "github:rejetto/vite-plugin-babel-import"
38
- },
39
- "eslintConfig": {
40
- "extends": [
41
- "react-app",
42
- "react-app/jest"
43
- ],
44
- "overrides": [
45
- {
46
- "files": [
47
- "*.ts"
48
- ],
49
- "rules": {
50
- "no-mixed-operators": "off"
51
- }
52
- }
53
- ]
54
- },
55
- "browserslist": {
56
- "production": [
57
- ">0.2%",
58
- "not dead",
59
- "not op_mini all"
60
- ],
61
- "development": [
62
- "last 1 chrome version",
63
- "last 1 firefox version",
64
- "last 1 safari version"
65
- ]
66
- }
67
- }
@@ -1,92 +0,0 @@
1
- import { createElement as h, useEffect, useRef, useState } from 'react'
2
- import { BoolField, Form, MultiSelectField } from '@hfs/mui-grid-form'
3
- import { Box, Button } from '@mui/material'
4
- import { apiCall } from './api'
5
- import { alertDialog } from './dialog'
6
- import { isEqualLax, modifiedSx } from './misc'
7
- import { Account, account2icon } from './AccountsPage'
8
- import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
9
-
10
- interface FormProps { account: Account, groups: string[], done: (username: string)=>void, close: ()=>void }
11
- export default function AccountForm({ account, done, groups, close }: FormProps) {
12
- const [values, setValues] = useState<Account & { password?: string, password2?: string }>(account)
13
- const [belongsOptions, setBelongOptions] = useState<string[]>([])
14
- useEffect(() => {
15
- setValues(account)
16
- setBelongOptions(groups.filter(x => x !== account.username ))
17
- ref.current?.querySelector('input')?.focus()
18
- }, [JSON.stringify(account)]) //eslint-disable-line
19
- const add = !account.username
20
- const group = !values.hasPassword
21
- const ref = useRef<HTMLFormElement>()
22
- return h(Form, {
23
- formRef: ref,
24
- values,
25
- set(v, k) {
26
- setValues({ ...values, [k]: v })
27
- },
28
- addToBar: [
29
- h(Button, { onClick: close, sx: { ml: 2 } }, "Close"),
30
- h(Box, { flex:1 }),
31
- account2icon(values, { fontSize: 'large', sx: { p: 1 }})
32
- ],
33
- fields: [
34
- { k: 'username', label: group ? 'Group name' : undefined, autoComplete: 'off', required: true, xl: group ? 12 : 4,
35
- getError: v => v !== account.username && apiCall('get_account', { username: v }).then(() => "already used", () => false),
36
- },
37
- !group && { k: 'password', md: 6, xl: 4, type: 'password', autoComplete: 'new-password', required: add,
38
- label: add ? "Password" : "Change password"
39
- },
40
- !group && { k: 'password2', md: 6, xl: 4, type: 'password', autoComplete: 'new-password', label: 'Repeat password',
41
- getError: (x, { values }) => (x||'') !== (values.password||'') && "Enter same password" },
42
- { k: 'ignore_limits', comp: BoolField, xl: 6,
43
- helperText: values.ignore_limits ? "Speed limits don't apply to this account" : "Speed limits apply to this account" },
44
- { k: 'admin', comp: BoolField, xl: 6, fromField: (v:boolean) => v||null, label: "Permission to access Admin interface",
45
- helperText: "To access THIS interface you are using right now",
46
- ...account.adminActualAccess && { value: true, disabled: true, helperText: "This permission is inherited" },
47
- },
48
- { k: 'belongs', comp: MultiSelectField, label: "Inherits from", options: belongsOptions,
49
- helperText: "Specify groups to inherit permissions from."
50
- + (belongsOptions.length ? '' : " There are no groups available, create one first.")
51
- },
52
- { k: 'redirect', helperText: "If you want this account to be redirected to a specific folder/address at login time" },
53
- ],
54
- onError: alertDialog,
55
- save: {
56
- sx: modifiedSx( !isEqualLax(values, account)),
57
- async onClick() {
58
- const { password='', password2, adminActualAccess, ...withoutPassword } = values
59
- const { username } = values
60
- if (add) {
61
- await apiCall('add_account', withoutPassword)
62
- if (password)
63
- try { await apiNewPassword(username, password) }
64
- catch(e) {
65
- apiCall('del_account', { username }).then() // best effort, don't wait
66
- throw e
67
- }
68
- done(username)
69
- return alertDialog("Account created", 'success')
70
- }
71
- await apiCall('set_account', {
72
- username: account.username,
73
- changes: withoutPassword,
74
- })
75
- if (password)
76
- await apiNewPassword(username, password)
77
- done(username)
78
- return alertDialog("Account modified", 'success')
79
- }
80
- }
81
- })
82
- }
83
-
84
- async function apiNewPassword(username: string, password: string) {
85
- const srp6aNimbusRoutines = new SRPRoutines(new SRPParameters())
86
- const res = await createVerifierAndSalt(srp6aNimbusRoutines, username, password)
87
- return apiCall('change_srp_others', { username, salt: String(res.s), verifier: String(res.v) }).catch(e => {
88
- if (e.code !== 406) // 406 = server was configured to support clear text authentication
89
- throw e
90
- return apiCall('change_password_others', { username, newPassword: password }) // unencrypted version
91
- })
92
- }