From a11fec6240d5ab8820eb9899f9956a56055e9f3f Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Thu, 8 Oct 2026 18:55:45 -0400 Subject: [PATCH] Started work on dynamic authentication for TURN servers --- config.example.json | 17 ++++++++---- config.example.jsonc | 20 ++++++++++---- src/app/channel/media/queue.js | 48 +++++++++++++++++++++++++++++++++- 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/config.example.json b/config.example.json index 51388bb..71798f6 100644 --- a/config.example.json +++ b/config.example.json @@ -34,11 +34,18 @@ "address": "toke@42069.weed", "pass": "CHANGE_ME" }, - "iceServers":[ - { - "urls":["stun:stun.moonlight-stream.org:3478"] - } - ], + "iceServers":{ + "ttl":12, + "stun":[ + ["stun:telstar.ourfore.st:3478"] + ], + "turn":[ + { + "urls": ["turns:telstar.ourfore.st:5349"], + "secret": "SHAREDSECRET" + } + ] + }, "links":{ "About": "/about", "Code": "https://git.ourfore.st/rainbownapkin/canopy", diff --git a/config.example.jsonc b/config.example.jsonc index 163517e..aac767b 100644 --- a/config.example.jsonc +++ b/config.example.jsonc @@ -66,11 +66,21 @@ "pass": "CHANGE_ME" }, //Privdes STUN/TURN servers for WebRTC functionality - "iceServers":[ - { - "urls":["stun:stun.moonlight-stream.org:3478"] - } - ], + "iceServers":{ + //Time to live for TURN credentials (in hours) + "ttl":12, + //array of urls for each stun server + "stun":[ + ["stun:telstar.ourfore.st:3478"] + ], + //Array of objects containing urls and shared secret of a turn server + "turn":[ + { + "urls": ["turns:telstar.ourfore.st:5349"], + "secret": "SHAREDSECRET" + } + ] + }, //Provides customizable links for navbar "links":{ "About": "/about", diff --git a/src/app/channel/media/queue.js b/src/app/channel/media/queue.js index 1b975d0..fc8de01 100644 --- a/src/app/channel/media/queue.js +++ b/src/app/channel/media/queue.js @@ -14,6 +14,9 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see .*/ +//Node imports +const crypto = require('crypto'); + //NPM imports const validator = require('validator'); const mongoose = require('mongoose'); @@ -340,7 +343,7 @@ class queue{ let title = "Livestream"; let filename = '/'; let streamURL = '/'; - let rawLink = {combo: [['default', config.iceServers]]}; + let rawLink = {}; if(data != null && data.title != null){ //If the title is too long @@ -411,9 +414,13 @@ class queue{ filename = filename[1]; } + //Set rawlink to hls url rawLink = {combo:[['default',streamURL]]}; //Otherwise if we're starting a web RTC Stream }else if(chanDB.settings.streamType == "webrtc"){ + //Set rawlink as iceServers + rawLink = {combo: [['default', this.prepIceConfig()]]}; + //Let our webRTCHandler know this.channel.webRTCHandler.startStream(socket); } @@ -443,6 +450,45 @@ class queue{ } } + prepIceConfig(){ + //Create array to hold ice servers + let iceServers = []; + + //for each stun server listed in the config + for(let server of config.iceServers.stun){ + //push the stun server into our array + iceServers.push({ + urls: server + }); + } + + //Run thorugh TURN servers to dynamically generate temporary channel-specific credentials + for(let server of config.iceServers.turn){ + //Get current time as JS epoch (millis) and convert to unix notation (seconds), then multiply config-specified ttl to convert hours to seconds, storing sum as the credential expiration epoch + const expiration = Math.floor(Date.now() / 1000) + (config.iceServers.ttl * 3600); + //Slap together username from ttl timestamp, and channel name (for session limiting purposes) + const user = `${expiration}:${this.channel.name}` + + //sha1 seems like a silly/outdated choice but considering some of the other shit coturn devs have been up to mediocrity doesnt surprise me + //Seriously, to watch a deeply important puzzle piece of modern infrastructure get TURNed into LLM-Slop is just depressing + //At least I get to make puns about it + + //Create token from server shared secret and username + const token = crypto.createHmac('sha1', `${server.secret}${user}`); + + //Hash token string and save it in base64 + const hash = token.digest('base64'); + + iceServers.push({ + urls: server.urls, + username: user, + credential: hash + }) + } + + return iceServers; + } + /** * Returns current channel stream type upon request * @param {Socket} socket - Socket we received the request from