mirror of
https://github.com/pockethost/pockethost.git
synced 2025-11-23 22:15:49 +00:00
feat(pockethost): add IP and hostname rate limiters
This commit is contained in:
parent
f91d1cf001
commit
262da3e455
@ -53,6 +53,7 @@
|
||||
"node-fetch": "^3.3.2",
|
||||
"node-os-utils": "^1.3.7",
|
||||
"pocketbase": "^0.21.3",
|
||||
"rate-limiter-flexible": "^8.1.0",
|
||||
"semver": "^7.6.3",
|
||||
"tail": "^2.2.6",
|
||||
"tsx": "^4.20.3",
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
import express from 'express'
|
||||
import { RateLimiterMemory } from 'rate-limiter-flexible'
|
||||
import { Logger } from 'src/common'
|
||||
|
||||
const getClientIp = (req: express.Request): string | undefined => {
|
||||
const cf = req.headers['cf-connecting-ip'] || req.headers['true-client-ip']
|
||||
if (cf) return Array.isArray(cf) ? cf[0] : cf
|
||||
|
||||
const xff = req.headers['x-forwarded-for']
|
||||
const xffStr = Array.isArray(xff) ? xff.join(',') : xff
|
||||
if (typeof xffStr === 'string') {
|
||||
const ip = xffStr.split(',')?.[0]?.trim()
|
||||
if (ip) return ip
|
||||
}
|
||||
|
||||
const xri = req.headers['x-real-ip']
|
||||
if (xri) return Array.isArray(xri) ? xri[0] : xri
|
||||
|
||||
return req.ip || req.socket?.remoteAddress
|
||||
}
|
||||
|
||||
// Middleware factory to create a rate limiting middleware
|
||||
export const createRateLimiterMiddleware = (logger: Logger) => {
|
||||
const { dbg, warn } = logger.create(`RateLimiter`)
|
||||
dbg(`Creating`)
|
||||
|
||||
const ipRateLimiter = new RateLimiterMemory({
|
||||
points: 1000,
|
||||
duration: 60 * 60,
|
||||
})
|
||||
|
||||
const hostnameRateLimiter = new RateLimiterMemory({
|
||||
points: 10000,
|
||||
duration: 60 * 60,
|
||||
})
|
||||
|
||||
return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
const ip = getClientIp(req)
|
||||
if (!ip) {
|
||||
warn(`Could not determine IP address`)
|
||||
return next()
|
||||
}
|
||||
|
||||
const hostname = req.hostname
|
||||
// dbg(`Request from ${ip} for host ${hostname}`)
|
||||
|
||||
try {
|
||||
const ipResult = await ipRateLimiter.consume(ip)
|
||||
dbg(`IP points remaining for ${ip}: ${ipResult.remainingPoints}`)
|
||||
} catch (rateLimiterRes: any) {
|
||||
const retryAfter = Math.ceil(rateLimiterRes.msBeforeNext / 1000)
|
||||
warn(`IP rate limit exceeded for ${ip} on host ${hostname}. Retry after ${retryAfter} seconds`)
|
||||
res.set('Retry-After', String(retryAfter))
|
||||
res.status(429).send(`Too Many Requests: retry after ${retryAfter} seconds`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const hostnameResult = await hostnameRateLimiter.consume(hostname)
|
||||
dbg(`Hostname points remaining for ${hostname}: ${hostnameResult.remainingPoints}`)
|
||||
next()
|
||||
} catch (rateLimiterRes: any) {
|
||||
const retryAfter = Math.ceil(rateLimiterRes.msBeforeNext / 1000)
|
||||
warn(`Hostname rate limit exceeded for ${hostname} by IP ${ip}. Retry after ${retryAfter} seconds`)
|
||||
res.set('Retry-After', String(retryAfter))
|
||||
res.status(429).send(`Too Many Requests: retry after ${retryAfter} seconds`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,6 +21,7 @@ import { createProxyMiddleware } from 'http-proxy-middleware'
|
||||
import https from 'https'
|
||||
import { createIpWhitelistMiddleware } from './cidr'
|
||||
import { createVhostProxyMiddleware } from './createVhostProxyMiddleware'
|
||||
import { createRateLimiterMiddleware } from './rate-limiter'
|
||||
|
||||
export type FirewallOptions = {
|
||||
logger: Logger
|
||||
@ -82,6 +83,7 @@ export const firewall = async ({ logger }: FirewallOptions) => {
|
||||
|
||||
// Use the IP blocker middleware
|
||||
app.use(createIpWhitelistMiddleware(IPCIDR_LIST()))
|
||||
app.use(createRateLimiterMiddleware(logger))
|
||||
|
||||
forEach(hostnameRoutes, (target, host) => {
|
||||
app.use(createVhostProxyMiddleware(host, target, IS_DEV(), logger))
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@ -253,6 +253,9 @@ importers:
|
||||
pocketbase:
|
||||
specifier: ^0.21.3
|
||||
version: 0.21.5
|
||||
rate-limiter-flexible:
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.0
|
||||
semver:
|
||||
specifier: ^7.6.3
|
||||
version: 7.6.3
|
||||
@ -3606,6 +3609,9 @@ packages:
|
||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
rate-limiter-flexible@8.1.0:
|
||||
resolution: {integrity: sha512-J+4xBdVboibP1h0Imn4nFoCLT+UM9Os9vJaWaRWkLsQxS7jrhLJeLlmzP5hyCEsLwtgFIIY5KcWiJGyyVTMaKg==}
|
||||
|
||||
raw-body@2.5.2:
|
||||
resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@ -7550,6 +7556,8 @@ snapshots:
|
||||
|
||||
range-parser@1.2.1: {}
|
||||
|
||||
rate-limiter-flexible@8.1.0: {}
|
||||
|
||||
raw-body@2.5.2:
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user