Vermittlungsserver: coturn mit Zugangsdaten, die verfallen
Damit Anrufe auch in Netzen zustande kommen, die keine direkte Verbindung zulassen (15-25 % der Faelle). coturn ist installiert, steht aber still, bis die Konfiguration liegt -- ein coturn mit Werkseinstellung ist ein offenes Relais. KEIN FESTES PASSWORT. Es laege dauerhaft im Browser jedes Team-Mitglieds und liesse sich nie entziehen. Der Server rechnet stattdessen bei jeder Abfrage Zugangsdaten, die nach zwoelf Stunden verfallen (server/workspace-turn.js, coturns `use-auth-secret`). Das Geheimnis liegt in einer Datei, nicht in der Datenbank: einstellung- Setzen() schreibt Werte ins Protokoll, und coturn braucht denselben Wert ohnehin in /etc. Die Oberflaeche frischt die Daten vor jedem Anruf auf. Der Chat ist eine App, die tagelang offen bleibt -- wer nur beim Laden holt, telefoniert am zweiten Tag ohne Vermittlung, und es faellt nicht auf: Es scheitern nur die, die sie gebraucht haetten. DIE WICHTIGSTE ZEILE DER KONFIGURATION ist die Sperrliste. Gemessen: dreizehn Dienste lauschen auf diesem Server nur oertlich, darunter Caddys Verwaltung auf 127.0.0.1:2019 -- wer sie erreicht, kann jede Website umleiten. Ohne Sperrliste waere der Vermittlungsserver die Tuer dorthin, und die Anfrage saehe fuer Caddy aus wie von localhost. Geprueft: pruef-anruf.mjs 51 -> 73 Pruefungen. Gegenprobe (Geheimnis als Passwort ausliefern + fremde Zugangsdaten ueberschreiben) macht genau 5 rot, darunter "das Geheimnis steht NIRGENDS in der Antwort". tools/turn-probelauf.sh beweist am echten coturn, was ein fester Vergleichswert nicht kann: dass coturn unsere Rechnung akzeptiert. Beide Seiten koennten sonst konsequent falsch rechnen und jede Pruefung waere gruen. Seine eigene Gegenprobe war zweimal zu Recht rot -- der erste Aufbau mass wegen `-y` gar nicht das Ziel, das er zu messen behauptete, sondern coturns eingebauten Loopback-Schutz. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -247,6 +247,10 @@
|
||||
kastenBauen();
|
||||
$('anruf').hidden = false;
|
||||
lage('Mikrofon …');
|
||||
/* Läuft NEBEN der Mikrofonfrage, nicht davor: Das Auffrischen
|
||||
kostet sonst Zeit, in der nichts passiert -- und die Frage nach
|
||||
dem Mikrofon dauert ohnehin länger als der kleine Abruf. */
|
||||
const adressenFertig = adressenAuffrischen();
|
||||
let eigener;
|
||||
try {
|
||||
eigener = await eigenenStromHolen(mitVideo);
|
||||
@@ -255,6 +259,7 @@
|
||||
setTimeout(() => { if (!anruf) $('anruf').hidden = true; }, 6000);
|
||||
return;
|
||||
}
|
||||
await adressenFertig;
|
||||
anruf = { raumId, video: mitVideo, verbindungen: new Map(),
|
||||
stroeme: new Map(), eigener, beginn: Date.now() };
|
||||
$('anruf-video').hidden = !mitVideo;
|
||||
@@ -278,6 +283,10 @@
|
||||
}
|
||||
|
||||
async function annehmen(raumId) {
|
||||
/* Auch hier auffrischen -- wer angerufen WIRD, braucht die
|
||||
Vermittlung genauso. Beide Wege einzeln abgesichert, nicht nur
|
||||
der, an den man zuerst denkt. */
|
||||
const adressenFertig = adressenAuffrischen();
|
||||
const a = await fetch(`/workspace/api/anruf/${raumId}/dabei`, {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
}).catch(() => null);
|
||||
@@ -288,6 +297,7 @@
|
||||
return;
|
||||
}
|
||||
const d = await a.json();
|
||||
await adressenFertig;
|
||||
kastenBauen();
|
||||
$('anruf').hidden = false;
|
||||
let eigener;
|
||||
@@ -500,6 +510,53 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Verbindungsadressen ----------------------------------------
|
||||
Die Zugangsdaten zum Vermittlungsserver VERFALLEN (sie werden vom
|
||||
Server gerechnet, nicht gespeichert -- siehe server/workspace-turn.js).
|
||||
|
||||
Diese Seite bleibt als App oft tagelang offen. Wer sie nur beim
|
||||
Laden holt, telefoniert am zweiten Tag ohne Vermittlung -- und das
|
||||
fällt nicht auf: Es scheitern nur die Anrufe derer, die sie
|
||||
gebraucht hätten. Genau dafür ist sie gebaut.
|
||||
|
||||
Deshalb vor jedem Anruf nachsehen. Der Ablaufzeitpunkt steht im
|
||||
Benutzernamen (`<ablauf>:<kennung>`) -- er lässt sich hier ablesen,
|
||||
ohne dass der Server ein zusätzliches Feld schicken muss. */
|
||||
|
||||
/* Fünf Minuten Vorlauf: Ein Anruf, der in 30 Sekunden beginnt, soll
|
||||
nicht mit Daten starten, die in 40 ablaufen. */
|
||||
const ZUGANG_VORLAUF = 300;
|
||||
let adressenGeholt = false;
|
||||
|
||||
function adressenFrisch() {
|
||||
if (!adressenGeholt) return false;
|
||||
const jetzt = Math.floor(Date.now() / 1000);
|
||||
return adressen.every((a) => {
|
||||
const m = /^(\d{10,}):/.exec(a && a.username || '');
|
||||
return !m || Number(m[1]) > jetzt + ZUGANG_VORLAUF;
|
||||
});
|
||||
}
|
||||
|
||||
async function adressenAuffrischen(erzwingen) {
|
||||
if (!erzwingen && adressenFrisch()) return;
|
||||
try {
|
||||
const a = await fetch('/workspace/api/anruf/adressen', { credentials: 'same-origin' });
|
||||
if (a.ok) {
|
||||
const d = await a.json();
|
||||
adressen = d.adressen || [];
|
||||
nurGleichesNetz = !!d.nur_gleiches_netz;
|
||||
gruppeMax = d.gruppe_max || 4;
|
||||
adressenGeholt = true;
|
||||
/* Ein eingetragener Vermittlungsserver, der niemanden
|
||||
durchlässt, ist schlimmer als keiner -- er sieht aus, als
|
||||
wäre alles eingerichtet. Deshalb steht er in der Konsole. */
|
||||
if (d.turn_grund) {
|
||||
console.warn('[anruf] Vermittlungsserver ohne Zugangsdaten:', d.turn_grund);
|
||||
}
|
||||
}
|
||||
} catch { /* dann eben ohne -- im selben Netz geht es trotzdem */ }
|
||||
}
|
||||
|
||||
/* ---------- Nach außen -------------------------------------------------- */
|
||||
|
||||
window.anrufStarten = starten;
|
||||
@@ -508,15 +565,7 @@
|
||||
window.anrufBereit = () => adressen.length > 0 || nurGleichesNetz;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const a = await fetch('/workspace/api/anruf/adressen', { credentials: 'same-origin' });
|
||||
if (a.ok) {
|
||||
const d = await a.json();
|
||||
adressen = d.adressen || [];
|
||||
nurGleichesNetz = !!d.nur_gleiches_netz;
|
||||
gruppeMax = d.gruppe_max || 4;
|
||||
}
|
||||
} catch { /* dann eben ohne -- im selben Netz geht es trotzdem */ }
|
||||
await adressenAuffrischen();
|
||||
try {
|
||||
const i = await fetch('/workspace/api/ich', { credentials: 'same-origin' });
|
||||
if (i.ok) ichId = (await i.json())?.id ?? null;
|
||||
|
||||
Reference in New Issue
Block a user