Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .secrets.baseline

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/api/hive_auth_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,11 @@ async def device_login(self):
_LOGGER.error("Device login failed: cannot reach endpoint.")
raise HiveApiError from err

if result and "AuthenticationResult" in result:
# Record the access token and issue time so get_device_data() reports
# when these tokens were created, not None, after a device login.
self._store_auth_result(result)

_LOGGER.debug("device_login - Device authentication completed successfully.")
return result

Expand Down
27 changes: 27 additions & 0 deletions tests/unit/test_hive_auth_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import datetime
from unittest.mock import AsyncMock, MagicMock, patch

import botocore.exceptions
Expand Down Expand Up @@ -1230,6 +1231,32 @@ async def test_successful_device_login_returns_auth_result(self):
mock_pdc.assert_called_once_with(initial_result["ChallengeParameters"])
assert result is final_result

async def test_device_login_records_access_token_and_token_created(self):
"""device_login() must store the tokens like login() and sms_2fa() do.

Otherwise get_device_data() reports token_created=None after a device
login and the session falls back to the epoch default, so every start
looks like an expired token (issue #123).
"""
auth = await _make_auth(device_key="dk-abc", device_group_key="grp-abc")
auth.device_password = "dev-pass" # pragma: allowlist secret
assert auth.token_created is None

initial_result = {"ChallengeParameters": {"USERNAME": "user@test.com"}}
final_result = {"AuthenticationResult": {"AccessToken": "device-access-token"}}

before = datetime.datetime.now()
with patch.object(auth, "process_device_challenge", new_callable=AsyncMock):
auth.loop.run_in_executor = AsyncMock(
side_effect=[initial_result, final_result]
)
await auth.device_login()

assert auth.access_token == "device-access-token"
assert isinstance(auth.token_created, datetime.datetime)
assert auth.token_created >= before
assert (await auth.get_device_data())[3] == auth.token_created

async def test_device_login_calls_second_respond_to_auth_challenge(self):
"""Lines 367-375: second respond_to_auth_challenge is called with device challenge."""
auth = await _make_auth(device_key="dk-xyz", device_group_key="grp-xyz")
Expand Down
Loading