Harden admin events and document deployment
This commit is contained in:
@@ -43,6 +43,7 @@ const state = {
|
||||
}
|
||||
let bitcoinPriceCache = null
|
||||
const adminEventClients = new Set()
|
||||
const rateBuckets = new Map()
|
||||
|
||||
const publicApiEnabled = () => appMode !== 'admin'
|
||||
const adminApiEnabled = () => appMode !== 'public'
|
||||
@@ -55,6 +56,32 @@ const json = (res, status, body) => {
|
||||
res.end(JSON.stringify(body))
|
||||
}
|
||||
|
||||
const rateLimit = (req, res) => {
|
||||
const method = String(req.method || 'GET').toUpperCase()
|
||||
if (!req.url?.startsWith('/api/') || method === 'GET') return false
|
||||
const ip = String(req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown').split(',')[0].trim()
|
||||
const pathname = new URL(req.url, `http://${req.headers.host}`).pathname
|
||||
const key = `${ip}:${pathname}`
|
||||
const now = Date.now()
|
||||
const windowMs = 60_000
|
||||
const limit = pathname.includes('/access/check') ? 120 : 30
|
||||
const bucket = rateBuckets.get(key) || { count: 0, resetAt: now + windowMs }
|
||||
if (now > bucket.resetAt) {
|
||||
bucket.count = 0
|
||||
bucket.resetAt = now + windowMs
|
||||
}
|
||||
bucket.count += 1
|
||||
rateBuckets.set(key, bucket)
|
||||
if (rateBuckets.size > 2000) {
|
||||
for (const [bucketKey, value] of rateBuckets) {
|
||||
if (now > value.resetAt) rateBuckets.delete(bucketKey)
|
||||
}
|
||||
}
|
||||
if (bucket.count <= limit) return false
|
||||
json(res, 429, { error: 'Too many requests. Try again shortly.' })
|
||||
return true
|
||||
}
|
||||
|
||||
const ensureStore = async () => {
|
||||
await fs.mkdir(dataDir, { recursive: true, mode: 0o700 })
|
||||
for (const file of Object.values(files)) {
|
||||
@@ -846,7 +873,10 @@ await seedDevelopmentStore()
|
||||
|
||||
http.createServer(async (req, res) => {
|
||||
try {
|
||||
if (req.url.startsWith('/api/')) await handleApi(req, res)
|
||||
if (req.url.startsWith('/api/')) {
|
||||
if (rateLimit(req, res)) return
|
||||
await handleApi(req, res)
|
||||
}
|
||||
else serveStatic(req, res)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
Reference in New Issue
Block a user