diff --git a/env-example b/env-example new file mode 100644 index 00000000..7287891d --- /dev/null +++ b/env-example @@ -0,0 +1,11 @@ +PORT=3000 +REDIS_URL=redis://redis:6379 +COTURN_ENABLED=true +TURN_REALM=file.pizza +TURN_HOST=file.pizza +TURN_PORT=3478 +TURNS_PORT=5349 +TURN_CRED= +TURN_TRANS=both +STUN_HOST= +STUN_PORT= diff --git a/src/app/api/ice/route.ts b/src/app/api/ice/route.ts index 8dedc1c4..00e20fc7 100644 --- a/src/app/api/ice/route.ts +++ b/src/app/api/ice/route.ts @@ -1,29 +1,75 @@ import { NextResponse } from 'next/server' import crypto from 'crypto' -import { setTurnCredentials } from '../../../coturn' +import { createHmac } from 'crypto' +import { setTurnCredentials, setTurnCredentials2 } from '../../../coturn' -const turnHost = process.env.TURN_HOST || '127.0.0.1' +const stunHost = process.env.STUN_HOST || 'stun.l.google.com' +const stunPort = process.env.STUN_PORT || 19302 +const turnHost = process.env.TURN_HOST || '127.0.0.1' +const turnTrans = process.env.TURN_TRANS || 'both' // both = udp and tcp, udp = udp only, tcp = tcp only +const turnCred = process.env.TURN_CRED || '' +const turnPort = process.env.TURN_PORT || 3478 +const turnsPort = process.env.TURNS_PORT || 5349 export async function POST(): Promise { if (!process.env.COTURN_ENABLED) { return NextResponse.json({ - iceServers: [{ urls: 'stun:stun.l.google.com:19302' }], + iceServers: [{ urls: `stun:${stunHost}:${stunPort}` }], }) } + // Use hmac to create a password from username and turnCred + function generateHmacCred(username: string): string { + return createHmac('sha1', turnCred).update(username).digest('hex') + } + // Generate ephemeral credentials - const username = crypto.randomBytes(8).toString('hex') - const password = crypto.randomBytes(8).toString('hex') const ttl = 86400 // 24 hours + let username: string = '' + let password: string = '' + if (!turnCred) { + username = crypto.randomBytes(8).toString('hex') + password = crypto.randomBytes(8).toString('hex') + } else { + let now: number = Date.now() + ttl + username = String(now) + //password = String(turnCred) + password = generateHmacCred(username) + } + + // Store stun and turn hosts in an array + const ar_stun: string[] = [] + const ar_turn: string[] = [] + let base_stun: string = 'stun:' + stunHost + ':' + stunPort + let base_turn: string = 'turn:' + turnHost + ':' + turnPort + let base_turns: string = 'turns:' + turnHost + ':' + turnsPort + + ar_stun.push(base_stun) + if (turnTrans === 'both') { + ar_turn.push(base_turn + '?transport=udp') + ar_turn.push(base_turn + '?transport=tcp') + ar_turn.push(base_turns + '?transport=udp') + ar_turn.push(base_turns + '?transport=tcp') + } else if (turnTrans === 'tcp') { + ar_turn.push(base_turn + '?transport=tcp') + ar_turn.push(base_turns + '?transport=tcp') + } else { + ar_turn.push(base_turn + '?transport=udp') + ar_turn.push(base_turns + '?transport=udp') + } // Store credentials in Redis - await setTurnCredentials(username, password, ttl) + if (!turnCred) { + await setTurnCredentials(username, password, ttl) + } else { + await setTurnCredentials2(username, password, ttl) + } return NextResponse.json({ iceServers: [ - { urls: 'stun:stun.l.google.com:19302' }, + { urls: ar_stun }, { - urls: [`turn:${turnHost}:3478`, `turns:${turnHost}:5349`], + urls: ar_turn, username, credential: password, }, diff --git a/src/coturn.ts b/src/coturn.ts index 7b56e4bf..f1c93691 100644 --- a/src/coturn.ts +++ b/src/coturn.ts @@ -32,3 +32,26 @@ export async function setTurnCredentials( await redis.setex(key, ttl, hmacKey) } + +export async function setTurnCredentials2( + username: string, + password: string, + ttl: number, +): Promise { + if (!process.env.COTURN_ENABLED) { + return + } + + const realm = process.env.TURN_REALM || 'file.pizza' + + if (!realm) { + throw new Error('TURN_REALM environment variable not set') + } + + const redis = getRedisClient() + + const hmacKey = password + const key = `turn/realm/${realm}/user/${username}/key` + + await redis.setex(key, ttl, hmacKey) +}