Files
archy/packages/app/vite-dev-chats.ts
T
DorianandClaude Opus 4.6 5c4afe00e5 fix(app): add rate limiting to all API endpoints
Add sliding-window rate limiter in server/dev-auth.ts (60 req/min reads,
10 req/min writes per IP). Apply checkRateLimit() in all Vite plugins
and claude-proxy.ts after auth validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:34:41 +00:00

83 lines
2.5 KiB
TypeScript

import type { Plugin, Connect } from 'vite'
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'
import { resolve, dirname } from 'path'
import { validateDevAuth, checkRateLimit } from './server/dev-auth'
const CHATS_DIR = '.dev'
const CHATS_FILE = 'chats.json'
const MAX_BODY_SIZE = 5 * 1024 * 1024 // 5MB
function getChatsPath(root: string): string {
const dir = resolve(root, CHATS_DIR)
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
return resolve(dir, CHATS_FILE)
}
function readChats(filePath: string): string {
if (!existsSync(filePath)) return '{}'
try {
return readFileSync(filePath, 'utf-8')
} catch {
return '{}'
}
}
function writeChats(filePath: string, data: string): void {
const dir = dirname(filePath)
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
writeFileSync(filePath, data, 'utf-8')
}
export function devChatsPlugin(): Plugin {
let chatsPath = ''
return {
name: 'aiui-dev-chats',
apply: 'serve',
configResolved(config) {
chatsPath = getChatsPath(config.root)
},
configureServer(server) {
server.middlewares.use('/api/dev-chats', (req: Connect.IncomingMessage, res: any, next: () => void) => {
if (!validateDevAuth(req, res)) return
if (!checkRateLimit(req, res, req.method === 'PUT')) return
if (req.method === 'GET') {
const data = readChats(chatsPath)
res.setHeader('Content-Type', 'application/json')
res.end(data)
return
}
if (req.method === 'PUT') {
let body = ''
let exceeded = false
req.on('data', (chunk: Buffer) => {
if (exceeded) return
body += chunk.toString()
if (body.length > MAX_BODY_SIZE) {
exceeded = true
res.writeHead(413, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: 'Payload too large (max 5MB)' }))
}
})
req.on('end', () => {
if (exceeded) return
try {
JSON.parse(body)
writeChats(chatsPath, body)
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ ok: true }))
} catch (e) {
res.writeHead(400, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: 'Invalid JSON' }))
}
})
return
}
next()
})
},
}
}