Skip to content

Commit

Permalink
admin/internet: "Accept requests only using domain"
Browse files Browse the repository at this point in the history
  • Loading branch information
rejetto committed Dec 6, 2023
1 parent 232cd8e commit 4cb1f23
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 20 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,9 @@ While this project focuses on ease of use, we care about security.

Some actions you can take for improved security:
- use https, better if using a proper certificate, even free with [Letsencrypt](https://letsencrypt.org/).
- have a domain (ddns is ok too), start vhosting plugin, configure your domain, enable "Block requests that are not using any of the domains above"
- have a domain (ddns is ok too), configure it in "Internet" page, and enable "Accept requests only using domain"
- install rejetto/antidos plugin
- start antibrute plugin (but it's started by default)
- ensure "antibrute" plugin is running
- disable "unprotected admin on localhost"

## Hidden features
Expand Down
36 changes: 28 additions & 8 deletions admin/src/InternetPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,16 @@ export default function InternetPage() {
.then(() => alertDialog("Domain seems ok", 'success'))
}, "Check"),
),
)
),
h(ConfigForm<{ force_base_url: boolean }>, {
keys: ['force_base_url'],
saveOnChange: true,
form: {
fields: [
{ k: 'force_base_url', comp: BoolField, label: "Accept requests only using domain (and localhost)" }
]
},
})
)
}

