Blink answers with 202 — and nobody got in
The login failed in the version I tested. Three days later the project had three new releases. What lay in between was not code.
Contents
In part one I took a project for half asleep and was wrong. The actual finding was not “nobody is interested” but “nobody gets in”.
Here is what caused it. It was a three-digit number nobody checked for.
The documented route did not work
Logging in to Blink requires a PIN by SMS. The add-on provided for two starts: the first begins the login and the PIN arrives, the second is where you enter it.
That never worked. Every attempt produced the SMS, and every attempt was followed
by Login failed.
My first find was self-inflicted: the options file had Windows line endings,
so the start script read one character too many. An embarrassing mistake, quickly
fixed — and afterwards the same Login failed stood there again.
The finding that looked final
Read in the source: the login stores two values in memory at the first step that the second step needs. Restart the service and they are gone.
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
That makes the two-start login impossible in principle. Not “does not work right now”, but: it cannot succeed while a restart sits between the two steps.
I took that for the final finding. It was only half the answer.
The observation that overturned it
Then came the sentence that changed direction — and it came not from the source but from the house:
On the second attempt Blink sent the same PIN as on the first.
That is not a side note. The PIN does not expire — the session does. The PIN belongs to the account and stays valid; what is missing are the two values from memory.
And from that the simplest route follows immediately: if the PIN is known in advance, nobody has to wait for a second start.
login -> 2FA demanded -> PIN ready -> redeemed at once -> token saved
No waiting block, no second start. The waiting logic remains as a fallback for cases where the PIN is not known in advance.
An everyday observation beat a code analysis here. I had read the source and understood it correctly — and still drawn the wrong conclusion, because I did not know the PIN stays the same.
And then the number
With an extra diagnostic line in the log the missing fact appeared:
oauth_signin HTTP 202
Login failed
The library in use knows exactly two cases at this point:
if response.status == 412: return "2FA_REQUIRED"
elif response.status in [301,302,303,307,308]: return "SUCCESS"
return None # -> "Login failed"
But Blink answers with 202 Accepted — credentials accepted, 2FA code sent.
That case falls through both checks and is reported as a failure.
Everything is explained in retrospect:
| Observation | Explanation |
|---|---|
| every attempt produced an SMS | the login succeeded every time |
yet Login failed appeared every time |
202 is not recognised |
| three times the same PIN | the same open challenge, nothing was locked |
The login had never failed. It was simply read wrongly.
An error message saying “Login failed” when the login succeeded sends every user in the wrong direction. You check passwords, accounts, lockouts — and none of it is the problem.
That is why nobody got in. Not lack of interest, but a status check that does not know about a third case.
What happened next
I submitted two contributions. What happened over the following 72 hours finally disposed of my assessment from part one:
| 30.08. | two contributions submitted |
| 31.08. | maintainer merges eight contributions, v0.2.0, automated build checks, roadmap |
| 31.08. | one of mine merged “taken as written” |
| 01.09. | detailed review of the second, three concrete points |
| 02.09. | a second, independent tester tries four cameras from my branch |
| 02.09. | v0.3.0, v0.4.0, v0.4.1 — nine commits in one day |
Three version jumps and a second independent tester in three days.
What the nudge consisted of — not volume of code
This is the part I consider most important. My share was smaller than the effect suggests. It was not a large contribution and not a new feature.
What counted was naming two blocking faults precisely, with the reason:
- The login failed in the version I tested — and why: HTTP 202 instead of 412, and the session-bound values.
- The video stream aborts before the camera is even woken — and why: because Blink’s server answers differently in three places from what the protocol description leads one to expect.
Two diagnoses, each with a log excerpt and a source location. Not “does not work for me”, but “here is the line, and here is what comes back instead”.
So what?
- A wrong error message costs more than none.
Login failedon a successful login probably drove more people away than any missing feature. - Read the source — but do not trust your conclusion immediately. I had read everything correctly and was still on the wrong track until an everyday observation arrived.
- An observation beats an analysis when it shows something the code does not say. That the PIN stays the same is written nowhere. You only see it by looking twice.
- A precise bug report is a contribution. You do not have to program anything to move a project forward. You have to be able to say exactly where it sticks.
- Check the return values that are not handled. Two cases checked, everything else an error — that is a trap sitting in a great deal of code.
The third part gets uncomfortable: the maintainer replies at length — and corrects me. Rightly.
If you want to follow this: use RSS
No newsletter. You give me no address, I keep no list, and nobody has to agree to anything to learn that an article is finished. RSS is the older and, for both sides, the more honest solution.
How it works, and why no newsletter
- 1Get a feed reader. No account needed with an app on your phone or a browser extension; use a hosted service if you prefer.
- 2Add the address above to it.
- 3Done. New articles turn up in your reader by themselves — unread stays unread, and nothing lands between invoices and ads in your inbox.
Why that is better: your reader fetches the file. I get no address and cannot see who reads or whether anyone opened it. Unsubscribing means deleting the entry — no link, no confirmation mail, no me.