diff --git a/src/app/api/ice/route.ts b/src/app/api/ice/route.ts index 7c4c7ff..3e662df 100644 --- a/src/app/api/ice/route.ts +++ b/src/app/api/ice/route.ts @@ -2,12 +2,25 @@ import { NextResponse } from 'next/server' import crypto from 'crypto' import { setTurnCredentials } from '../../../coturn' +const ICE_RATE_LIMIT_WINDOW_MS = 60_000 +const ICE_RATE_LIMIT_MAX_REQUESTS = 10 + +type IceRateLimitEntry = { + count: number + expiresAt: number +} + +const iceRateLimitStore = + (globalThis as typeof globalThis & { __iceRateLimitStore?: Map }).__iceRateLimitStore || + new Map() +;(globalThis as typeof globalThis & { __iceRateLimitStore?: Map }).__iceRateLimitStore = iceRateLimitStore + const turnHost = process.env.TURN_HOST || '127.0.0.1' const stunServer = process.env.STUN_SERVER || 'stun:stun.l.google.com:19302' const peerjsHost = process.env.PEERJS_HOST || '0.peerjs.com' const peerjsPath = process.env.PEERJS_PATH || '/' -export async function POST(): Promise { +export async function POST(request: Request): Promise { if (!process.env.COTURN_ENABLED) { return NextResponse.json({ host: peerjsHost, @@ -16,12 +29,31 @@ export async function POST(): Promise { }) } - // Generate ephemeral credentials + const expectedToken = process.env.ICE_API_TOKEN + const providedToken = request.headers.get('authorization')?.replace(/^Bearer\s+/i, '').trim() + if (!expectedToken || providedToken !== expectedToken) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const forwardedFor = request.headers.get('x-forwarded-for') + const realIp = request.headers.get('x-real-ip') + const ip = forwardedFor?.split(',')[0]?.trim() || realIp || 'unknown' + const now = Date.now() + const rateLimitEntry = iceRateLimitStore.get(ip) + + if (!rateLimitEntry || rateLimitEntry.expiresAt <= now) { + iceRateLimitStore.set(ip, { count: 1, expiresAt: now + ICE_RATE_LIMIT_WINDOW_MS }) + } else if (rateLimitEntry.count >= ICE_RATE_LIMIT_MAX_REQUESTS) { + return NextResponse.json({ error: 'Too many requests' }, { status: 429 }) + } else { + rateLimitEntry.count += 1 + iceRateLimitStore.set(ip, rateLimitEntry) + } + const username = crypto.randomBytes(8).toString('hex') const password = crypto.randomBytes(8).toString('hex') - const ttl = 86400 // 24 hours + const ttl = 600 - // Store credentials in Redis await setTurnCredentials(username, password, ttl) return NextResponse.json({