Create a personal session on MAS

In order to get an access token for a user, one needs to create a personal session on MAS.

We now do so, and extract the access token and device ID from the response.

TODO: We're handing back the MAS user ID as the device ID. Is that correct?
This commit is contained in:
Andrew Morgan
2026-01-16 18:05:33 +00:00
parent 9c2b9463a9
commit 801d28f3ca
2 changed files with 71 additions and 5 deletions
@@ -74,13 +74,20 @@ class GuestRegistrationServlet(DirectServeJsonResource):
continue
if self._mas_admin_client is None:
logger.info("Registering local Synapse guest user with localpart '%s'", localpart)
logger.info(
"Registering local Synapse guest user with localpart '%s'",
localpart,
)
user_id = await self._api.register_user(
localpart, displayname + self._config.display_name_suffix
)
device_id, access_token, _, _ = await self._api.register_device(
user_id
)
else:
logger.info("Registering MAS guest user with username '%s'", localpart)
await self._mas_admin_client.create_user(localpart)
mas_user_id = await self._mas_admin_client.create_user(localpart)
user_id = self._api.get_qualified_user_id(localpart)
@@ -89,7 +96,20 @@ class GuestRegistrationServlet(DirectServeJsonResource):
displayname + self._config.display_name_suffix,
)
device_id, access_token, _, _ = await self._api.register_device(user_id)
# Determine how long to keep the access token valid for.
#
# If a user reaper is enabled, just have the token expire after
# the configured period.
expires_in = (
self._config.user_expiration_seconds
if self._config.enable_user_reaper
else 0
)
device_id, access_token = (
await self._mas_admin_client.create_personal_session(
mas_user_id, expires_in
)
)
logger.debug("Registered user '%s'", user_id)
@@ -22,15 +22,61 @@ class MasAdminClient:
self._admin_api_base_url = config.admin_api_base_url.rstrip("/")
self._oauth_base_url = config.oauth_base_url.rstrip("/")
async def create_user(self, username: str) -> None:
async def create_user(self, username: str) -> str:
"""Creates a new user in MAS with the given username.
Args:
username: The username (localpart) of the user to create.
Returns:
The MAS ID of the created user.
"""
token = await self.request_admin_token()
url = self._build_admin_url("/api/admin/v1/users")
await self._api.http_client.post_json_get_json(
response = await self._api.http_client.post_json_get_json(
uri=url,
post_json={"username": username},
headers={"Authorization": [f"Bearer {token}"]},
)
mas_user_id = response.get("data", {}).get("id")
if mas_user_id is None or not isinstance(mas_user_id, str):
raise ValueError("MAS user creation response missing `data.id` field")
return mas_user_id
async def create_personal_session(
self, mas_user_id: str, expires_in: int
) -> tuple[str, str]:
token = await self.request_admin_token()
url = self._build_admin_url("/api/admin/v1/personal-sessions")
request_body = {
"actor_user_id": mas_user_id,
"expires_in": expires_in,
"scope": "openid urn:matrix:client:api:*",
"human_name": "guest user",
}
response = await self._api.http_client.post_json_get_json(
uri=url,
post_json=request_body,
headers={"Authorization": [f"Bearer {token}"]},
)
data = response.get("data", {})
attributes = data.get("attributes", {}) if isinstance(data, dict) else {}
access_token = attributes.get("access_token")
# TODO: Is this the correct device ID?
device_id = data.get("id") if isinstance(data, dict) else None
if not isinstance(access_token, str) or len(access_token) == 0:
raise ValueError("MAS session response missing `access_token` field")
if not isinstance(device_id, str) or len(device_id) == 0:
raise ValueError("MAS session response missing device id")
return device_id, access_token
async def request_admin_token(self) -> str:
url = self._build_oauth_url("/oauth2/token")