Skip to content

Commit 5a441ee

Browse files
committed
Reject reusing access tokens when enabling double puppeting
1 parent 8fa1c7e commit 5a441ee

File tree

3 files changed

+48
-30
lines changed

3 files changed

+48
-30
lines changed

mautrix/bridge/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
AutologinError,
66
CustomPuppetError,
77
CustomPuppetMixin,
8+
EncryptionKeysFound,
89
HomeserverURLNotFound,
910
InvalidAccessToken,
1011
OnlyLoginSelf,

mautrix/bridge/commands/login_matrix.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,7 @@
66
from mautrix.client import Client
77
from mautrix.types import EventID
88

9-
from ..custom_puppet import (
10-
AutologinError,
11-
HomeserverURLNotFound,
12-
InvalidAccessToken,
13-
OnlyLoginSelf,
14-
OnlyLoginTrustedDomain,
15-
)
9+
from ..custom_puppet import AutologinError, CustomPuppetError, InvalidAccessToken
1610
from .handler import SECTION_AUTH, CommandEvent, command_handler
1711

1812

@@ -36,20 +30,10 @@ async def login_matrix(evt: CommandEvent) -> None:
3630
try:
3731
await puppet.switch_mxid(evt.args[0], evt.sender.mxid)
3832
await evt.reply("Successfully enabled double puppeting.")
39-
except OnlyLoginSelf:
40-
await evt.reply("You may only enable double puppeting with your own Matrix account.")
41-
except OnlyLoginTrustedDomain:
42-
await evt.reply(f"This bridge does not allow double puppeting from {homeserver}.")
43-
except HomeserverURLNotFound:
44-
await evt.reply(
45-
f"Unable to find the base URL for {homeserver}. Please ensure a client"
46-
" .well-known file is set up, or ask the bridge administrator to add the"
47-
" homeserver URL to the bridge config."
48-
)
4933
except AutologinError as e:
5034
await evt.reply(f"Failed to create an access token: {e}")
51-
except InvalidAccessToken:
52-
await evt.reply("Invalid access token.")
35+
except CustomPuppetError as e:
36+
await evt.reply(str(e))
5337

5438

5539
@command_handler(

mautrix/bridge/custom_puppet.py

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,24 @@ def __init__(self):
5656

5757
class OnlyLoginSelf(CustomPuppetError):
5858
def __init__(self):
59-
super().__init__("You may only replace your puppet with your own Matrix account.")
59+
super().__init__("You may only enable double puppeting with your own Matrix account.")
60+
61+
62+
class EncryptionKeysFound(CustomPuppetError):
63+
def __init__(self):
64+
super().__init__(
65+
"The given access token is for a device that has encryption keys set up. "
66+
"Please provide a fresh token, don't reuse one from another client."
67+
)
6068

6169

6270
class HomeserverURLNotFound(CustomPuppetError):
6371
def __init__(self, domain: str):
64-
super().__init__(f"Could not discover a valid homeserver URL for {domain}")
72+
super().__init__(
73+
f"Could not discover a valid homeserver URL for {domain}."
74+
" Please ensure a client .well-known file is set up, or ask the bridge administrator "
75+
"to add the homeserver URL to the bridge config."
76+
)
6577

6678

6779
class OnlyLoginTrustedDomain(CustomPuppetError):
@@ -235,7 +247,7 @@ async def switch_mxid(
235247
self.base_url = base_url
236248
self.intent = self._fresh_intent()
237249

238-
await self.start(start_sync_task=start_sync_task)
250+
await self.start(start_sync_task=start_sync_task, check_e2ee_keys=True)
239251

240252
try:
241253
del self.by_custom_mxid[prev_mxid]
@@ -255,7 +267,21 @@ async def try_start(self, retry_auto_login: bool = True) -> None:
255267
except Exception:
256268
self.log.exception("Failed to initialize custom mxid")
257269

258-
async def start(self, retry_auto_login: bool = False, start_sync_task: bool = True) -> None:
270+
async def _invalidate_double_puppet(self) -> None:
271+
if self.custom_mxid and self.by_custom_mxid.get(self.custom_mxid) == self:
272+
del self.by_custom_mxid[self.custom_mxid]
273+
self.custom_mxid = None
274+
self.access_token = None
275+
self.next_batch = None
276+
await self.save()
277+
self.intent = self._fresh_intent()
278+
279+
async def start(
280+
self,
281+
retry_auto_login: bool = False,
282+
start_sync_task: bool = True,
283+
check_e2ee_keys: bool = False,
284+
) -> None:
259285
"""Initialize the custom account this puppet uses. Should be called at startup to start
260286
the /sync task. Is called by :meth:`switch_mxid` automatically."""
261287
if not self.is_real_user:
@@ -271,17 +297,24 @@ async def start(self, retry_auto_login: bool = False, start_sync_task: bool = Tr
271297
self.log.warning(f"Got {e.errcode} while trying to initialize custom mxid")
272298
whoami = None
273299
if not whoami or whoami.user_id != self.custom_mxid:
274-
if self.custom_mxid and self.by_custom_mxid.get(self.custom_mxid) == self:
275-
del self.by_custom_mxid[self.custom_mxid]
276300
prev_custom_mxid = self.custom_mxid
277-
self.custom_mxid = None
278-
self.access_token = None
279-
self.next_batch = None
280-
await self.save()
281-
self.intent = self._fresh_intent()
301+
await self._invalidate_double_puppet()
282302
if whoami and whoami.user_id != prev_custom_mxid:
283303
raise OnlyLoginSelf()
284304
raise InvalidAccessToken()
305+
if check_e2ee_keys:
306+
try:
307+
devices = await self.intent.query_keys({whoami.user_id: [whoami.device_id]})
308+
device_keys = devices.device_keys.get(whoami.user_id, {}).get(whoami.device_id)
309+
except Exception:
310+
self.log.warning(
311+
"Failed to query keys to check if double puppeting token was reused",
312+
exc_info=True,
313+
)
314+
else:
315+
if device_keys and len(device_keys.keys) > 0:
316+
await self._invalidate_double_puppet()
317+
raise EncryptionKeysFound()
285318
if self.sync_with_custom_puppets and start_sync_task:
286319
if self._sync_task:
287320
self._sync_task.cancel()

0 commit comments

Comments
 (0)