Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions mergin/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")

Expand Down
23 changes: 22 additions & 1 deletion mergin/test/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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': '[email protected]', '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"] == "[email protected]"
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': '[email protected]'}
assert decoded_value["user_id"] == 1
assert decoded_value["username"] == "api"
assert decoded_value["email"] == "[email protected]"


def test_create_delete_project(mc: MerginClient):
test_project = "test_create_delete"
Expand Down