"""Stable Jellyfin identities for private, user-scoped integrations.""" import hashlib from contextlib import closing from .. import db def source_key(base_url: str | None) -> str: return hashlib.sha256(str(base_url or "").strip().rstrip("/").encode()).hexdigest() def linked_user_id(username: str, base_url: str | None) -> str | None: user = db.get_user_by_username(username) if not user or not base_url: return None with closing(db._connect()) as conn, conn: row = conn.execute( "SELECT jellyfin_user_id FROM jellyfin_user_links WHERE source = ? AND local_user_id = ?", (source_key(base_url), user["id"]), ).fetchone() return row[0] if row else None def link_user(username: str, jellyfin_user_id: str, base_url: str | None) -> None: """Use only verified login or canonical Jellyfin user sync, never playback names.""" user = db.get_user_by_username(username) if not user or not jellyfin_user_id or not base_url: return with closing(db._connect()) as conn, conn: if conn.execute("SELECT 1 FROM user_identity_confirmations WHERE local_user_id = ?", (user["id"],)).fetchone(): # Reviewed identities are updated only through the admin confirmation workflow. return # A renamed or re-created account must not silently take over an existing identity. conn.execute( "INSERT OR IGNORE INTO jellyfin_user_links (source, local_user_id, jellyfin_user_id) VALUES (?, ?, ?)", (source_key(base_url), user["id"], str(jellyfin_user_id)), )