using System.Net; using System.Net.Http.Json; using Halo.Api.Endpoints; namespace Halo.Api.Tests; /// Anmelden am Fernseher, ohne ein Passwort zu tippen. public class CodeSignInTests { private static Task CodeAnfordern(HaloServer server) => server.Client.PostAsJsonAsync("/api/v1/auth/code", new CodeRequest("Wohnzimmer", "tv-1")); [Test] public async Task Der_ganze_Weg_vom_Bildschirm_zum_Token() { using var server = new HaloServer(); var telefon = await server.SetUpAndSignIn(); // 1. Der Fernseher fordert an und zeigt den Code. var angefordert = await (await CodeAnfordern(server)).Content.ReadFromJsonAsync(); Assert.That(angefordert!.Code, Does.Match("^[0-9]{6}$")); // 2. Noch hat niemand bestätigt. var wartend = await server.Client.GetFromJsonAsync($"/api/v1/auth/code/{angefordert.Handle}"); Assert.That(wartend!.State, Is.EqualTo("pending")); Assert.That(wartend.Login, Is.Null); // 3. Am Telefon bestätigen. var bestätigt = await server.AlsAngemeldeter(telefon.Token) .PostAsync($"/api/v1/auth/code/{angefordert.Code}", null); bestätigt.EnsureSuccessStatusCode(); // 4. Der Fernseher holt sein Token ab. var fertig = await server.Client.GetFromJsonAsync($"/api/v1/auth/code/{angefordert.Handle}"); Assert.That(fertig!.State, Is.EqualTo("approved")); Assert.That(fertig.Login!.Token, Is.Not.Empty); // Und es gilt auch wirklich. var ich = await server.AlsAngemeldeter(fertig.Login.Token) .GetFromJsonAsync("/api/v1/auth/me"); Assert.That(ich!.UserName, Is.EqualTo("guido")); } /// Der Code allein darf niemals ein Token herausgeben. /// /// /// Die wichtigste Prüfung dieser Datei. Gäbe der Server auf einen /// erratenen Abholschein hin Token heraus, wären sechsstellige Codes ein /// Türschloss mit einer Million Schlüsseln — in Minuten durchprobiert, und /// am Ende stünde ein fremdes Gerät in der Mediathek. /// [Test] public async Task Ein_fremder_Abholschein_bekommt_nichts() { using var server = new HaloServer(); var telefon = await server.SetUpAndSignIn(); var echt = await (await CodeAnfordern(server)).Content.ReadFromJsonAsync(); await server.AlsAngemeldeter(telefon.Token).PostAsync($"/api/v1/auth/code/{echt!.Code}", null); // Der Code ist bestätigt — aber der Abholschein stimmt nicht. var erraten = await server.Client.GetFromJsonAsync("/api/v1/auth/code/ausgedacht"); Assert.That(erraten!.State, Is.EqualTo("expired")); Assert.That(erraten.Login, Is.Null); } /// Auch der Code selbst taugt nicht als Abholschein. [Test] public async Task Der_Code_ist_kein_Abholschein() { using var server = new HaloServer(); var telefon = await server.SetUpAndSignIn(); var echt = await (await CodeAnfordern(server)).Content.ReadFromJsonAsync(); await server.AlsAngemeldeter(telefon.Token).PostAsync($"/api/v1/auth/code/{echt!.Code}", null); var mitCode = await server.Client.GetFromJsonAsync($"/api/v1/auth/code/{echt.Code}"); Assert.That(mitCode!.Login, Is.Null); } [Test] public async Task Ein_abgeholtes_Token_gibt_es_kein_zweites_Mal() { using var server = new HaloServer(); var telefon = await server.SetUpAndSignIn(); var angefordert = await (await CodeAnfordern(server)).Content.ReadFromJsonAsync(); await server.AlsAngemeldeter(telefon.Token).PostAsync($"/api/v1/auth/code/{angefordert!.Code}", null); var erstesMal = await server.Client.GetFromJsonAsync($"/api/v1/auth/code/{angefordert.Handle}"); var zweitesMal = await server.Client.GetFromJsonAsync($"/api/v1/auth/code/{angefordert.Handle}"); Assert.That(erstesMal!.State, Is.EqualTo("approved")); Assert.That(zweitesMal!.State, Is.EqualTo("expired")); Assert.That(zweitesMal.Login, Is.Null); } /// Bestätigen darf nur, wer angemeldet ist. [Test] public async Task Ein_Unangemeldeter_kann_nichts_bestaetigen() { using var server = new HaloServer(); await server.SetUpAndSignIn(); var angefordert = await (await CodeAnfordern(server)).Content.ReadFromJsonAsync(); var versuch = await server.Client.PostAsync($"/api/v1/auth/code/{angefordert!.Code}", null); Assert.That(versuch.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); var danach = await server.Client.GetFromJsonAsync($"/api/v1/auth/code/{angefordert.Handle}"); Assert.That(danach!.State, Is.EqualTo("pending")); } [Test] public async Task Ein_erfundener_Code_laesst_sich_nicht_bestaetigen() { using var server = new HaloServer(); var telefon = await server.SetUpAndSignIn(); var versuch = await server.AlsAngemeldeter(telefon.Token).PostAsync("/api/v1/auth/code/000000", null); Assert.That(versuch.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); } /// Am Telefon soll stehen, was man da hereinlässt. [Test] public async Task Die_Bestaetigung_nennt_das_Geraet() { using var server = new HaloServer(); var telefon = await server.SetUpAndSignIn(); var angefordert = await (await CodeAnfordern(server)).Content.ReadFromJsonAsync(); var antwort = await server.AlsAngemeldeter(telefon.Token) .PostAsync($"/api/v1/auth/code/{angefordert!.Code}", null); Assert.That(await antwort.Content.ReadAsStringAsync(), Does.Contain("Wohnzimmer")); } }