Distinguish rejected credentials from a generic login failure - #1300
Open
txtmode wants to merge 1 commit into
Open
Distinguish rejected credentials from a generic login failure#1300txtmode wants to merge 1 commit into
txtmode wants to merge 1 commit into
Conversation
A wrong password makes the OAuth signin step return 401 with a JSON body
explaining why:
{"error":"unauthorized",
"error_cause":"invalid_user_credentials",
"error_description":"Invalid user credentials."}
oauth_signin read the body only inside its 202 branch, so a 401 logged
"OAuth signin failed: status=401 body=" with the explanation discarded,
and _oauth_login_flow returned False like any other failure. Callers
cannot tell a rejected password from a transient server error, so they
retry forever instead of asking for new credentials. In Home Assistant
this shows up as Blink retrying every 10 minutes with no re-authenticate
prompt, because start() returns False and the coordinator raises
ConfigEntryNotReady rather than ConfigEntryAuthFailed.
Read the body once for every status that is not already handled, use it
for the log, and return a new "INVALID_CREDENTIALS" result on 401.
_oauth_login_flow now raises UnauthorizedError for that case, the same
way it raises BlinkTwoFARequiredError when 2FA is needed. Both mean the
app has to involve the user, and UnauthorizedError already passes
through Blink.start() untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
When the account password no longer matches, Blink's OAuth signin step answers 401 with a body that says exactly that:
oauth_signinread the response body only inside its 202 branch, so on a 401 it loggedOAuth signin failed: status=401 body=and threw the explanation away._oauth_login_flowthen returned False, the same as for any other failure, so a caller cannot tell a rejected password from a transient server error.Downstream that turns a five second fix into a long diagnosis. In Home Assistant,
Blink.start()returns False,availablestays False, and the coordinator raisesConfigEntryNotReady, so the integration retries every 10 minutes forever and never shows the re-authenticate prompt. I found this on an instance that had made roughly 1,800 failed logins over 12 days while the log only ever said "Login failed".Changes
oauth_signinreads the body once for any status it has not already handled, so the failure log carries Blink's own explanation."INVALID_CREDENTIALS"result._oauth_login_flowraisesUnauthorizedErrorfor that result, the way it already raisesBlinkTwoFARequiredErrorfor 2FA. Both mean the app must involve the user rather than retry.UnauthorizedErroralready propagates throughBlink.start()untouched, so consumers that handle it get a working re-auth path with no further change. The 412, 202 and redirect paths behave exactly as before.Testing
Five tests added to
tests/test_oauth.pycovering the 401 JSON body, a 401 non-JSON body, the body reaching the log on an unexpected status, the new raise, and the unchanged False for other failures. Full suite passes, 254 tests, ruff and black clean.🤖 Generated with Claude Code