blocked ips: added comment and expiration

Massimo Melina committed Oct 4, 2023 at 00:00 UTC 3dc5dec477823f23b3ef466a388c443602723225
2 files changed +23 -7
admin/src/OptionsPage.ts
+7 -4
@@ -154,7 +154,7 @@ export default function OptionsPage() {
154 { k: 'admin_net', comp: NetmaskField, label: "Admin-panel accessible from", placeholder: "any address",
155 helperText: h(Fragment, {}, "IP address of browser machine. ", h(WildcardsSupported))
156 },
157 - { k: 'mime', comp: ArrayField, label: false, reorder: true, prepend: true,
157 + { k: 'mime', comp: ArrayField, label: false, reorder: true, prepend: true, sm: 12,
158 fields: [
159 { k: 'k', label: "File mask", $width: 1, $column: {
160 renderCell: ({ value, id }: any) => h('code', {},
@@ -169,9 +169,12 @@ export default function OptionsPage() {
169 toField: x => Object.entries(x || {}).map(([k,v]) => ({ k, v })),
170 fromField: x => Object.fromEntries(x.map((row: any) => [row.k, row.v])),
171 },
172 - { k: 'block', label: "Blocked IPs", multiline: true, minRows:3, helperText: h(Fragment, {}, "Enter an IP address for each line. ", h(WildcardsSupported)),
173 - fromField: (all:string) => all.split('\n').map(s => s.trim()).filter(Boolean).map(ip => ({ ip })),
174 - toField: (all: any) => !Array.isArray(all) ? '' : all.map(x => x?.ip).filter(Boolean).join('\n')
172 + { k: 'block', label: false, comp: ArrayField, prepend: true, sm: 12,
173 + fields: [
174 + { k: 'ip', label: "Blocked IP", sm: 6, helperText: h(WildcardsSupported) },
175 + { k: 'expire', $type: 'dateTime', minDate: new Date(), sm: 6, helperText: "Leave empty for no expiration" },
176 + { k: 'comment' },
177 + ],
178 },
179 { k: 'server_code', comp: TextEditorField, sm: 12, getError: v => try_(() => new Function(v) && null, e => e.message),
180 helperText: md(`This code works similarly to [a plugin](${REPO_URL}blob/main/dev-plugins.md) (with some limitations)`)
src/block.ts
+16 -3
@@ -2,14 +2,18 @@
2
3 import { defineConfig } from './config'
4 import { getConnections, normalizeIp } from './connections'
5 -import { makeNetMatcher, onlyTruthy } from './misc'
5 +import { makeNetMatcher, MINUTE, onlyTruthy } from './misc'
6 import { Socket } from 'net'
7
8 -interface BlockingRule { ip: string }
8 +interface BlockingRule { ip: string, comment?: string, expire?: Date }
9
10 const block = defineConfig('block', [] as BlockingRule[], rules => {
11 + const now = new Date()
12 const ret = !Array.isArray(rules) ? []
12 - : onlyTruthy(rules.map(rule => makeNetMatcher(rule.ip, true)))
13 + : onlyTruthy(rules.map(rule => {
14 + rule.expire &&= new Date(rule.expire)
15 + return !(rule.expire! > now) && makeNetMatcher(rule.ip, true)
16 + }))
17 // reapply new block to existing connections
18 for (const { socket, ip } of getConnections())
19 applyBlock(socket, ip)
@@ -20,3 +24,12 @@ export function applyBlock(socket: Socket, ip=normalizeIp(socket.remoteAddress||
24 if (ip && block.compiled().find(rule => rule(ip)))
25 return socket.destroy()
26 }
27 +
28 +setInterval(() => { // twice a minute, check if any block has expired
29 + const now = new Date()
30 + const next = block.get().filter(x => !x.expire || x.expire > now)
31 + const n = block.get().length - next.length
32 + if (!n) return
33 + console.log("blocking rules:", n, "expired")
34 + block.set(next)
35 +}, MINUTE/2)