main
ts 55 lines 2.96 KB
Raw
1 import { defineConfig } from './config'
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'
9 import _ from 'lodash'
10
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))
14 const values = Object.values(map).map(x => enforceFinal('/', enforceStarting('/', x.replace(/\/{2,}/g, '/'))))
15 return (host: string) => values[matchers.findIndex(m => m(host))]
16 })
17 const forceAddress = defineConfig(CFG.force_address, false)
18
19 export const rootsMiddleware: Koa.Middleware = (ctx, next) =>
20 (() => {
21 if (!ctx.path) // it was once reported "Cannot read properties of null (reading 'startsWith')" but I can't reproduce it, and it shouldn't happen anyway
22 return disconnect(ctx, 'no path') // let's assume that such requests can't be served anyway. This will leave a trace with an ip, anyway
23 ctx.state.originalPath = ctx.path
24 let params: undefined | typeof ctx.state.params | typeof ctx.query // undefined if we are not going to work on api parameters
25 if (ctx.path.startsWith(SPECIAL_URI)) { // special uris should be excluded...
26 if (!ctx.path.startsWith(API_URI)) return // ...unless it's an api
27 params = ctx.state.params || ctx.query // for api we'll translate params
28 changeUriParams(v => removeStarting(ctx.state.revProxyPath, v)) // this removal must be done before adding the root; this operation doesn't conceptually belong to "roots", and it may be placed in different middleware, but it's convenient to do it here
29 const { referer } = ctx.headers
30 if (referer && try_(() => new URL(referer).pathname.startsWith(ctx.state.revProxyPath + ADMIN_URI))) return // exclude apis for admin-panel
31 }
32 if (_.isEmpty(roots.get())) return
33 const root = ctx.state.root = roots.compiled()(ctx.host)
34 if (!ctx.state.skipFilters && forceAddress.get()
35 && root === undefined && !isLocalHost(ctx) && ctx.host !== baseUrl.compiled())
36 return disconnect(ctx, forceAddress.key()) // returning truthy will not call next
37 if (!root || root === '/') return // no transformation is required
38 changeUriParams(v => join(root, v))
39 if (!params)
40 ctx.path = join(root, ctx.path)
41
42 function changeUriParams(cb: Callback<string, string>) {
43 if (!params) return
44 for (const [k, v] of Object.entries(params))
45 if (k.startsWith('uri'))
46 params[k] = Array.isArray(v) ? v.map(cb) : _.isString(v) ? cb(v) : v
47 }
48 })() || next()
49
50 declare module "koa" {
51 interface DefaultState {
52 originalPath: string // before roots is applied
53 root?: string
54 }
55 }