Configuration options for MAS

In order to create and manage users with MAS enabled, we'll need to
reach out to the MAS admin API. We can do so automatically by requesting
an admin-enabled token, assuming a matching client has been configured
on the MAS side.

Add some config options for the guest module (OAuth2 client) side.
This commit is contained in:
Andrew Morgan
2026-01-14 15:45:46 +00:00
parent cf7d63acfd
commit 1cb888e4e7
3 changed files with 95 additions and 3 deletions
@@ -41,6 +41,17 @@ The module provides (optional) configuration options:
- `enable_user_reaper` - if true, the module disables all users that are older than the configured expiration time. Default: `true`.
- `user_expiration_seconds` - the expiration time in seconds when a guest user expires after their creation. Default: `86400` (=24 hours).
If matrix-authentication-service (MAS) is configured, the module will need to
interface with it in order to register/deactivate users. Provide the below
options in order to give the module access to [MAS' Admin
API](https://element-hq.github.io/matrix-authentication-service/topics/admin-api.html).
- `mas` - optional configuration for Matrix Authentication Service (MAS). When set, the module creates users via MAS' admin API.
- `admin_api_base_url` - Base URL for MAS' admin API (e.g. `https://mas.example.org`). Trailing slashes will be automatically stripped.
- `oauth_base_url` - Base URL for MAS' OAuth endpoints (defaults to `admin_api_base_url` if not set). Trailing slashes will be automatically stripped.
- `client_id` - client ID for the automated tool. Must be a valid [ULID](https://github.com/ulid/spec). Generate one [here](https://ulidtools.com/).
- `client_secret` - client secret for the automated tool. Ideally long and cryptographically secure. Keep it a secret!
Example configuration:
```yaml
@@ -49,6 +60,37 @@ modules:
config:
# Use a german suffix
display_name_suffix: " (Gast)"
# The below is required if using MAS
mas:
admin_api_base_url: https://mas.example.org
oauth_base_url: https://mas.example.org
# The `client_id` must be a valid ULID:
# https://github.com/ulid/spec
# Generate ULID's easily at:
# https://ulidtools.com/
client_id: 000000000000000000000G0EST
client_secret: your-client-secret
```
Enable [the Admin API on a MAS
listener](https://element-hq.github.io/matrix-authentication-service/topics/admin-api.html#enabling-the-api).
Then, add the following to your MAS config file:
```yaml
policy:
data:
admin_clients:
- 000000000000000000000G0EST
# ...
clients:
# The `client_id` must be a valid ULID https://github.com/ulid/spec
# Generate ULID's easily at: https://ulidtools.com/
- client_id: 000000000000000000000G0EST
# The guest module uses the client_secret_basic authentication method.
client_auth_method: client_secret_basic
client_secret: your-client-secret
```
## Production installation
@@ -7,12 +7,24 @@
# Originally licensed under the Apache License, Version 2.0:
# <http://www.apache.org/licenses/LICENSE-2.0>.
from typing import Optional
import attr
@attr.s(frozen=True, auto_attribs=True)
class MasConfig:
admin_api_base_url: str
oauth_base_url: str
client_id: str
# TODO: Add a filepath option for the secret as well.
client_secret: str
@attr.s(frozen=True, auto_attribs=True)
class GuestModuleConfig:
user_id_prefix: str
display_name_suffix: str
enable_user_reaper: bool
user_expiration_seconds: int
mas: Optional[MasConfig] = None
@@ -8,7 +8,7 @@
# <http://www.apache.org/licenses/LICENSE-2.0>.
import logging
from typing import Any, Dict, Literal, Tuple, Union
from typing import Any, Dict, Literal, Optional, Tuple, Union
from synapse.module_api import (
NOT_SPAM,
@@ -21,7 +21,8 @@ from synapse.module_api import (
from synapse.module_api.errors import ConfigError
from synapse.types import UserID
from synapse_guest_module.config import GuestModuleConfig
from synapse_guest_module.config import GuestModuleConfig, MasConfig
from synapse_guest_module.mas_admin_client import MasAdminClient
from synapse_guest_module.guest_registration_servlet import GuestRegistrationServlet
from synapse_guest_module.guest_user_reaper import GuestUserReaper
@@ -33,7 +34,12 @@ class GuestModule:
self._api = api
self._config = config
self.registration_servlet = GuestRegistrationServlet(config, api)
mas_admin_client = (
MasAdminClient(api, config.mas) if config.mas is not None else None
)
self.registration_servlet = GuestRegistrationServlet(
config, api, mas_admin_client
)
self._api.register_web_resource(
"/_synapse/client/register_guest", self.registration_servlet
)
@@ -81,11 +87,43 @@ class GuestModule:
"Config option 'user_expiration_seconds' must be a number"
)
mas_config = config.get("mas")
mas: Optional[MasConfig] = None
if mas_config is not None:
if not isinstance(mas_config, dict):
raise ConfigError("Config option 'mas' must be an object")
admin_api_base_url = mas_config.get("admin_api_base_url")
if not isinstance(admin_api_base_url, str) or len(admin_api_base_url.strip()) == 0:
raise ConfigError("Config option 'mas.admin_api_base_url' is required and must be a string")
oauth_base_url = mas_config.get("oauth_base_url", admin_api_base_url)
if not isinstance(oauth_base_url, str) or len(oauth_base_url.strip()) == 0:
raise ConfigError(
"Config option 'mas.oauth_base_url' must be a string"
)
client_id = mas_config.get("client_id")
if not isinstance(client_id, str) or len(client_id.strip()) == 0:
raise ConfigError("Config option 'mas.client_id' is required and must be a string")
client_secret = mas_config.get("client_secret")
if not isinstance(client_secret, str) or len(client_secret.strip()) == 0:
raise ConfigError("Config option 'mas.client_secret' is required and must be a string")
mas = MasConfig(
admin_api_base_url.strip(),
oauth_base_url.strip(),
client_id.strip(),
client_secret.strip(),
)
return GuestModuleConfig(
user_id_prefix,
display_name_suffix,
enable_user_reaper,
user_expiration_seconds,
mas,
)
async def profile_update(