hfs 0.55.0-alpha2 → 0.55.0-beta4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
- "author": "Ovidiu",
2
+ "author": "<0x1g3n>",
3
3
  "version": 1.0,
4
- "hfs_version": "0.53.0",
4
+ "hfs_version": "0.54.0",
5
5
  "translate": {
6
6
  "Select": "Selectează",
7
7
  "n_files": "{n,plural,one{# fișier} other{# fișiere}}",
@@ -10,12 +10,12 @@
10
10
  "select_count": "{n,plural, one{# selectat} other{# selectate}}",
11
11
  "filter_placeholder": "Tastează aici pentru a filtra lista de mai jos (caută după cuvinte cheie)",
12
12
  "Select some files": "Selectează câteva fișiere",
13
- "zip_checkboxes": "Folosește căsuțele de selectare pentru a alege fișierele, apoi poți folosi Zip din nou",
14
- "zip_tooltip_selected": "Descarcă elementele selectate ca un singur fișier zip",
15
- "zip_tooltip_whole": "Descarcă întreaga listă (nefiltrată) ca un singur fișier zip. Dacă selectezi câteva elemente, doar acelea vor fi descărcate.",
16
- "zip_confirm_search": "Ești sigur că vrei să descarci TOATE rezultatele acestei căutări sub formă de arhivă ZIP?",
17
- "zip_confirm_folder": "Ești sigur că vrei să descarci ÎNTREGUL folder sub formă de arhivă ZIP?",
18
- "select_tooltip": "Selecția se aplică pentru \"Zip\" și \"Șterge\" (când sunt disponibile), dar poți, de asemenea, filtra lista",
13
+ "zip_checkboxes": "Folosește căsuțele de selectare pentru a alege fișierele, apoi poți folosi ZIP din nou",
14
+ "zip_tooltip_selected": "Descarcă elementele selectate sub un singur fișier .ZIP",
15
+ "zip_tooltip_whole": "Descarcă întreaga listă (nefiltrată) ca un singur fișier .ZIP. Dacă selectezi câteva elemente, doar acelea vor fi descărcate.",
16
+ "zip_confirm_search": "Ești sigur că vrei să descarci TOATE rezultatele acestei căutări sub formă de arhivă .ZIP?",
17
+ "zip_confirm_folder": "Ești sigur că vrei să descarci ÎNTREGUL folder sub formă de arhivă .ZIP?",
18
+ "select_tooltip": "Selecția se aplică pentru \"ZIP\" și \"Șterge\" (când sunt disponibile), dar poți, de asemenea, filtra lista",
19
19
  "delete_hint": "Pentru a șterge, prima dată fă clic pe Selectează",
20
20
  "delete_confirm": "Ștergi {n,plural, one{# element} other{# elemente}}?",
21
21
  "delete_completed": "Ștergere: {n} completată",
@@ -24,7 +24,7 @@
24
24
  "Delete": "Șterge",
25
25
  "Options": "Opțiuni",
26
26
  "Search": "Căutare",
27
- "Zip": "Zip",
27
+ "Zip": "Arhivă ZIP",
28
28
  "search_msg": "Caută în acest folder și în sub-foldere",
29
29
  "Searching": "Căutare",
30
30
  "Searched": "Căutat",
@@ -33,13 +33,13 @@
33
33
  "stopped_before": "Oprit înainte de a găsi ceva",
34
34
  "empty_list": "Lista este goală. Nu este nimic aici",
35
35
  "filter_none": "Niciun rezultat pentru filtrul aplicat",
36
- "Admin-panel": "Panoul de administrare",
36
+ "Admin-panel": "Panoul Administratorului",
37
37
  "Login": "Autentificare",
38
- "Username": "Nume utilizator",
38
+ "Username": "Nume de utilizator",
39
39
  "Password": "Parolă",
40
40
  "login_untrusted": "Autentificare anulată: identitatea serverului nu poate fi de încredere",
41
- "login_bad_credentials": "Numele de utilizator sau parola sunt incorecte. Te rugăm să re-efectuezi logarea.",
42
- "login_bad_cookies": "Cookies nefuncționale - autentificare eșuată",
41
+ "login_bad_credentials": "Numele de utilizator sau parola sunt incorecte. Te rog să re-efectuezi logarea.",
42
+ "login_bad_cookies": "Cookie-uri nefuncționale - autentificare eșuată",
43
43
  "User panel": "Panoul utilizatorului",
44
44
  "Change password": "Schimbă parola",
45
45
  "enter_pass": "Introdu noua parolă",
@@ -65,8 +65,8 @@
65
65
  "Enter folder name": "Introdu numele folderului",
66
66
  "Successfully created": "Creat cu succes",
67
67
  "enter_folder": "Intră în folder",
68
- "folder_exists": "Există deja un folderul cu același nume",
69
- "Sort by:": "Sortați după: {by}",
68
+ "folder_exists": "Există deja un folder cu același nume",
69
+ "Sort by:": "Sortează după: {by}",
70
70
  "name": "nume",
71
71
  "extension": "extensie",
72
72
  "size": "dimensiune",
@@ -78,7 +78,7 @@
78
78
  "auto": "automat",
79
79
  "light": "luminos",
80
80
  "dark": "întunecat",
81
- "parent folder": "Folder sursă",
81
+ "parent folder": "Folder sursă (rădăcină)",
82
82
  "home": "acasă",
83
83
  "Continue": "Continuă",
84
84
  "Confirm": "Confirmă",
@@ -127,7 +127,7 @@
127
127
  "in_queue": "{n} în așteptare",
128
128
  "enter_comment": "Comentariu pentru {name}",
129
129
  "Comment": "Comentariu",
130
- "upload_dd_hint": "Poți încărca fișiere făcând drag&drop pe lista de fișiere",
130
+ "upload_dd_hint": "Poți încărca fișiere făcând drag&drop în lista cu fișierele deja existente",
131
131
  "Upload not available": "Încărcarea nu este disponibilă",
132
132
  "Cut": "Decupează",
133
133
  "n_items": "{n,plural, one{# element} other{# elemente}}",
@@ -161,6 +161,8 @@
161
161
  "Show details": "Arată detalii",
162
162
  "upload_conflict": "deja există",
163
163
  "Logged in": "Autentificat cu succes",
164
- "Logged out": "Deconectat cu succes"
164
+ "Logged out": "Deconectat cu succes",
165
+ "Cancel": "Anulează",
166
+ "allow_session_ip_change": "Permite schimbarea adresei IP pe durata sesiunilor de logare"
165
167
  }
