KapfenbergWas passiert, wenn …?Sicherheit und ZugriffLokal statt Cloud

Blink antwortet mit 202 — und niemand kam hinein

Die Anmeldung scheiterte in der getesteten Fassung. Drei Tage später hatte das Projekt drei neue Versionen. Was dazwischen lag, war kein Code.

Im ersten Teil habe ich ein Projekt für halb eingeschlafen gehalten und mich geirrt. Der eigentliche Befund war nicht „niemand interessiert sich“, sondern „niemand kommt hinein“.

Hier steht, woran es lag. Es war eine dreistellige Zahl, die niemand abgefragt hat.

Der beworbene Weg funktionierte nicht

Die Anmeldung bei Blink verlangt eine PIN per SMS. Das Add-on sah dafür zwei Starts vor: Beim ersten wird die Anmeldung begonnen und die PIN kommt, beim zweiten trägt man sie ein.

Das funktionierte nie. Bei jedem Versuch kam die SMS, und bei jedem Versuch stand anschließend Login failed.

Mein erster Fund war ein selbstgemachter: Die Optionsdatei hatte Windows-Zeilenenden, und das Startskript las dadurch ein Zeichen zu viel. Ein peinlicher Fehler, schnell behoben — und danach stand dasselbe Login failed wieder da.

Der Befund, der endgültig schien

Nachgelesen im Quelltext: Die Anmeldung legt beim ersten Schritt zwei Werte im Arbeitsspeicher ab, die der zweite Schritt braucht. Startet der Dienst neu, sind sie weg.

if not hasattr(self, "_oauth_csrf_token") or not hasattr(self, "_oauth_code_verifier"):
    _LOGGER.error("No OAuth 2FA state found. Start login flow first.")
    return False

Damit ist die Zwei-Start-Anmeldung prinzipiell unmöglich. Nicht „geht gerade nicht“, sondern: Sie kann nicht gelingen, solange zwischen den beiden Schritten ein Neustart liegt.

Ich hielt das für den endgültigen Befund. Es war nur die halbe Antwort.

Die Beobachtung, die alles umwarf

Dann der Satz, der die Richtung änderte — und er kam nicht aus dem Quelltext, sondern aus dem Haus:

Beim zweiten Anlauf schickte Blink dieselbe PIN wie beim ersten.

Das ist kein Nebenbefund. Nicht die PIN verfällt — die Sitzung verfällt. Die PIN hängt am Konto und bleibt gültig; was fehlt, sind die beiden Werte aus dem Arbeitsspeicher.

Und daraus folgt sofort der einfachste Weg: Ist die PIN vorab bekannt, muss niemand auf einen zweiten Start warten.

Anmeldung -> 2FA verlangt -> PIN liegt bereit -> sofort eingelöst -> Token gespeichert

Kein Warteblock, kein zweiter Start. Die Wartelogik bleibt als Rückfall für den Fall, dass die PIN einmal nicht vorab bekannt ist.

Eine Beobachtung aus dem Alltag hat hier eine Codeanalyse geschlagen. Ich hatte den Quelltext gelesen und richtig verstanden — und trotzdem die falsche Schlussfolgerung gezogen, weil ich nicht wusste, dass die PIN gleich bleibt.

Und dann die Zahl

Mit einer zusätzlichen Diagnosezeile im Protokoll kam der fehlende Fakt:

oauth_signin HTTP 202
Login failed

Die verwendete Bibliothek kennt an dieser Stelle genau zwei Fälle:

if   response.status == 412:                      return "2FA_REQUIRED"
elif response.status in [301,302,303,307,308]:    return "SUCCESS"
return None                                       # -> "Login failed"

Blink antwortet aber mit 202 Accepted — Zugangsdaten angenommen, 2FA-Code verschickt. Dieser Fall fällt durch beide Prüfungen und wird als Fehlschlag gemeldet.

Damit erklärt sich rückwirkend alles:

Beobachtung Erklärung
bei jedem Versuch kam eine SMS die Anmeldung ging jedes Mal durch
trotzdem stand jedes Mal Login failed 202 wird nicht erkannt
dreimal dieselbe PIN dieselbe offene Abfrage, nichts war gesperrt

Die Anmeldung hatte nie versagt. Sie wurde nur falsch gelesen.

Eine Fehlermeldung, die „Login failed“ sagt, obwohl die Anmeldung erfolgreich war, schickt jeden Benutzer in die falsche Richtung. Man prüft Kennwörter, Konten, Sperren — und nichts davon ist das Problem.

Das ist der Grund, warum niemand hineinkam. Nicht fehlendes Interesse, sondern eine Statusabfrage, die einen dritten Fall nicht kennt.

Was dann passiert ist

Ich habe zwei Beiträge eingereicht. Was in den folgenden 72 Stunden geschah, hat meine Einschätzung aus Teil 1 endgültig erledigt:

30.08. zwei Beiträge eingereicht
31.08. Betreuer führt acht Beiträge zusammen, v0.2.0, automatische Bauprüfung, Roadmap
31.08. einer meiner Beiträge „taken as written“ übernommen
01.09. ausführlicher Review zum zweiten, drei konkrete Punkte
02.09. ein zweiter, unabhängiger Prüfer testet vier Kameras aus meinem Zweig
02.09. v0.3.0, v0.4.0, v0.4.1 — neun Commits an einem Tag

Drei Versionssprünge und ein zweiter unabhängiger Prüfer in drei Tagen.

Woraus der Schubs bestand — nicht aus Codemenge

Das ist der Teil, den ich am wichtigsten finde. Mein Anteil war kleiner, als die Wirkung vermuten lässt. Es war kein großer Beitrag und keine neue Funktion.

Was zählte, war zwei blockierende Fehler genau zu benennen, mitsamt dem Grund:

  • Die Anmeldung scheiterte in der Fassung, die ich getestet habe — und warum: HTTP 202 statt 412, und die an die Sitzung gebundenen Werte.
  • Der Videostrom bricht ab, bevor die Kamera überhaupt geweckt ist — und weshalb: weil Blinks Server an drei Stellen anders antwortet, als es die Protokollbeschreibung erwarten lässt.

Zwei Diagnosen, jede mit Protokollauszug und Quelltextstelle. Kein „geht nicht bei mir“, sondern „hier ist die Zeile, und hier ist, was stattdessen zurückkommt“.

Und was heißt das?

  1. Eine falsche Fehlermeldung ist teurer als gar keine. Login failed bei erfolgreicher Anmeldung hat vermutlich mehr Leute vertrieben als jede fehlende Funktion.
  2. Lies den Quelltext — aber glaub deiner Schlussfolgerung nicht sofort. Ich hatte alles richtig gelesen und war trotzdem auf dem Holzweg, bis eine Alltagsbeobachtung dazukam.
  3. Eine Beobachtung schlägt eine Analyse, wenn sie etwas zeigt, das im Code nicht steht. Dass die PIN gleich bleibt, steht nirgends geschrieben. Man sieht es nur, wenn man zweimal hinschaut.
  4. Ein präziser Fehlerbericht ist ein Beitrag. Man muss nichts programmieren, um ein Projekt voranzubringen. Man muss sagen können, woran genau es hakt.
  5. Prüf die Rückgabewerte, die nicht behandelt werden. Zwei Fälle abgefragt, alles andere als Fehler — das ist eine Falle, die in sehr viel Code steckt.

Im dritten Teil wird es unangenehm: Der Betreuer antwortet ausführlich — und berichtigt mich. Zu Recht.