/** * GET /api/calls/ice-servers * * Liefert ephemere TURN-Credentials für eine WebRTC-Audio-Verbindung. * coturn läuft mit `use-auth-secret` (TURN-REST-API-Schema): * username = ":" * credential = base64( HMAC-SHA1(TURN_SECRET, username) ) * coturn akzeptiert das ohne DB-Lookup, die Credentials laufen nach TTL ab. * * Privacy: iceTransportPolicy = "relay" → der Client tauscht NIE direkte * Host-IPs aus, alles läuft über coturn. Ist TURN nicht konfiguriert, wird * bewusst 503 geworfen (kein IP-leakender STUN-only-Fallback). * * Response: { iceServers: RTCIceServer[], iceTransportPolicy: "relay", ttl: number } */ import { createHmac } from "node:crypto"; import { requireUser } from "../../utils/auth"; const TTL_SECONDS = 600; // 10 min — länger als ein typischer Call-Aufbau export default defineEventHandler(async (event) => { const user = await requireUser(event); const config = useRuntimeConfig(event); const host = config.turnHost as string; const secret = config.turnSecret as string; if (!host || !secret) { // coturn/Secrets noch nicht eingerichtet → Calls sind nicht verfügbar. throw createError({ statusCode: 503, statusMessage: "calls_not_configured" }); } const expiry = Math.floor(Date.now() / 1000) + TTL_SECONDS; const username = `${expiry}:${user.id}`; const credential = createHmac("sha1", secret).update(username).digest("base64"); return { iceServers: [ { urls: [ `turn:${host}:3478?transport=udp`, `turn:${host}:3478?transport=tcp`, `turns:${host}:5349?transport=tcp`, ], username, credential, }, ], iceTransportPolicy: "relay" as const, ttl: TTL_SECONDS, }; });