plugins: frontend will now receive vfs_path config values adjusted with both reverse-proxy-path and root
Massimo Melina committed
Mar 23, 2025 at 15:49 UTC
5b8eb3782b328878179df40f9b9f600dc800b8c8
4 files changed
+36
-7
dev-plugins.md
+5
-1
@@ -210,7 +210,11 @@ Based on `type`, other properties are supported:
210
- `vfs_path` path to VFS
211
- `folders: boolean` set false to forbid selection of folders. Default is true.
212
- `files: boolean | string` set force to forbid selection of files. If you set a string, it will be used as a file-mask.
213
- E.g. `*.jpg|*.png` Default is true.
213
+ E.g. `*.jpg|*.png` Default is true.
214
+ Note: the path you configure inside the admin-panel may differ to what the frontend sees, because:
215
+ - a "root" is applied to the specific host/domain;
216
+ - a reverse-proxy may add something in front.
217
+ For this reason, if your config is marked with `frontend: true`, the value that will be actually sent to the frontend may be adjusted if necessary.
218
- `username`
219
- `groups: undefined | boolean` true if you want only groups, false if you want only users. Default is undefined.
220
- `multiple: boolean` if you set this to true, the field will allow the selection of multiple accounts,
src/middlewares.ts
+1
-1
@@ -141,7 +141,7 @@ declare module "koa" {
141
interface DefaultState {
142
params: Record<string, any>
143
account?: Account // user logged in
144
- revProxyPath: string
144
+ revProxyPath: string // must not have final slash
145
connection: Connection
146
}
147
}
src/roots.ts
+7
-3
@@ -1,5 +1,8 @@
1
import { defineConfig, getConfig } from './config'
2
-import { ADMIN_URI, API_URI, Callback, CFG, isLocalHost, join, makeMatcher, removeStarting, SPECIAL_URI, try_ } from './misc'
2
+import {
3
+ ADMIN_URI, API_URI, Callback, CFG, isLocalHost, join, makeMatcher, removeStarting, SPECIAL_URI, try_,
4
+ enforceFinal, enforceStarting
5
+} from './misc'
6
import Koa from 'koa'
7
import { disconnect } from './connections'
8
import { baseUrl } from './listen'
@@ -8,7 +11,7 @@ import _ from 'lodash'
11
export const roots = defineConfig(CFG.roots, {} as { [hostMask: string]: string }, map => {
12
const list = Object.keys(map)
13
const matchers = list.map(hostMask => makeMatcher(hostMask))
11
- const values = Object.values(map)
14
+ const values = Object.values(map).map(x => enforceFinal('/', enforceStarting('/', x)))
15
return (host: string) => values[matchers.findIndex(m => m(host))]
16
})
17
const forceAddress = defineConfig(CFG.force_address, false)
@@ -29,7 +32,7 @@ export const rootsMiddleware: Koa.Middleware = (ctx, next) =>
32
if (referer && try_(() => new URL(referer).pathname.startsWith(ctx.state.revProxyPath + ADMIN_URI))) return // exclude apis for admin-panel
33
}
34
if (_.isEmpty(roots.get())) return
32
- const root = roots.compiled()?.(ctx.host)
35
+ const root = ctx.state.root = roots.compiled()?.(ctx.host)
36
if (!ctx.state.skipFilters && forceAddress.get())
37
if (root === undefined && !isLocalHost(ctx) && ctx.host !== baseUrl.compiled()) {
38
disconnect(ctx, forceAddress.key())
@@ -51,5 +54,6 @@ export const rootsMiddleware: Koa.Middleware = (ctx, next) =>
54
declare module "koa" {
55
interface DefaultState {
56
originalPath: string // before roots is applied
57
+ root?: string
58
}
59
}
\ No newline at end of file
src/serveGuiFiles.ts
+23
-2
@@ -12,7 +12,8 @@ import { refresh_session } from './api.auth'
12
import { ApiError } from './apiMiddleware'
13
import { join, extname } from 'path'
14
import {
15
- CFG, debounceAsync, formatBytes, FRONTEND_OPTIONS, isPrimitive, newObj, objSameKeys, onlyTruthy, parseFileContent
15
+ CFG, debounceAsync, formatBytes, FRONTEND_OPTIONS, isPrimitive, newObj, objSameKeys, onlyTruthy, parseFileContent,
16
+ enforceStarting
17
} from './misc'
18
import { favicon, title } from './adminApis'
19
import { customHtml, getAllSections, getSection } from './customHtml'
@@ -84,7 +85,7 @@ async function treatIndex(ctx: Koa.Context, filesUri: string, body: string) {
85
const plugins = Object.fromEntries(onlyTruthy(mapPlugins((pl,name) => {
86
let configs = newObj(getPluginConfigFields(name), (v, k, skip) =>
87
!v.frontend ? skip() :
87
- (pluginsConfig.get()?.[name]?.[k] ?? pl.getData().config?.[k]?.defaultValue)
88
+ adjustValueByConfig(pluginsConfig.get()?.[name]?.[k], pl.getData().config?.[k])
89
)
90
configs = getPluginInfo(name).onFrontendConfig?.(configs) || configs
91
return !_.isEmpty(configs) && [name, configs]
@@ -150,6 +151,26 @@ async function treatIndex(ctx: Koa.Context, filesUri: string, body: string) {
151
return getSection('bottom') + all
152
return all // unchanged
153
})
154
+
155
+ function adjustValueByConfig(v: any, cfg: any) {
156
+ v ??= cfg.defaultValue
157
+ const {type} = cfg
158
+ if (v && type === 'vfs_path') {
159
+ v = enforceStarting('/', v)
160
+ const { root } = ctx.state
161
+ if (root)
162
+ if (v.startsWith(root))
163
+ v = v.slice(root.length - 1)
164
+ else
165
+ return
166
+ if (ctx.state.revProxyPath)
167
+ v = ctx.state.revProxyPath + v
168
+ }
169
+ else if (type === 'array' && Array.isArray(v))
170
+ v = v.map(x => objSameKeys(x, (xv, xk) => adjustValueByConfig(xv, cfg.fields[xk])))
171
+ return v
172
+ }
173
+
174
}
175
176
function serializeCss(v: any) {