From 7ada4f592901153f0481ff1865f2119ca23ff48a Mon Sep 17 00:00:00 2001 From: punkgun Date: Sun, 19 Jul 2026 20:51:57 +0200 Subject: [PATCH] Send auth.hardware_id as hardware_id header in request_login Blink rejects non-UUID hardware_id headers on /oauth/token with a 406, so the device_id default ("Blinkpy") breaks in-session token refresh. Verified against the live API: 406 with "Blinkpy", 200 with a UUID. Co-Authored-By: Claude Fable 5 --- blinkpy/api.py | 2 +- tests/test_auth.py | 4 ++-- tests/test_oauth.py | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/blinkpy/api.py b/blinkpy/api.py index d3221d71..94569170 100644 --- a/blinkpy/api.py +++ b/blinkpy/api.py @@ -130,7 +130,7 @@ async def request_login( headers = { "Content-Type": "application/x-www-form-urlencoded", "User-Agent": DEFAULT_USER_AGENT, - "hardware_id": login_data.get("device_id", "Blinkpy"), + "hardware_id": auth.hardware_id, } # Add 2FA code to headers if provided diff --git a/tests/test_auth.py b/tests/test_auth.py index 88dd8d14..4deb79d7 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -277,7 +277,7 @@ async def test_query_refresh_token_exists( headers={ "Content-Type": "application/x-www-form-urlencoded", "User-Agent": const.DEFAULT_USER_AGENT, - "hardware_id": const.DEVICE_ID, + "hardware_id": self.auth.hardware_id, }, timeout=10, ) @@ -307,7 +307,7 @@ async def test_query_auth_expired( headers={ "Content-Type": "application/x-www-form-urlencoded", "User-Agent": const.DEFAULT_USER_AGENT, - "hardware_id": const.DEVICE_ID, + "hardware_id": self.auth.hardware_id, }, timeout=10, ) diff --git a/tests/test_oauth.py b/tests/test_oauth.py index 1a6ce831..bd247908 100644 --- a/tests/test_oauth.py +++ b/tests/test_oauth.py @@ -363,3 +363,18 @@ async def test_complete_2fa_login(): # State should be cleaned up assert not hasattr(auth, "_oauth_csrf_token") assert not hasattr(auth, "_oauth_code_verifier") + + +@pytest.mark.asyncio +async def test_request_login_sends_auth_hardware_id_header(): + """Test that request_login sends auth.hardware_id as the hardware_id header.""" + auth = Mock() + auth.query = AsyncMock(return_value=Mock(status=200)) + auth.refresh_token = "refresh_token" + auth.hardware_id = "726D586E-6A27-49E4-B61B-1BB070908899" + + login_data = {"username": "foo", "password": "bar"} + await api.request_login(auth, "https://example.com", login_data, is_refresh=True) + + headers = auth.query.call_args.kwargs["headers"] + assert headers["hardware_id"] == auth.hardware_id