166
168
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "author": "Mefistofell, SanokKule, ItsNeverLight",
3
3
  "version": 2.6,
4
- "hfs_version": "0.54.0",
4
+ "hfs_version": "0.55.0",
5
5
  "translate": {
6
6
  "Select": "Выбор",
7
7
  "n_files": "{n,plural, one{# файл} few{# файла} many{# файлов}}",
@@ -168,6 +168,7 @@
168
168
  "Logged out": "Выход выполнен",
169
169
  "Cancel": "Отмена",
170
170
  "allow_session_ip_change": "Разрешить смену IP в этой сессии",
171
- "focus_hint": "Набирая текст на клавиатуре, вы можете выполнять поиск и выбирать элементы из списка."
171
+ "focus_hint": "Набирая текст на клавиатуре, вы можете выполнять поиск и выбирать элементы из списка.",
172
+ "copy_links": "Копировать ссылки"
172
173
  }
173
174
  }
package/src/listen.js CHANGED
@@ -49,7 +49,7 @@ const persistence_1 = require("./persistence");
49
49
  let httpSrv;
50
50
  let httpsSrv;
51
51
  const openBrowserAtStart = (0, config_1.defineConfig)('open_browser_at_start', !const_1.DEV);
52
- exports.baseUrl = (0, config_1.defineConfig)('base_url', '', x => { var _a; return (_a = /(?<=\/\/)[^\/]+/.exec(x)) === null || _a === void 0 ? void 0 : _a[0]; }); // compiled is host only
52
+ exports.baseUrl = (0, config_1.defineConfig)(misc_1.CFG.base_url, '', x => { var _a; return (_a = /(?<=\/\/)[^\/]+/.exec(x)) === null || _a === void 0 ? void 0 : _a[0]; }); // compiled is host only
53
53
  async function getBaseUrlOrDefault() {
54
54
  return exports.baseUrl.get() || await nat_1.defaultBaseUrl.get();
55
55
  }
