Harden admin events and document deployment

This commit is contained in:
Dorian
2026-05-14 12:53:37 -05:00
parent 3d87041f2d
commit 1708bfcf99
3 changed files with 141 additions and 2 deletions

View File

@@ -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)