| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061 |
- /**
- * Media Proxy API
- *
- * Server-side proxy for fetching remote media URLs (images/videos).
- * Required because browser fetch() to remote CDN URLs fails with CORS errors.
- * The media orchestrator uses this to download generated media as blobs
- * for IndexedDB persistence.
- *
- * POST /api/proxy-media
- * Body: { url: string }
- * Response: Binary blob with appropriate Content-Type
- */
- import { NextRequest, NextResponse } from 'next/server';
- import { validateUrlForSSRF } from '@/lib/server/ssrf-guard';
- import { apiError } from '@/lib/server/api-response';
- import { createLogger } from '@/lib/logger';
- const log = createLogger('ProxyMedia');
- export const maxDuration = 60;
- export async function POST(request: NextRequest) {
- let url: string | undefined;
- try {
- ({ url } = await request.json());
- if (!url || typeof url !== 'string') {
- return apiError('MISSING_REQUIRED_FIELD', 400, 'Missing or invalid url');
- }
- // Block local/private network URLs to prevent SSRF
- const ssrfError = await validateUrlForSSRF(url);
- if (ssrfError) {
- return apiError('INVALID_URL', 403, ssrfError);
- }
- // Disable redirect following to prevent redirect-to-internal attacks
- const response = await fetch(url, { redirect: 'manual' });
- if (response.status >= 300 && response.status < 400) {
- return apiError('REDIRECT_NOT_ALLOWED', 403, 'Redirects are not allowed');
- }
- if (!response.ok) {
- return apiError('UPSTREAM_ERROR', 502, `Upstream returned ${response.status}`);
- }
- const blob = await response.blob();
- const contentType = response.headers.get('content-type') || 'application/octet-stream';
- return new NextResponse(blob, {
- headers: {
- 'Content-Type': contentType,
- 'Content-Length': String(blob.size),
- 'Cache-Control': 'private, max-age=3600',
- },
- });
- } catch (error) {
- log.error(`Proxy media failed [url="${url?.substring(0, 100) ?? 'unknown'}"]:`, error);
- return apiError('INTERNAL_ERROR', 500, error instanceof Error ? error.message : String(error));
- }
- }
|