@@ -163,7 +163,7 @@ const considerHttps = (0, misc_1.debounceAsync)(async () => {
163
163
  events_1.default.emit('httpsReady');
164
164
  nat_1.defaultBaseUrl.proto = 'https';
165
165
  nat_1.defaultBaseUrl.port = (_f = getCurrentPort(httpsSrv)) !== null && _f !== void 0 ? _f : 0;
166
- });
166
+ }, { wait: 200 }); // give time to have key and cert ready
167
167
  exports.cert = (0, config_1.defineConfig)('cert', '');
168
168
  exports.privateKey = (0, config_1.defineConfig)('private_key', '');
169
169
  const httpsNeeds = [exports.cert, exports.privateKey];
@@ -180,7 +180,6 @@ for (const cfg of httpsNeeds) {
180
180
  httpsOptions[k] = '';
181
181
  unwatch = (0, watchLoad_1.watchLoad)(v, async (data) => {
182
182
  httpsOptions[k] = data;
183
- await (0, misc_1.wait)(500); // when a file is written, give some time for the other to be ready
184
183
  await considerHttps();
185
184
  }, { immediateFirst: true }).unwatch;
186
185
  await considerHttps();
@@ -201,8 +200,10 @@ function startServer(srv, { port, host }) {
201
200
  if (!srv)
202
201
  return resolve(0);
203
202
  try {
204
- if (port === const_1.PORT_DISABLED || !host && !await testIpV4()) // !host means ipV4+6, and if v4 port alone is busy we won't be notified of the failure, so we'll first test it on its own
203
+ if (port === const_1.PORT_DISABLED)
205
204
  return resolve(0);
205
+ if (!host && !await testIpV4()) // !host means ipV4+6, and if v4 port alone is busy we won't be notified of the failure, so we'll first test it on its own
206
+ throw srv.error;
206
207
  // from a few tests, this seems enough to support the expect-100 http/1.1 mechanism, at least with curl -T, not used by chrome|firefox anyway
207
208
  srv.on('checkContinue', (req, res) => srv.emit('request', req, res));
208
209
  port = await listen(host);
@@ -212,16 +213,16 @@ function startServer(srv, { port, host }) {
212
213
  }
213
214
  catch (e) {
214
215
  srv.error = String(e);
215
- console.error(srv.name, "couldn't listen on port", port, srv.error);
216
+ console.error(srv.name, `couldn't listen on port ${port}:`, srv.error);
216
217
  resolve(0);
217
218
  }
218
219
  });
219
220
  async function testIpV4() {
220
- const res = await listen('0.0.0.0');
221
- await new Promise(res => srv === null || srv === void 0 ? void 0 : srv.close(res));
221
+ const res = await listen('0.0.0.0', true);
222
+ await new Promise(res => srv === null || srv === void 0 ? void 0 : srv.close(res)); // close, if any, and wait
222
223
  return res > 0;
223
224
  }
224
- function listen(host) {
225
+ function listen(host, silence = false) {
225
226
  return new Promise(async (resolve, reject) => {
226
227
  srv === null || srv === void 0 ? void 0 : srv.on('error', onError).listen({ port, host }, () => {
227
228
  const ad = srv.address();
@@ -246,7 +247,8 @@ function startServer(srv, { port, host }) {
246
247
  srv.busy = (0, find_process_1.default)('port', port).then(res => res === null || res === void 0 ? void 0 : res.map(x => { var _a; return (0, misc_1.prefix)("Service", x.name === 'svchost.exe' && ((_a = x.cmd.split(x.name)[1]) === null || _a === void 0 ? void 0 : _a.trim())) || x.name; }).join(' + '), () => '');
247
248
  srv.error = `port ${port} busy: ${await srv.busy || "unknown process"}`;
248
249
  }
249
- console.error(srv.name, srv.error);
250
+ if (!silence)
251
+ console.error(srv.name, srv.error);
250
252
  resolve(0);
251
253
  }
252
254
  });
package/src/perm.js CHANGED
@@ -4,13 +4,14 @@ 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.changeSrpHelper = exports.accountCanLoginAdmin = exports.accountCanLogin = exports.accountHasPassword = exports.getFromAccount = exports.delAccount = exports.addAccount = exports.renameAccount = exports.normalizeUsername = exports.accountsConfig = exports.updateAccount = exports.createAdmin = exports.saveSrpInfo = exports.getAccount = exports.expandUsername = void 0;
7
+ exports.changeSrpHelper = exports.accountCanLoginAdmin = exports.accountCanLogin = exports.accountHasPassword = exports.getFromAccount = exports.delAccount = exports.addAccount = exports.renameAccount = exports.normalizeUsername = exports.accountsConfig = exports.updateAccount = exports.createAdmin = exports.saveSrpInfo = exports.getAccount = exports.ctxBelongsTo = exports.expandUsername = void 0;
8
8
  const lodash_1 = __importDefault(require("lodash"));
9
9
  const misc_1 = require("./misc");
10
10
  const config_1 = require("./config");
11
11
  const tssrp6a_1 = require("tssrp6a");
12
12
  const events_1 = __importDefault(require("./events"));
13
13
  const apiMiddleware_1 = require("./apiMiddleware");
14
+ const auth_1 = require("./auth");
14
15
  let accounts = {};
15
16
  // provides the username and all other usernames it inherits based on the 'belongs' attribute. Useful to check permissions
16
17
  function expandUsername(who) {
@@ -27,6 +28,12 @@ function expandUsername(who) {
27
28
  return ret;
28
29
  }
29
30
  exports.expandUsername = expandUsername;
31
+ // check if current username or any ancestor match the provided usernames
32
+ function ctxBelongsTo(ctx, usernames) {
33
+ return (0, misc_1.getOrSet)(ctx.state, 'usernames', () => expandUsername((0, auth_1.getCurrentUsername)(ctx))) // cache ancestors' usernames inside context state
34
+ .some((u) => usernames.includes(u));
35
+ }
36
+ exports.ctxBelongsTo = ctxBelongsTo;
30
37
  function getAccount(username, normalize = true) {
31
38
  if (normalize)
32
39
  username = normalizeUsername(username);
package/src/plugins.js CHANGED
@@ -48,6 +48,10 @@ const first_1 = require("./first");
48
48
  const frontEndApis_1 = require("./frontEndApis");
49
49
  const index_1 = require("./index");
50
50
  const block_1 = require("./block");
51
+ const lang_1 = require("./lang");
52
+ const i18n_1 = require("./i18n");
53
+ const perm_1 = require("./perm");
54
+ const auth_1 = require("./auth");
51
55
  exports.PATH = 'plugins';
52
56
  exports.DISABLING_SUFFIX = '-disabled';
53
57
  exports.STORAGE_FOLDER = 'storage';
@@ -129,7 +133,7 @@ function getPluginConfigFields(id) {
129
133
  exports.getPluginConfigFields = getPluginConfigFields;
130
134
  async function initPlugin(pl, morePassedToInit) {
131
135
  var _a;
132
- return Object.assign(pl, await ((_a = pl.init) === null || _a === void 0 ? void 0 : _a.call(pl, {
136
+ const res = await ((_a = pl.init) === null || _a === void 0 ? void 0 : _a.call(pl, {
133
137
  Const,
134
138
  require,
135
139
  getConnections: connections_1.getConnections,
@@ -140,8 +144,11 @@ async function initPlugin(pl, morePassedToInit) {
140
144
  notifyClient: frontEndApis_1.notifyClient,
141
145
  addBlock: block_1.addBlock,
142
146
  misc,
147
+ ctxBelongsTo: perm_1.ctxBelongsTo,
148
+ getCurrentUsername: auth_1.getCurrentUsername,
143
149
  ...morePassedToInit
144
- })));
150
+ }));
151
+ return Object.assign(pl, typeof res === 'function' ? { unload: res } : res);
145
152
  }
146
153
  const already = new Set();
147
154
  function warnOnce(msg) {
@@ -502,6 +509,9 @@ function watchPlugin(id, path) {
502
509
  }
503
510
  });
504
511
  },
512
+ async i18n(ctx) {
513
+ return (0, i18n_1.i18nFromTranslations)(await (0, lang_1.getLangData)(ctx));
514
+ },
505
515
  });
506
516
  const folder = (0, path_1.dirname)(module);
507
517
  const { sections, unwatch } = (0, customHtml_1.watchLoadCustomHtml)(folder);
package/src/serveFile.js CHANGED
@@ -41,7 +41,7 @@ async function serveFileNode(ctx, node) {
41
41
  : lodash_1.default.find(mime, (val, mask) => (0, misc_1.matches)(name, mask));
42
42
  if (allowedReferer.get()) {
43
43
  const ref = (_a = /\/\/([^:/]+)/.exec(ctx.get('referer'))) === null || _a === void 0 ? void 0 : _a[1]; // extract host from url
44
- if (ref && ref !== host() // automatically accept if referer is basically the hosting domain
44
+ if (ref && ref !== (0, misc_1.normalizeHost)(ctx.host) // automatically accept if referer is basically the hosting domain
45
45
  && !(0, misc_1.matches)(ref, allowedReferer.get()))
46
46
  return ctx.status = const_1.HTTP_FORBIDDEN;
47
47
  }
@@ -54,10 +54,6 @@ async function serveFileNode(ctx, node) {
54
54
  await serveFile(ctx, source || '', mimeString);
55
55
  if (await maxDownloadsPerAccount(ctx) === undefined) // returning false will not execute other limits
56
56
  await maxDownloads(ctx) || await maxDownloadsPerIp(ctx);
57
- function host() {
58
- const s = ctx.host;
59
- return s[0] === '[' ? s.slice(1, s.indexOf(']')) : s === null || s === void 0 ? void 0 : s.split(':')[0];
60
- }
61
57
  }
62
58
  exports.serveFileNode = serveFileNode;
63
59
  const mimeCfg = (0, config_1.defineConfig)('mime', {}, obj => {
@@ -19,6 +19,7 @@ const lodash_1 = __importDefault(require("lodash"));
19
19
  const config_1 = require("./config");
20
20
  const lang_1 = require("./lang");
21
21
  const upload_1 = require("./upload");
22
+ const size1024 = (0, config_1.defineConfig)(misc_1.CFG.size_1024, false, x => misc_1.formatBytes.k = x ? 1024 : 1000); // we both configure formatBytes, and also provide a compiled version (number instead of boolean)
22
23
  const splitUploads = (0, config_1.defineConfig)(misc_1.CFG.split_uploads, 0);
23
24
  exports.logGui = (0, config_1.defineConfig)(misc_1.CFG.log_gui, false);
24
25
  lodash_1.default.each(misc_1.FRONTEND_OPTIONS, (v, k) => (0, config_1.defineConfig)(k, v)); // define default values
@@ -102,6 +103,7 @@ async function treatIndex(ctx, filesUri, body) {
102
103
  prefixUrl: ctx.state.revProxyPath,
103
104
  dontOverwriteUploading: upload_1.dontOverwriteUploading.get(),
104
105
  splitUploads: splitUploads.get(),
106
+ kb: size1024.compiled(),
105
107
  forceTheme: (0, plugins_1.mapPlugins)(p => lodash_1.default.isString(p.isTheme) ? p.isTheme : undefined).find(Boolean),
106
108
  customHtml: lodash_1.default.omit((0, customHtml_1.getAllSections)(), ['top', 'bottom', 'htmlHead', 'style']),
107
109
  ...(0, misc_1.newObj)(misc_1.FRONTEND_OPTIONS, (v, k) => (0, config_1.getConfig)(k)),
package/src/update.js CHANGED
@@ -127,7 +127,10 @@ async function update(tagOrUrl = '') {
127
127
  const bin = process.execPath;
128
128
  const binPath = (0, path_1.dirname)(bin);
129
129
  const binFile = 'hfs' + (const_1.IS_WINDOWS ? '.exe' : ''); // currently running bin could have been renamed
130
- const newBinFile = 'new-' + binFile;
130
+ let newBinFile = binFile;
131
+ do {
132
+ newBinFile = 'new-' + newBinFile;
133
+ } while ((0, fs_1.existsSync)((0, path_1.join)(binPath, newBinFile)));
131
134
  plugins_1.pluginsWatcher.pause();
132
135
  try {
133
136
  await (0, misc_1.unzip)(updateSource, path => (0, path_1.join)(binPath, path === binFile ? newBinFile : path));
package/src/upload.js CHANGED
@@ -19,6 +19,7 @@ const comments_1 = require("./comments");
19
19
  const lodash_1 = __importDefault(require("lodash"));
20
20
  const events_1 = __importDefault(require("./events"));
21
21
  const promises_1 = require("fs/promises");
22
+ const expiringCache_1 = require("./expiringCache");
22
23
  exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)('delete_unfinished_uploads_after', 86400);
23
24
  exports.minAvailableMb = (0, config_1.defineConfig)('min_available_mb', 100);
24
25
  exports.dontOverwriteUploading = (0, config_1.defineConfig)('dont_overwrite_uploading', true);
@@ -35,7 +36,7 @@ function setUploadMeta(path, ctx) {
35
36
  });
36
37
  }
37
38
  // stay sync because we use this function with formidable()
38
- const diskSpaceCache = {};
39
+ const diskSpaceCache = (0, expiringCache_1.expiringCache)(3000); // invalidate shortly
39
40
  const openFiles = new Set();
40
41
  function uploadWriter(base, baseUri, path, ctx) {
41
42
  let fullPath = '';
@@ -65,13 +66,10 @@ function uploadWriter(base, baseUri, path, ctx) {
65
66
  while ((closestVfsNode === null || closestVfsNode === void 0 ? void 0 : closestVfsNode.parent) && !closestVfsNode.original)
66
67
  closestVfsNode = closestVfsNode.parent; // if it's not original, it surely has a parent
67
68
  const statDir = closestVfsNode.source;
68
- if (!Object.hasOwn(diskSpaceCache, statDir)) {
69
- const c = diskSpaceCache[statDir] = (0, util_os_1.getDiskSpaceSync)(statDir);
70
- if (!c)
71
- throw 'miss';
72
- setTimeout(() => delete diskSpaceCache[statDir], 3000); // invalidate shortly
73
- }
74
- const { free } = diskSpaceCache[statDir];
69
+ const res = diskSpaceCache.try(statDir, () => (0, util_os_1.getDiskSpaceSync)(statDir));
70
+ if (!res)
71
+ throw 'miss';
72
+ const { free } = res;
75
73
  if (typeof free !== 'number' || isNaN(free))
76
74
  throw '';
77
75
  if (reqSize > free - (min || 0))
package/src/util-os.js CHANGED
@@ -76,17 +76,18 @@ async function runCmd(cmd, args = [], options = {}) {
76
76
  return (stderr || stdout).replace(/\r/g, '');
77
77
  }
78
78
  exports.runCmd = runCmd;
79
+ // returns pid-to-name object
79
80
  async function getWindowsServicePids() {
80
81
  var _a;
81
82
  const res = await runCmd('tasklist /svc /fo csv');
82
83
  const parsed = new csv_1.default().parse(res);
83
84
  const no = (_a = parsed === null || parsed === void 0 ? void 0 : parsed[1]) === null || _a === void 0 ? void 0 : _a[2];
84
- return parsed.slice(2).filter(x => x[2] !== no).map(x => Number(x[1]));
85
+ return Object.fromEntries(parsed.slice(2).filter(x => x[2] !== no).map(x => [x[1], x[2]]));
85
86
  }
86
87
  exports.RUNNING_AS_SERVICE = const_1.IS_WINDOWS && getWindowsServicePids().then(x => {
87
- const ret = x.includes(node_process_1.pid);
88
+ const ret = x[node_process_1.pid] || x[node_process_1.ppid];
88
89
  if (ret)
89
- console.log("running as service");
90
+ console.log("running as service", ret);
90
91
  return ret;
91
92
  }, e => {
92
93
  console.log("couldn't determine if we are running as a service");
package/src/vfs.js CHANGED
@@ -224,13 +224,8 @@ function statusCodeForMissingPerm(node, perm, ctx, assign = true) {
224
224
  }
225
225
  cur = who;
226
226
  } while (1);
227
- if (Array.isArray(who)) {
228
- const arr = who; // shut up ts
229
- // check if I or any ancestor match `who`, but cache ancestors' usernames inside context state
230
- const some = (0, misc_1.getOrSet)(ctx.state, 'usernames', () => (0, perm_1.expandUsername)((0, auth_1.getCurrentUsername)(ctx)))
231
- .some((u) => arr.includes(u));
232
- return some ? 0 : const_1.HTTP_UNAUTHORIZED;
233
- }
227
+ if (Array.isArray(who))
228
+ return (0, perm_1.ctxBelongsTo)(ctx, who) ? 0 : const_1.HTTP_UNAUTHORIZED;
234
229
  return typeof who === 'boolean' ? (who ? 0 : const_1.HTTP_FORBIDDEN)
235
230
  : who === misc_1.WHO_ANY_ACCOUNT ? ((0, auth_1.getCurrentUsername)(ctx) ? 0 : const_1.HTTP_UNAUTHORIZED)
236
231
  : (0, misc_1.throw_)(Error(`invalid permission: ${perm}=${(0, misc_1.try_)(() => JSON.stringify(who))}`));