plugins: event checkVfsPermission

Massimo Melina committed Aug 11, 2025 at 13:42 UTC f69af56c94f5d27bad2165430b0614b0594def74
3 files changed +39 -20
dev-plugins.md
+25 -18
@@ -1,31 +1,30 @@
1 -# For plug-in makers
1 +# For plugin makers
2
3 -If the information you are searching for is not in this document, [please ask](https://github.com/rejetto/hfs/discussions).
3 +If the information you are searching for is not in this document, [feel free to ask](https://github.com/rejetto/hfs/discussions).
4
5 -A plug-in is a folder with a `plugin.js` file in it. To install a plugin you just copy the folder into the `plugins` folder.
5 +A plugin for HFS is a folder that contains a `plugin.js` file. To install a plugin you just copy the folder into the `plugins` folder.
6 You will find `plugins` folder near `config.yaml`, and then in `USER_FOLDER/.hfs` for Linux and macOS, or near `hfs.exe` on Windows.
7
8 -Plug-ins can be hot-swapped, and to some extent can be edited without restarting the server.
8 +plugins can be hot-swapped, and to some extent can be edited without restarting the server.
9
10 -Each plug-in has access to the same set of features.
11 -Normally you'll have a plug-in that's a theme, and another that's a firewall,
12 -but nothing is preventing a single plug-in from doing both tasks.
10 +Normally you'll have a plugin that's a theme, and another that's a firewall,
11 +but nothing is preventing a single plugin from doing both tasks.
12
13 ## Development environment
14
16 -The simplest way to develop is to create your folder inside the .hfs/plugins folder, and work there.
15 +The simplest way to develop is to create your folder inside the `.hfs/plugins` folder, and work there.
16 Each time you make a change, you'll see it reflected in the running server.
17 This is probably the easiest form to start with.
18
19 A neater way is to keep it both in the form of github repo and installed plugin.
20 If you want to do so, have a folder with your github repo in it, *outside* your `.hfs` folder.
22 -As you'll see in the [[Publish your plug-in]] section, you should keep your files inside the `dist` subfolder.
23 -Then you'll need to link it inside the plugins folder.
24 -If you go in your .hfs/plugins folder on linux and mac, and enter
21 +As you'll see in the [[Publish your plugin]] section, you should keep your files inside the `dist` subfolder.
22 +Then you'll need to link it inside the `plugins` folder.
23 +If you go in your `.hfs/plugins` folder on linux and mac, and enter
24
25 ln -s /PATH_TO_YOUR_REPO/dist MY_PLUGIN_NAME
26
28 -On Windows it may be something like
27 +On Windows, the command would be something like
28
29 mklink /d C:\path\to\hfs\plugins\my_plugin C:\my_code\my_plugin\dist
30
@@ -34,7 +33,8 @@ This allows you to continue editing your repo and be ready to commit changes.
33
34 ## Backend / Frontend
35
37 -Plugins can run both in backend (the server) and frontend (the browser). Frontend files reside in the "public" folder, while all the rest is backend.
36 +plugins can have a part running in the backend (the server) and a part running in the frontend (the browser).
37 +Frontend files reside in the "public" folder, while all the rest is backend.
38
39 ## System icons
40
@@ -280,9 +280,8 @@ The `api` object you get as parameter of the `init` contains the following:
280 - `storageDir: string` folder where a plugin is supposed to store run-time data. This folder is preserved during
281 an update of the plugin, while the rest could be deleted.
282
283 -- `events` this is the main events emitter used by HFS.
284 - These are backend side events, not to be confused with frontend ones. It's not the standard EventEmitter class,
285 - and the API is slightly different.
283 +- `events` this is the main events emitter used by HFS. These are backend-side events, not to be confused with frontend ones.
284 + It's not the standard EventEmitter class, but the API is mostly the same.
285
286 - `events.on(name: string, listener: Callback): Callback`
287
@@ -732,6 +731,13 @@ This section is still partially documented, and you may need to have a look at t
731 to let the default behavior while getting the content of the list, return a function, and it will be called for each
732 entry, passed as first parameter (an object of standard class fs.Dirent), and when the list is over it will be called
733 with a boolean, true if the list is completed and false if it was aborted
734 +- `checkVfsPermission` called when a vfs permission is checked
735 + - parameters: { node, perm, who, ctx }
736 + - `perm: string` is the permission we are checking for
737 + - `node: VfsNode` is the node on which the permission is checked
738 + - `who: Who` is like `node[perm]`, that is the configured permission on the node,
739 + but without references to other permissions or the object form, as they have already been translated
740 + - return: an http error as number >= 400, or 0 or undefined
741
742 # Notifications (backend-to-frontend events)
743
@@ -818,7 +824,7 @@ If you use a library for the browser, you'll have to keep it in the "public" fol
824 If you want to use a module for node.js, just include "node_modules" folder (not in "public" folder).
825 You can decide if you want to use some building system/transpiler, but you'll have to set it up yourself.
826
821 -## Publish your plug-in
827 +## Publish your plugin
828
829 While you may just put a zip on any website, that would require manual installation.
830 If you want to appear in the Admin-panel, for easier finding and installation, please do as follows.
@@ -1045,4 +1051,5 @@ If you want to override a text regardless of the language, use the special langu
1051 - HFS.textSortCompare
1052 - 12.8 (v0.57.10)
1053 - api.onServer
1048 - - HFS.elementToEntry
\ No newline at end of file
1054 + - HFS.elementToEntry
1055 + - backend event: checkVfsPermission
\ No newline at end of file
src/listen.ts
+8 -2
@@ -41,6 +41,7 @@ export function getHttpsWorkingPort() {
41 }
42
43 const commonServerOptions: http.ServerOptions = { requestTimeout: 0 }
44 +// these are properties that can be assigned to the server object
45 const commonServerAssign = { headersTimeout: 30_000, timeout: MINUTE } // 'headersTimeout' is not recognized by type lib, and 'timeout' is not effective when passed in parameters
46
47 const readyToListen = Promise.all([ storedMap.isOpening(), events.once('app') ])
@@ -103,9 +104,14 @@ const considerHttps = debounceAsync(async () => {
104 defaultBaseUrl.port = getCurrentPort(httpSrv) ?? 0
105 let port = httpsPortCfg.get()
106 try {
106 - const moreOptions = Object.assign({}, ...await events.emitAsync('httpsServerOptions') || [])
107 + const moreOptions = Object.assign({}, ...await events.emitAsync('httpsServerOptions') || []) // emitAsync returns an array of objects
108 httpsSrv = Object.assign(
108 - https.createServer(port === PORT_DISABLED ? {} : { ...commonServerOptions, key: httpsOptions.private_key, cert: httpsOptions.cert, ...moreOptions }, app.callback()),
109 + https.createServer(port === PORT_DISABLED ? {} : {
110 + ...commonServerOptions,
111 + key: httpsOptions.private_key,
112 + cert: httpsOptions.cert,
113 + ...moreOptions,
114 + }, app.callback()),
115 { name: 'https' },
116 commonServerAssign
117 )
src/vfs.ts
+6
@@ -266,6 +266,12 @@ export function statusCodeForMissingPerm(node: VfsNode, perm: keyof VfsPerms, ct
266 }
267 cur = who
268 } while (1)
269 + const eventName = 'checkVfsPermission'
270 + if (events.anyListener(eventName)) {
271 + const first = Number(events.emit(eventName, { who, node, perm, ctx })?.find(Boolean))
272 + if (first >= 400)
273 + return first
274 + }
275
276 if (Array.isArray(who))
277 return ctxBelongsTo(ctx, who) ? 0 : HTTP_UNAUTHORIZED