fix: MD5-hash Qobuz password and never log credential-bearing login errors
streamrip's email/password login expects the MD5 digest, not plaintext (caused 'Invalid credentials'). Also wrap login() so streamrip's exception — which embeds the email/password/app_id — can never reach the worker logs; re-raise a clean credential-free RuntimeError instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,14 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import hashlib
|
||||||
import os
|
import os
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
|
|
||||||
|
def _hashed_password(password: str) -> str:
|
||||||
|
"""streamrip's Qobuz email/password login expects the password as an MD5 hex digest."""
|
||||||
|
return hashlib.md5(password.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
class StreamripClient:
|
class StreamripClient:
|
||||||
"""Real Qobuz client backed by streamrip (v2, async → sync via asyncio.run).
|
"""Real Qobuz client backed by streamrip (v2, async → sync via asyncio.run).
|
||||||
|
|
||||||
@@ -23,7 +29,7 @@ class StreamripClient:
|
|||||||
cfg = Config.defaults()
|
cfg = Config.defaults()
|
||||||
cfg.session.qobuz.use_auth_token = False
|
cfg.session.qobuz.use_auth_token = False
|
||||||
cfg.session.qobuz.email_or_userid = self._email
|
cfg.session.qobuz.email_or_userid = self._email
|
||||||
cfg.session.qobuz.password_or_token = self._password
|
cfg.session.qobuz.password_or_token = _hashed_password(self._password)
|
||||||
cfg.session.qobuz.quality = 3
|
cfg.session.qobuz.quality = 3
|
||||||
if download_folder is not None:
|
if download_folder is not None:
|
||||||
cfg.session.downloads.folder = download_folder
|
cfg.session.downloads.folder = download_folder
|
||||||
@@ -37,7 +43,10 @@ class StreamripClient:
|
|||||||
|
|
||||||
client = QobuzClient(self._make_config())
|
client = QobuzClient(self._make_config())
|
||||||
try:
|
try:
|
||||||
await client.login()
|
try:
|
||||||
|
await client.login()
|
||||||
|
except Exception as e: # never let streamrip's credential-bearing error escape
|
||||||
|
raise RuntimeError(f"Qobuz login failed ({type(e).__name__}) — check credentials") from None
|
||||||
pages = await client.search("album", query, limit=5)
|
pages = await client.search("album", query, limit=5)
|
||||||
finally:
|
finally:
|
||||||
await client.session.close()
|
await client.session.close()
|
||||||
@@ -68,7 +77,10 @@ class StreamripClient:
|
|||||||
cfg = self._make_config(download_folder=dest)
|
cfg = self._make_config(download_folder=dest)
|
||||||
client = QobuzClient(cfg)
|
client = QobuzClient(cfg)
|
||||||
try:
|
try:
|
||||||
await client.login()
|
try:
|
||||||
|
await client.login()
|
||||||
|
except Exception as e: # never let streamrip's credential-bearing error escape
|
||||||
|
raise RuntimeError(f"Qobuz login failed ({type(e).__name__}) — check credentials") from None
|
||||||
on_progress(0.0)
|
on_progress(0.0)
|
||||||
db = Database(downloads=Dummy(), failed=Dummy())
|
db = Database(downloads=Dummy(), failed=Dummy())
|
||||||
pending = PendingAlbum(album_id, client, cfg, db)
|
pending = PendingAlbum(album_id, client, cfg, db)
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import hashlib
|
||||||
|
|
||||||
|
from lyra_worker.adapters._streamrip import StreamripClient, _hashed_password
|
||||||
|
|
||||||
|
|
||||||
|
def test_hashed_password_is_md5_hex():
|
||||||
|
# streamrip's Qobuz email/password login expects the MD5 hex digest, not plaintext.
|
||||||
|
assert _hashed_password("hunter2") == hashlib.md5(b"hunter2").hexdigest()
|
||||||
|
assert len(_hashed_password("anything")) == 32
|
||||||
|
assert all(c in "0123456789abcdef" for c in _hashed_password("anything"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_configured():
|
||||||
|
assert StreamripClient({"qobuz.email": "a@b.c", "qobuz.password": "p"}).is_configured() is True
|
||||||
|
assert StreamripClient({}).is_configured() is False
|
||||||
Reference in New Issue
Block a user