Backend: - GET /api/calls/ice-servers: ephemeral HMAC TURN credentials (10-min TTL), iceTransportPolicy:"relay" (no IP leak), 503 until coturn configured - nitro runtimeConfig: turnHost/turnSecret/turnRealm (Infisical staging set) Ops: - ops/calls/ runbook + turnserver.conf (self-hosted coturn, force-relay, use-auth-secret, hardening). coturn provisioned + verified on rebreak-server. Frontend (DM header redesign): - removed standalone "i" button; header center (avatar+name+chevron) opens info sheet - call icon top-right, only when canCall (mutual-follow + callsEnabled); shows "coming soon" until the WebRTC client lands Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
53 lines
1.7 KiB
TypeScript
53 lines
1.7 KiB
TypeScript
/**
|
|
* 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 = "<unix_expiry>:<userId>"
|
|
* 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,
|
|
};
|
|
});
|