Jaithra Polavarapu
feat: add secret-gated inference proxy for external frontends
3492294
Raw
History Blame Contribute Delete
2.45 kB
/**
* Inference Proxy — /predict
*
* Lets a separately-hosted frontend (e.g. the Vercel deployment, which has
* no Python inference process of its own) reach this Space's internal
* FastAPI service. Gated on a shared secret since it bypasses the
* Firebase-authenticated /api/predict route entirely — this is
* backend-to-backend, not meant to be called from a browser.
*/
import { NextRequest } from 'next/server'
import { rateLimit, getRateLimitHeaders } from '@/lib/security/rateLimiter'
import { createSecureResponse, addSecurityHeaders } from '@/lib/security/headers'
import { getInferenceUrl } from '@/lib/server/inferenceUrl'
const INFERENCE_TIMEOUT_MS = 30_000
function isAuthorized(request: NextRequest): boolean {
const expected = process.env.INFERENCE_PROXY_TOKEN
if (!expected) return false
return request.headers.get('x-inference-proxy-token') === expected
}
export async function POST(request: NextRequest) {
const rateLimitResponse = rateLimit(request, '/api/inference/predict')
if (rateLimitResponse) {
return addSecurityHeaders(rateLimitResponse)
}
if (!isAuthorized(request)) {
return createSecureResponse({ error: 'Unauthorized' }, 401)
}
let inferenceUrl: string
try {
inferenceUrl = getInferenceUrl()
} catch (error) {
console.error('Inference proxy: service misconfigured:', error)
return createSecureResponse({ error: 'Prediction service is not configured.' }, 503)
}
const formData = await request.formData()
let upstream: Response
try {
upstream = await fetch(`${inferenceUrl}/predict`, {
method: 'POST',
body: formData,
signal: AbortSignal.timeout(INFERENCE_TIMEOUT_MS),
})
} catch (error) {
console.error('Inference proxy: upstream unreachable:', error)
return createSecureResponse(
{ error: 'Prediction service unavailable. Please try again shortly.' },
503
)
}
let result: any
try {
result = await upstream.json()
} catch {
console.error('Inference proxy: upstream returned non-JSON, status:', upstream.status)
return createSecureResponse({ error: 'Prediction failed. Please try again later.' }, 500)
}
const response = createSecureResponse(result, upstream.status)
const rateLimitHeaders = getRateLimitHeaders(request, '/api/inference/predict')
Object.entries(rateLimitHeaders).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}