feat: token-based auth (replace deprecated email/password login)#1
Open
guyathomas wants to merge 2 commits into
Open
feat: token-based auth (replace deprecated email/password login)#1guyathomas wants to merge 2 commits into
guyathomas wants to merge 2 commits into
Conversation
Qobuz disabled email/password authentication server-side in April 2026.
This commit migrates qobuz-dl to a token-based flow where the user
captures `user_id` and `user_auth_token` from a logged-in browser
session at play.qobuz.com and pastes them into the CLI.
Implementation
--------------
* qopy.Client: constructor no longer authenticates; new
`Client.from_token(user_id, user_auth_token, app_id, secrets)`
classmethod hits the `user/login` token endpoint and configures the
session. Old `Client(email, pwd, ...)` arity now raises TypeError.
* qopy: token-leak hardening across the request surface — all errors
raised from `_authenticate_with_token` and `api_call` strip
`request.url` (which embeds the token in the query string) via
`from None` chains and a sanitized HTTPError replacement for
`raise_for_status()`. `(connect, read)` timeouts on every request.
* qopy: `cfg_setup()` failures (the new April-2026 SHA-256/HKDF
signing rollout breaks the MD5-signed `track/getFileUrl` probe) are
downgraded to a logged warning so login + metadata/search still work.
* qopy: `_extract_label` falls back through credential.parameters
shapes to "Unknown" rather than KeyError on response drift.
* core: `initialize_client_with_token` replaces `initialize_client`.
* cli: `_reset_config` rewritten — prompts for user_id (input) and
user_auth_token (getpass, cross-platform: termios POSIX / msvcrt
Windows) so the token never hits terminal scrollback. Persists via
the new atomic writer.
* cli: `main()` dispatch reordered — `--reset`, `--show-config`, and
`--purge` short-circuit BEFORE the strict config-load so a corrupt
config can still be inspected and recovered. Auth errors from
`from_token` are caught and exit cleanly with the (token-free)
message.
* cli: corrupt-config error message surfaces the exception TYPE only;
configparser.ParsingError can echo the offending source line which
may include `user_auth_token = ...`.
* cli: download subcommands (`dl`, `fun`, `lucky`) exit cleanly with
code 3 + README pointer when `client.sec is None` (the broken-signing
case), instead of tracebacking from InvalidAppSecretError mid-download.
* cli: `--show-config` redacts `user_auth_token`, `secrets`, and the
legacy `password` key by default; `-S/--show-secrets` opt-in reveals
them. Round-trips through configparser so multi-line continuation
values are fully redacted; falls back to a regex pass for malformed
input. `app_id` stays visible (public constant in bundle.js).
* _config_io: new module providing `atomic_write_config(path, parser)`
— tempfile-in-same-dir → fsync(fd) → close → os.replace → chmod 0600
on POSIX → fsync(parent dir). Best-effort cleanup on failure leaves
the previous credentials store intact.
* cli: `quality_fallback` boolean wiring fixed — was `(not A) or
(not B)` which made `--no-fallback` only effective when both config
and CLI agreed.
* commands: `-S/--show-secrets` flag added; `-sc/--show-config` help
text notes default redaction.
* __init__: package docstring documents the migration; `__all__` set.
* setup.py: version 0.9.10.0; `python_requires>=3.9` (3.8 EOL Oct
2024); `extras_require={"dev": [...]}`; tests excluded from package.
* docs: README rewritten — token-capture flow uses a console
one-liner (`JSON.parse(localStorage.getItem('localuser'))`) with an
Application → Local Storage fallback. Status banner notes downloads
verified end-to-end; documents the InvalidAppSecretError clean-exit
behaviour for accounts hit by the partial signing rollout.
* .gitignore: cover *.pyc, *.egg-info, .venv, .pytest_cache, build,
dist.
Adds pytest configuration, fixtures, and tests covering every behavior introduced in the token-auth migration. Targets ~security-critical and regression-prone surfaces identified by the review pipeline. Configuration ------------- * pytest.ini: testpaths=tests, strict markers. * tests/conftest.py: bundle_snippet + tmp_config_dir fixtures. * tests/fixtures/: live bundle.js excerpt (canary for app_id regex and `localuser` storage marker), and four `user/login` response shape variants (studio, credential-label-only, no-short-label, free-account). Coverage -------- * test_qopy.py: Client.from_token happy path, query-param shape, label extraction tolerance (parametrized), 401/400 error mapping, free-account rejection. Token-leak guards: 5xx response, transport ConnectionError, and api_call HTTPError on token-bearing endpoints all checked against str/repr/log/__context__. cfg_setup downgrade paths for both InvalidAppSecretError and RequestException. * test_cli_main.py: dispatch precedence (token > legacy > none), corrupt-config exit hygiene (raw error not echoed; UnicodeDecodeError handled), stale-token clean exit, --show-config / --purge work even on corrupt config, parametrized quality_fallback wiring across config-only / CLI-only / both-false (regression for the boolean bug), download-without-secret guard. * test_cli_reset.py: token keys persisted, no email/password written, getpass used for token (regression guard), atomic_write_config is the writer. * test_config_io.py: writes content correctly, sets 0600 on POSIX (skipif Windows), preserves original on os.replace failure, cleans temp on parser.write or fsync failure, chmod-failure-after-replace contract pinned. * test_show_config.py: redacts user_auth_token / secrets / legacy password by default; --show-secrets reveals; multi-line continuation values fully redacted; benign text containing the word "password" is not over-matched; app_id stays visible. * test_bundle_regex.py: live-bundle canary for the app_id regex and the `localuser` localStorage marker.
8b96574 to
00f8ce2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Qobuz disabled email/password login server-side in April 2026.
qobuz-dlno longer authenticates.What
Replace email/password with token-based login. User pastes
user_id+user_auth_tokenfrom a logged-in browser session (one-liner in the DevTools console) and the CLI persists them at~/.config/qobuz-dl/config.ini(mode0600on POSIX).Client.from_token(...)classmethod is the new auth entry point;Client(email, pwd, ...)raisesTypeError.getpassfor the token prompt.--show-configredacts secrets by default;-S/--show-secretsopt-in.from Nonechains, sanitizedHTTPError).Testing