Expand Down Expand Up @@ -251,7 +260,7 @@ export default function InternetPage() {
catch { mapPort(HIGHER_PORT, '') }
toast("Port forwarded, now verify again", 'success')
retry()
})
})
const cfg = await apiCall('get_config', { only: [CFG.geo_enable, CFG.geo_allow] })
const { close } = alertDialog(h(Box, {}, msg + "Possible causes:", h('ul', {},
cfg[CFG.geo_enable] && cfg[CFG.geo_allow] != null && h('li', {}, "You may be blocking a country from where the test is performed"),
Expand Down Expand Up @@ -345,22 +354,29 @@ function TitleCard({ title, icon, color, children }: { title: ReactNode, icon?:
)))
}

type FormRest<T> = Omit<FormProps<T>, 'values' | 'set' | 'save'>
function ConfigForm<T=any>({ keys, form, ...rest }: Partial<FormRest<T>> & { keys: (keyof T)[], form: ((values: T) => FormRest<T>) }) {
type FormRest<T> = Omit<FormProps<T>, 'values' | 'set' | 'save'> & Partial<Pick<FormProps<T>, 'save'>>
function ConfigForm<T=any>({ keys, form, saveOnChange, ...rest }: Partial<FormRest<T>> & {
keys: (keyof T)[],
form: FormRest<T> | ((values: T) => FormRest<T>),
saveOnChange?: boolean
}) {
const config = useApiEx('get_config', { only: keys })
const [values, setValues] = useState<any>(config.data)
useEffect(() => setValues((v: any) => config.data || v), [config.data])
const modified = values && !_.isEqual(values, config.data)
useEffect(() => {
if (modified && saveOnChange) save()
}, [modified])
if (!values)
return config.element
const formProps = form(values)
const modified = !_.isEqual(values, config.data)
const formProps = _.isFunction(form) ? form(values) : form
return h(Form, {
values,
set(v, k) {
setValues((was: any) => ({ ...was, [k]: v }))
},
save: {
onClick: () => apiCall('set_config', { values }).then(config.reload),
save: saveOnChange ? false : {
onClick: save,
sx: modifiedSx(modified),
},
...Array.isArray(formProps) ? { fields: formProps } : formProps,
Expand All @@ -376,4 +392,8 @@ function ConfigForm<T=any>({ keys, form, ...rest }: Partial<FormRest<T>> & { key
...rest.addToBar||[],
],
})

function save() {
return apiCall('set_config', { values }).then(config.reload)
}
}
4 changes: 2 additions & 2 deletions src/block.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt

import { defineConfig } from './config'
import { getConnections, normalizeIp } from './connections'
import { disconnect, getConnections, normalizeIp } from './connections'
import { makeNetMatcher, MINUTE, onlyTruthy } from './misc'
import { Socket } from 'net'

Expand All @@ -22,7 +22,7 @@ const block = defineConfig('block', [] as BlockingRule[], rules => {

export function applyBlock(socket: Socket, ip=normalizeIp(socket.remoteAddress||'')) {
if (ip && block.compiled().find(rule => rule(ip)))
return socket.destroy()
return disconnect(socket)
}

setInterval(() => { // twice a minute, check if any block has expired
Expand Down
6 changes: 6 additions & 0 deletions src/connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,9 @@ export function updateConnection(conn: Connection, change: Partial<Connection>)
Object.assign(conn, change)
events.emit('connectionUpdated', conn, change)
}

export function disconnect(what: Context | Socket) {
if ('socket' in what)
what = what.socket
return what.destroy()
}
4 changes: 2 additions & 2 deletions src/geo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { stat, rename, unlink } from 'node:fs/promises'
import { IP2Location } from 'ip2location-nodejs'
import _ from 'lodash'
import { Middleware } from 'koa'
import { updateConnection } from './connections'
import { disconnect, updateConnection } from './connections'

const ip2location = new IP2Location()
const enabled = defineConfig(CFG.geo_enable, false)
Expand All @@ -22,7 +22,7 @@ export const geoFilter: Middleware = async (ctx, next) => {
const country = connection.country ??= await ip2country(ctx.ip)
updateConnection(connection, { country })
if (country ? list.get().includes(country) !== allow.get() : !allowUnknown.get())
return ctx.socket.destroy()
return disconnect(ctx)
}
return next()
}
Expand Down
3 changes: 2 additions & 1 deletion src/listen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ let httpsSrv: undefined | http.Server & ServerExtra

const openBrowserAtStart = defineConfig('open_browser_at_start', !DEV)

export const baseUrl = defineConfig('base_url', '')
export const baseUrl = defineConfig('base_url', '',
x => /\/\/[^\/]+/.exec(x)?.[1]) // compiled is host only

export function getBaseUrlOrDefault() {
return baseUrl.get() || defaultBaseUrl.get()
Expand Down
13 changes: 8 additions & 5 deletions src/middlewares.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt

import compress from 'koa-compress'
import Koa, { Middleware } from 'koa'
import Koa from 'koa'
import { ADMIN_URI, API_URI, BUILD_TIMESTAMP, DEV,
HTTP_FORBIDDEN, HTTP_NOT_FOUND, HTTP_FOOL, HTTP_UNAUTHORIZED, HTTP_BAD_REQUEST, HTTP_METHOD_NOT_ALLOWED,
} from './const'
import { FRONTEND_URI } from './const'
import { statusCodeForMissingPerm, nodeIsDirectory, urlToNode, vfs, walkNode, VfsNode, getNodeName } from './vfs'
import { DAY, asyncGeneratorToReadable, dirTraversal, filterMapGenerator, isLocalHost, stream2string, tryJson,
splitAt } from './misc'
import { DAY, asyncGeneratorToReadable, dirTraversal, filterMapGenerator, isLocalHost, stream2string, tryJson, splitAt
} from './misc'
import { zipStreamFromFolder } from './zip'
import { serveFile, serveFileNode } from './serveFile'
import { serveGuiFiles } from './serveGuiFiles'
import mount from 'koa-mount'
import { Readable } from 'stream'
import { applyBlock } from './block'
import { accountCanLogin, getAccount } from './perm'
import { socket2connection, updateConnection, normalizeIp } from './connections'
import { socket2connection, updateConnection, normalizeIp, disconnect } from './connections'
import basicAuth from 'basic-auth'
import { invalidSessions, srpCheck } from './auth'
import { basename, dirname } from 'path'
Expand All @@ -33,6 +33,7 @@ import { app } from './index'

const forceHttps = defineConfig('force_https', true)
const ignoreProxies = defineConfig('ignore_proxies', false)
const forceBaseUrl = defineConfig('force_base_url', false)
export const sessionDuration = defineConfig('session_duration', Number(process.env.SESSION_DURATION) || DAY/1000,
v => v * 1000)

Expand Down Expand Up @@ -196,6 +197,8 @@ export const someSecurity: Koa.Middleware = async (ctx, next) => {
catch {
return ctx.status = HTTP_FOOL
}
if (forceBaseUrl.get() && !isLocalHost(ctx) && ctx.host === baseUrl.compiled())
return disconnect(ctx)
return next()
}

Expand Down Expand Up @@ -248,7 +251,7 @@ export const paramsDecoder: Koa.Middleware = async (ctx, next) => {
await next()
}

export const sessionMiddleware: Middleware = (ctx, next) =>
export const sessionMiddleware: Koa.Middleware = (ctx, next) =>
session({
key: 'hfs_$id' + (ctx.secure ? '' : '_http'), // once https cookie is created, http cannot
signed: true,
Expand Down

0 comments on commit 4cb1f23

Please sign in to comment.