| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 |
- import { NextRequest } from 'next/server';
- import { createLogger } from '@/lib/logger';
- import { validateUrlForSSRF } from '@/lib/server/ssrf-guard';
- import { apiError, apiSuccess } from '@/lib/server/api-response';
- const log = createLogger('Azure Voices');
- export const maxDuration = 30;
- /**
- * Azure TTS Voice List API
- * Fetches available voices from Azure Speech Services
- */
- export async function POST(req: NextRequest) {
- let baseUrl: string | undefined;
- try {
- const body = await req.json();
- const { apiKey } = body;
- baseUrl = body.baseUrl;
- if (!apiKey) {
- return apiError('MISSING_API_KEY', 400, 'API Key is required');
- }
- if (!baseUrl) {
- return apiError('MISSING_REQUIRED_FIELD', 400, 'Base URL is required');
- }
- // Validate baseUrl against SSRF
- const ssrfError = await validateUrlForSSRF(baseUrl);
- if (ssrfError) {
- return apiError('INVALID_URL', 403, ssrfError);
- }
- // Call Azure voices list endpoint; disable redirect following to prevent SSRF via redirect
- const response = await fetch(`${baseUrl}/cognitiveservices/voices/list`, {
- method: 'GET',
- headers: {
- 'Ocp-Apim-Subscription-Key': apiKey,
- },
- redirect: 'manual',
- });
- if (response.status >= 300 && response.status < 400) {
- return apiError('REDIRECT_NOT_ALLOWED', 403, 'Redirects are not allowed');
- }
- if (!response.ok) {
- const errorText = await response.text();
- return apiError(
- 'UPSTREAM_ERROR',
- response.status,
- 'Failed to fetch voices from Azure',
- errorText || response.statusText,
- );
- }
- const voices = await response.json();
- return apiSuccess({ voices });
- } catch (error) {
- log.error(`Azure voices fetch failed [baseUrl="${baseUrl ?? 'unknown'}"]:`, error);
- return apiError(
- 'INTERNAL_ERROR',
- 500,
- 'Failed to fetch voices',
- error instanceof Error ? error.message : 'Unknown error',
- );
- }
- }
|