Fix: Jellyfin login should use a stable device id to avoid leaking device records...
authorJc2k <john.carr@unrouted.co.uk>
Wed, 8 Jan 2025 13:40:17 +0000 (13:40 +0000)
committerGitHub <noreply@github.com>
Wed, 8 Jan 2025 13:40:17 +0000 (14:40 +0100)
music_assistant/providers/jellyfin/__init__.py
tests/conftest.py

index 1317488f811ea0cbe587093e8aafc7ce3c87b0c6..7a359d3b579f713e09791daea43f6026d1ea6bab 100644 (file)
@@ -2,9 +2,9 @@
 
 from __future__ import annotations
 
+import hashlib
 import mimetypes
 import socket
-import uuid
 from asyncio import TaskGroup
 from collections.abc import AsyncGenerator
 from typing import TYPE_CHECKING
@@ -133,6 +133,24 @@ class JellyfinProvider(MusicProvider):
 
     async def handle_async_init(self) -> None:
         """Initialize provider(instance) with given configuration."""
+        username = str(self.config.get_value(CONF_USERNAME))
+
+        # Device ID should be stable between reboots
+        # Otherwise every time the provider starts we "leak" a new device
+        # entry in the Jellyfin backend, which creates devices and entities
+        # in HA if they also use the Jellyfin integration there.
+
+        # We follow a suggestion a Jellyfin dev gave to HA and use an ID
+        # that is stable even if provider is removed and re-added.
+        # They said mix in username in case the same device/app has 2
+        # connections to the same servers
+
+        # Neither of these are secrets (username is handed over to mint a
+        # token and server_id is used in zeroconf) but hash them anyway as its meant
+        # to be an opaque identifier
+
+        device_id = hashlib.sha256(f"{self.mass.server_id}+{username}".encode()).hexdigest()
+
         session_config = SessionConfiguration(
             session=self.mass.http_session,
             url=str(self.config.get_value(CONF_URL)),
@@ -140,13 +158,13 @@ class JellyfinProvider(MusicProvider):
             app_name=USER_APP_NAME,
             app_version=CLIENT_VERSION,
             device_name=socket.gethostname(),
-            device_id=str(uuid.uuid4()),
+            device_id=device_id,
         )
 
         try:
             self._client = await authenticate_by_name(
                 session_config,
-                str(self.config.get_value(CONF_USERNAME)),
+                username,
                 str(self.config.get_value(CONF_PASSWORD)),
             )
         except Exception as err:
index 62c294cd3846df4543facd4448eda9dfa3742def..de17ce16c1611daddd95b7c6ddf7f2560e71d6dd 100644 (file)
@@ -7,7 +7,6 @@ from collections.abc import AsyncGenerator
 import pytest
 
 from music_assistant import MusicAssistant
-from tests.common import wait_for_sync_completion
 
 
 @pytest.fixture(name="caplog")
@@ -27,8 +26,7 @@ async def mass(tmp_path: pathlib.Path) -> AsyncGenerator[MusicAssistant, None]:
 
     mass = MusicAssistant(str(storage_path))
 
-    async with wait_for_sync_completion(mass):
-        await mass.start()
+    await mass.start()
 
     try:
         yield mass