diff --git a/mergin/client.py b/mergin/client.py index e9d0b21e..75f34026 100644 --- a/mergin/client.py +++ b/mergin/client.py @@ -62,15 +62,30 @@ class ServerType(Enum): def decode_token_data(token): - token_prefix = "Bearer ." + token_prefix = "Bearer " if not token.startswith(token_prefix): - raise TokenError(f"Token doesn't start with 'Bearer .': {token}") + raise TokenError(f"Token doesn't start with 'Bearer ': {token}") try: - data = token[len(token_prefix) :].split(".")[0] - # add proper base64 padding" - data += "=" * (-len(data) % 4) - decoded = zlib.decompress(base64.urlsafe_b64decode(data)) - return json.loads(decoded) + token_raw = token[len(token_prefix) :] + is_compressed = False + + # compressed tokens start with dot, + # see https://github.com/pallets/itsdangerous/blob/main/src/itsdangerous/url_safe.py#L55 + if token_raw.startswith("."): + token_raw = token_raw.lstrip(".") + is_compressed = True + + payload_raw = token_raw.split(".")[0] + + # add proper base64 padding + payload_raw += "=" * (-len(payload_raw) % 4) + payload_data = base64.urlsafe_b64decode(payload_raw) + + if is_compressed: + payload_data = zlib.decompress(payload_data) + + return json.loads(payload_data) + except (IndexError, TypeError, ValueError, zlib.error): raise TokenError(f"Invalid token data: {token}") diff --git a/mergin/test/test_client.py b/mergin/test/test_client.py index 32bc192f..43263929 100644 --- a/mergin/test/test_client.py +++ b/mergin/test/test_client.py @@ -152,16 +152,37 @@ def test_login(mc): assert MerginClient(mc.url, auth_token=token) invalid_token = "Completely invalid token...." - with pytest.raises(TokenError, match=f"Token doesn't start with 'Bearer .': {invalid_token}"): + with pytest.raises(TokenError, match=f"Token doesn't start with 'Bearer ': {invalid_token}"): decode_token_data(invalid_token) invalid_token = "Bearer .jas646kgfa" with pytest.raises(TokenError, match=f"Invalid token data: {invalid_token}"): decode_token_data(invalid_token) + invalid_token = "Bearer jas646kgfa" + with pytest.raises(TokenError, match=f"Invalid token data: {invalid_token}"): + decode_token_data(invalid_token) + with pytest.raises(LoginError, match="Invalid username or password"): mc.login("foo", "bar") + valid_token_dot = "Bearer .eJxNi0kKgDAMAL8iubqQRuuSkz-RojkEWi0uIIh_Fz15G2aYC45N1kEnYJN9PLsgwOCijl5l3iEDCU793_VyuhC9FOMS3n5GXd-JkGyObU6UmI6pZoNFZRtbUorIiHA_KFshoA.abc.def" + decoded_value = decode_token_data(valid_token_dot) + + # expected: {'user_id': 1, 'username': 'apiclient', 'email': 'apiclient@example.com', 'expire': '2025-08-22 19:26:10.457532+00:00'} + assert decoded_value["user_id"] == 1 + assert decoded_value["username"] == "apiclient" + assert decoded_value["email"] == "apiclient@example.com" + assert decoded_value["expire"] == "2025-08-22 19:26:10.457532+00:00" + + valid_token_nodot = "Bearer eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFwaSIsImVtYWlsIjoiYXBpQGUuY29tIn0.abc.def" + decoded_value = decode_token_data(valid_token_nodot) + + # expected: {'user_id': 1, 'username': 'api', 'email': 'api@e.com'} + assert decoded_value["user_id"] == 1 + assert decoded_value["username"] == "api" + assert decoded_value["email"] == "api@e.com" + def test_create_delete_project(mc: MerginClient): test_project = "test_create_delete"