88from itertools import chain
99import asyncio
1010import logging
11+ import hashlib
12+ import hmac
13+ import json
1114
1215from aiohttp import ClientConnectionError
1316
1417from mautrix .types import (UserID , FilterID , Filter , RoomEventFilter , RoomFilter , EventFilter ,
1518 EventType , SyncToken , RoomID , Event , PresenceState )
1619from mautrix .appservice import AppService , IntentAPI
1720from mautrix .errors import IntentError , MatrixError , MatrixRequestError , MatrixInvalidToken
21+ from mautrix .api import Path
1822
1923from .matrix import BaseMatrixHandler
2024
@@ -60,6 +64,8 @@ class CustomPuppetMixin(ABC):
6064
6165 sync_with_custom_puppets : bool = True
6266 only_handle_own_synced_events : bool = True
67+ login_shared_secret : Optional [bytes ] = None
68+ login_device_name : Optional [str ] = None
6369
6470 az : AppService
6571 loop : asyncio .AbstractEventLoop
@@ -96,6 +102,33 @@ def _fresh_intent(self) -> IntentAPI:
96102 return (self .az .intent .user (self .custom_mxid , self .access_token )
97103 if self .is_real_user else self .default_mxid_intent )
98104
105+ def can_auto_login (self , mxid : UserID ) -> bool :
106+ if not self .login_shared_secret :
107+ return False
108+ _ , server = self .az .intent .parse_user_id (mxid )
109+ if server != self .az .domain :
110+ return False
111+ return True
112+
113+ async def _login_with_shared_secret (self , mxid : UserID ) -> Optional [str ]:
114+ if not self .can_auto_login (mxid ):
115+ return None
116+ password = hmac .new (self .login_shared_secret , mxid .encode ("utf-8" ),
117+ hashlib .sha512 ).hexdigest ()
118+ url = self .az .intent .api .base_url + str (Path .login )
119+ resp = await self .az .http_session .post (url , data = json .dumps ({
120+ "type" : "m.login.password" ,
121+ "initial_device_display_name" : self .login_device_name ,
122+ "device_id" : self .login_device_name ,
123+ "identifier" : {
124+ "type" : "m.id.user" ,
125+ "user" : mxid ,
126+ },
127+ "password" : password
128+ }), headers = {"Content-Type" : "application/json" })
129+ data = await resp .json ()
130+ return data ["access_token" ]
131+
99132 async def switch_mxid (self , access_token : Optional [str ], mxid : Optional [UserID ]) -> None :
100133 """
101134 Switch to a real Matrix user or away from one.
@@ -106,6 +139,11 @@ async def switch_mxid(self, access_token: Optional[str], mxid: Optional[UserID])
106139 mxid: The expected Matrix user ID of the custom account, or ``None`` when
107140 ``access_token`` is None.
108141 """
142+ if access_token == "auto" :
143+ access_token = await self ._login_with_shared_secret (mxid )
144+ if not access_token :
145+ raise ValueError ("Failed to log in with shared secret" )
146+ self .log .debug (f"Logged in for { mxid } using shared secret" )
109147 prev_mxid = self .custom_mxid
110148 self .custom_mxid = mxid
111149 self .access_token = access_token
0 commit comments