-
Type:
Bug
-
Resolution: Unresolved
-
Priority:
High
-
None
-
Component/s: API, good-first-bug
-
None
Problem
Every API request authenticated with a MetaBrainz (meba_) access token fails with 401 Invalid authorization token, even when the token is active and carries the right scopes.
validate_auth_header() in listenbrainz/webserver/views/api_tools.py resolves the token with:
user = db_user.get_by_mb_id(db_conn, token["sub"], fetch_email=fetch_email)
get_by_mb_id() matches against the musicbrainz_id column, so it expects a MusicBrainz username. The introspection response does not put the username in sub.
Evidence
A live token introspected against https://metabrainz.org/oauth2/introspect :
{"active": true, "client_id": "...", "token_type": "Bearer",
"scope": ["profile", "musicbrainz:collection", "musicbrainz:tag", "musicbrainz:rating"],
"sub": 2907893, "username": "narendev",
"issued_by": "https://metabrainz.org/", "issued_at": 1791654824, "expires_at": 1791658424}
sub is the numeric MetaBrainz account id, the username sits in username. The lookup therefore never matches.
The same token works against the MusicBrainz web service, which maps the musicbrainz:* scopes and resolves the user correctly, so the token itself is fine.
Why the tests did not catch it
/oauth2/userinfo uses the opposite convention, there sub holds the username and the numeric id is in metabrainz_user_id:
{"metabrainz_user_id": 2907893, "sub": "narendev", "profile": "https://musicbrainz.org/user/narendev", "zoneinfo": "UTC"}
The mocks in test_oauth_invalid_access_token were written in that shape ("metabrainz_user_id": 123, "sub": "lucifer"), so they do not reflect the introspection endpoint. The test also only covers rejections, and its third case stops at the scope check before the user lookup is reached.
Fix
Pull request: https://github.com/metabrainz/listenbrainz-server/pull/3921