// 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); });