Paket bauen und veroeffentlichen / paket (push) Failing after 1m21s
- /pult als installierbare Web-App (Manifest, Service Worker, Symbole) - Soundboard: Sounds vom Handy hochladen, bearbeiten, im Stream spielen ueber die neue Browser-Source /sounds - Szenen, Clip und Stummschalter: der Stream-PC waehlt sich mit eigenem Schluessel beim Hub ein (fern:verbinden), Befehle warten auf OBS - Browser-Sources melden, welche Seite sie sind Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
3.5 KiB
TypeScript
80 lines
3.5 KiB
TypeScript
// Das Sitzungs-Cookie ist der einzige Schutz vor der Einrichtungsseite, auf
|
|
// der Twitch-Zugangsdaten eingetragen werden. Hier wird geprueft, dass es
|
|
// haelt, was es soll - und dass es einen Neustart übersteht.
|
|
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import crypto from 'node:crypto';
|
|
import { sitzungGueltig, sitzungToken } from './auth.ts';
|
|
|
|
const KEY = crypto.randomBytes(32);
|
|
const HASH = 'ein-passwort-hash';
|
|
const JETZT = Date.parse('2026-09-17T18:00:00Z');
|
|
const IN_30_TAGEN = JETZT + 30 * 24 * 3_600_000;
|
|
|
|
test('ein frisch ausgestelltes Cookie gilt', () => {
|
|
const t = sitzungToken(KEY, HASH, IN_30_TAGEN);
|
|
assert.equal(sitzungGueltig(t, KEY, HASH, JETZT), true);
|
|
});
|
|
|
|
test('ein Neustart meldet niemanden ab', () => {
|
|
// Der eigentliche Grund für diese Bauform: der Dienst startet bei jedem
|
|
// Ausrollen neu. Lag die Sitzungsliste im Arbeitsspeicher, war man danach
|
|
// jedes Mal draußen. Derselbe Schlüssel - dasselbe Ergebnis, ohne dass
|
|
// irgendwo etwas gemerkt wurde.
|
|
const t = sitzungToken(KEY, HASH, IN_30_TAGEN);
|
|
const nachNeustart = Buffer.from(KEY); // derselbe Wert, neues Objekt
|
|
assert.equal(sitzungGueltig(t, nachNeustart, HASH, JETZT), true);
|
|
});
|
|
|
|
test('abgelaufen ist abgelaufen', () => {
|
|
const t = sitzungToken(KEY, HASH, JETZT - 1);
|
|
assert.equal(sitzungGueltig(t, KEY, HASH, JETZT), false);
|
|
});
|
|
|
|
test('das Ablaufdatum laesst sich nicht verlaengern', () => {
|
|
// Ohne Signatur ueber das Datum koennte jeder das Cookie umschreiben und
|
|
// waere damit dauerhaft angemeldet.
|
|
const t = sitzungToken(KEY, HASH, JETZT + 1000);
|
|
const [, sig] = t.split('.');
|
|
const gefaelscht = `${Buffer.from(String(IN_30_TAGEN)).toString('base64url')}.${sig}`;
|
|
assert.equal(sitzungGueltig(gefaelscht, KEY, HASH, JETZT), false);
|
|
});
|
|
|
|
test('ein fremder Schluessel oeffnet nichts', () => {
|
|
const t = sitzungToken(KEY, HASH, IN_30_TAGEN);
|
|
assert.equal(sitzungGueltig(t, crypto.randomBytes(32), HASH, JETZT), false);
|
|
});
|
|
|
|
test('ein neues Passwort macht alle Cookies ungueltig', () => {
|
|
// Das ist der Ersatz für eine Abmeldeliste: wer sein Passwort wechselt,
|
|
// wirft damit alle offenen Sitzungen raus.
|
|
const t = sitzungToken(KEY, HASH, IN_30_TAGEN);
|
|
assert.equal(sitzungGueltig(t, KEY, 'neuer-hash', JETZT), false);
|
|
});
|
|
|
|
test('Unsinn im Cookie wird abgewiesen, nicht geglaubt', () => {
|
|
for (const müll of [undefined, '', 'abc', 'abc.def', '.', 'x.y.z',
|
|
`${Buffer.from('keine-zahl').toString('base64url')}.xx`]) {
|
|
assert.equal(sitzungGueltig(müll, KEY, HASH, JETZT), false, `"${müll}" haette abgewiesen gehoert`);
|
|
}
|
|
});
|
|
|
|
test('zwei Anmeldungen mit demselben Ablauf ergeben dasselbe Cookie', () => {
|
|
// Bewusst so: ohne Zufallsanteil gibt es nichts, was der Server sich merken
|
|
// müsste. Der Preis ist, dass sich eine einzelne Sitzung nicht gezielt
|
|
// beenden lässt - dafür gibt es den Passwortwechsel.
|
|
assert.equal(sitzungToken(KEY, HASH, IN_30_TAGEN), sitzungToken(KEY, HASH, IN_30_TAGEN));
|
|
});
|
|
|
|
test('der Schluessel des Stream-PCs kommt aus dem Bearer-Kopf', async () => {
|
|
// Hier fehlten beim ersten Wurf die Backslashes in der Regex - jeder
|
|
// richtige Schluessel wurde abgelehnt, und nichts im Log sah danach aus.
|
|
const { bearer } = await import('./auth.ts');
|
|
assert.equal(bearer('Bearer 33qg61wQf_ad-x9v'), '33qg61wQf_ad-x9v');
|
|
assert.equal(bearer('Bearer abc'), 'abc');
|
|
assert.equal(bearer('Basic abc'), null);
|
|
assert.equal(bearer('Bearer'), null);
|
|
assert.equal(bearer(undefined), null);
|
|
});
|