config.allowed_referer

Massimo Melina committed Mar 13, 2022 at 20:27 UTC 0c4dd9093848b8ec46affaa38354a34f74cbac98
5 files changed +18 -1
README.md
+1
@@ -204,6 +204,7 @@ Supported entries are:
204 - `https_port` listen on a specific port. Default is 443.
205 - `cert` use this file for https certificate. Minimum to start https is to give a cert and a private_key. Default is none.
206 - `private_key` use this file for https private key. Default is none.
207 +- `allowed_referer` you can decide what domains can link to your files. Wildcards supported. Default is any.
208 - `block` a list of rules that will block connections. At the moment only `ip` rules are supported. E.g.:
209 ```
210 block:
admin/src/ConfigPage.ts
+1
@@ -72,6 +72,7 @@ export default function ConfigPage() {
72 },
73 { k: 'accounts', comp: StringField, label: "Accounts file" },
74 { k: 'open_browser_at_start', comp: BoolField },
75 + { k: 'allowed_referer', placeholder: "any", helperText: "Leave empty to allow any", },
76 { k: 'zip_calculate_size_for_seconds', comp: NumberField, md: 6, label: "Calculate ZIP size for seconds",
77 helperText: "If time is not enough, the browser will not show download percentage" },
78 { k: 'mime', comp: StringStringField,
server/src/middlewares.ts
+9 -1
@@ -3,7 +3,7 @@
3 import compress from 'koa-compress'
4 import Koa from 'koa'
5 import session from 'koa-session'
6 -import { ADMIN_URI, BUILD_TIMESTAMP, SESSION_DURATION } from './const'
6 +import { ADMIN_URI, BUILD_TIMESTAMP, FORBIDDEN, SESSION_DURATION } from './const'
7 import Application from 'koa'
8 import { FRONTEND_URI } from './const'
9 import { cantReadStatusCode, hasPermission, urlToNode } from './vfs'
@@ -17,6 +17,7 @@ import { getAccount, getCurrentUsername } from './perm'
17 import { getConfig, subscribeConfig } from './config'
18 import { getConnections, socket2connection, updateConnection } from './connections'
19 import { Socket } from 'net'
20 +import { isMatch } from 'micromatch'
21
22 export const gzipper = compress({
23 threshold: 2048,
@@ -57,6 +58,13 @@ export const serveGuiAndSharedFiles: Koa.Middleware = async (ctx, next) => {
58 const { path } = ctx
59 if (ctx.body)
60 return next()
61 + const allowedRef = getConfig('allowed_referer')
62 + if (allowedRef) {
63 + const ref = /\/\/([^:/]+)/.exec(ctx.get('referer'))?.[1] // extract host from url
64 + if (ref && ref !== ctx.get('host')?.split(':')[0] // automatic accept if referer is basically the hosting domain
65 + && !isMatch(ref, allowedRef))
66 + return ctx.status = FORBIDDEN
67 + }
68 if (path.startsWith(FRONTEND_URI))
69 return serveFrontendPrefixed(ctx,next)
70 if (path+'/' === ADMIN_URI)
tests/config.yaml
+1
@@ -1,3 +1,4 @@
1 +allowed_referer: x.com
2 vfs:
3 mime:
4 "*.jpg|*.png|*.mp3|*.txt": auto
tests/test.ts
+6
@@ -61,8 +61,13 @@ describe('basics', () => {
61 it('zip.head', req('/f1/?get=zip', { empty:true, length:13074 }, { method:'HEAD' }) )
62 it('zip.alfa is forbidden', req('/protectFromAbove/child/?get=zip&list=alfa.txt*renamed', { empty: true, length:138 }, { method:'HEAD' }))
63 it('login', reqApi('login', { username, password }, 406)) // by default, we don't support clear-text login
64 +
65 + it('referer', req('/f1/page/gpl.png', 403, {
66 + headers: { Referer: 'https://some-website.com/try-to-trick/x.com/' }
67 + }))
68 })
69
70 +/*
71 let cookie:any
72 describe('after-login', () => {
73 before(req(API+'login', (data, res) => Boolean(cookie = res.headers['set-cookie']), {
@@ -74,6 +79,7 @@ describe('after-login', () => {
79 headers: { cookie },
80 })(done))
81 })
82 +*/
83
84 type Tester = number
85 | ((data: any, fullResponse: any) => boolean | Error)