Skip to content

Commit 0abb102

Browse files
authored
Hotfix access token structure (#264)
* Do not expect tokens to start with dot * Compress only tokens that start with dot
1 parent 423f76c commit 0abb102

File tree

2 files changed

+44
-8
lines changed

2 files changed

+44
-8
lines changed

mergin/client.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,30 @@ class ServerType(Enum):
6262

6363

6464
def decode_token_data(token):
65-
token_prefix = "Bearer ."
65+
token_prefix = "Bearer "
6666
if not token.startswith(token_prefix):
67-
raise TokenError(f"Token doesn't start with 'Bearer .': {token}")
67+
raise TokenError(f"Token doesn't start with 'Bearer ': {token}")
6868
try:
69-
data = token[len(token_prefix) :].split(".")[0]
70-
# add proper base64 padding"
71-
data += "=" * (-len(data) % 4)
72-
decoded = zlib.decompress(base64.urlsafe_b64decode(data))
73-
return json.loads(decoded)
69+
token_raw = token[len(token_prefix) :]
70+
is_compressed = False
71+
72+
# compressed tokens start with dot,
73+
# see https://github.com/pallets/itsdangerous/blob/main/src/itsdangerous/url_safe.py#L55
74+
if token_raw.startswith("."):
75+
token_raw = token_raw.lstrip(".")
76+
is_compressed = True
77+
78+
payload_raw = token_raw.split(".")[0]
79+
80+
# add proper base64 padding
81+
payload_raw += "=" * (-len(payload_raw) % 4)
82+
payload_data = base64.urlsafe_b64decode(payload_raw)
83+
84+
if is_compressed:
85+
payload_data = zlib.decompress(payload_data)
86+
87+
return json.loads(payload_data)
88+
7489
except (IndexError, TypeError, ValueError, zlib.error):
7590
raise TokenError(f"Invalid token data: {token}")
7691

mergin/test/test_client.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,37 @@ def test_login(mc):
152152
assert MerginClient(mc.url, auth_token=token)
153153

154154
invalid_token = "Completely invalid token...."
155-
with pytest.raises(TokenError, match=f"Token doesn't start with 'Bearer .': {invalid_token}"):
155+
with pytest.raises(TokenError, match=f"Token doesn't start with 'Bearer ': {invalid_token}"):
156156
decode_token_data(invalid_token)
157157

158158
invalid_token = "Bearer .jas646kgfa"
159159
with pytest.raises(TokenError, match=f"Invalid token data: {invalid_token}"):
160160
decode_token_data(invalid_token)
161161

162+
invalid_token = "Bearer jas646kgfa"
163+
with pytest.raises(TokenError, match=f"Invalid token data: {invalid_token}"):
164+
decode_token_data(invalid_token)
165+
162166
with pytest.raises(LoginError, match="Invalid username or password"):
163167
mc.login("foo", "bar")
164168

169+
valid_token_dot = "Bearer .eJxNi0kKgDAMAL8iubqQRuuSkz-RojkEWi0uIIh_Fz15G2aYC45N1kEnYJN9PLsgwOCijl5l3iEDCU793_VyuhC9FOMS3n5GXd-JkGyObU6UmI6pZoNFZRtbUorIiHA_KFshoA.abc.def"
170+
decoded_value = decode_token_data(valid_token_dot)
171+
172+
# expected: {'user_id': 1, 'username': 'apiclient', 'email': 'apiclient@example.com', 'expire': '2025-08-22 19:26:10.457532+00:00'}
173+
assert decoded_value["user_id"] == 1
174+
assert decoded_value["username"] == "apiclient"
175+
assert decoded_value["email"] == "apiclient@example.com"
176+
assert decoded_value["expire"] == "2025-08-22 19:26:10.457532+00:00"
177+
178+
valid_token_nodot = "Bearer eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFwaSIsImVtYWlsIjoiYXBpQGUuY29tIn0.abc.def"
179+
decoded_value = decode_token_data(valid_token_nodot)
180+
181+
# expected: {'user_id': 1, 'username': 'api', 'email': 'api@e.com'}
182+
assert decoded_value["user_id"] == 1
183+
assert decoded_value["username"] == "api"
184+
assert decoded_value["email"] == "api@e.com"
185+
165186

166187
def test_create_delete_project(mc: MerginClient):
167188
test_project = "test_create_delete"

0 commit comments

Comments
 (0)