Improve config comments and errors with mbc auth
This commit is contained in:
parent
61711e8329
commit
8c3e3a3255
5 changed files with 50 additions and 31 deletions
|
@ -32,7 +32,7 @@ enc = functools.partial(quote, safe="")
|
|||
friendly_errors = {
|
||||
"server_not_found": "Registration target server not found.\n\n"
|
||||
"To log in or register through maubot, you must add the server to the\n"
|
||||
"registration_secrets section in the config. If you only want to log in,\n"
|
||||
"homeservers section in the config. If you only want to log in,\n"
|
||||
"leave the `secret` field empty."
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,10 @@ class Config(BaseFileConfig):
|
|||
base["server.unshared_secret"] = self._new_token()
|
||||
else:
|
||||
base["server.unshared_secret"] = shared_secret
|
||||
copy("registration_secrets")
|
||||
if "registration_secrets" in self:
|
||||
base["homeservers"] = self["registration_secrets"]
|
||||
else:
|
||||
copy("homeservers")
|
||||
copy("admins")
|
||||
for username, password in base["admins"].items():
|
||||
if password and not bcrypt_regex.match(password):
|
||||
|
|
|
@ -42,13 +42,18 @@ server:
|
|||
# Set to "generate" to generate and save a new token at startup.
|
||||
unshared_secret: generate
|
||||
|
||||
# Shared registration secrets to allow registering new users from the management UI
|
||||
registration_secrets:
|
||||
example.com:
|
||||
# Known homeservers. This is required for the `mbc auth` command and also allows
|
||||
# more convenient access from the management UI. This is not required to create
|
||||
# clients in the management UI, since you can also just type the homeserver URL
|
||||
# into the box there.
|
||||
homeservers:
|
||||
matrix.org:
|
||||
# Client-server API URL
|
||||
url: https://example.com
|
||||
url: https://matrix-client.matrix.org
|
||||
# registration_shared_secret from synapse config
|
||||
secret: synapse_shared_registration_secret
|
||||
# You can leave this empty if you don't have access to the homeserver.
|
||||
# When this is empty, `mbc auth --register` won't work, but `mbc auth` (login) will.
|
||||
secret: null
|
||||
|
||||
# List of administrator users. Plaintext passwords will be bcrypted on startup. Set empty password
|
||||
# to prevent normal login. Root is a special user that can't have a password and will always exist.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# maubot - A plugin-based Matrix bot system.
|
||||
# Copyright (C) 2019 Tulir Asokan
|
||||
# Copyright (C) 2021 Tulir Asokan
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
|
@ -22,30 +22,36 @@ import string
|
|||
import hmac
|
||||
|
||||
from aiohttp import web
|
||||
from mautrix.api import HTTPAPI, Path, SynapseAdminPath, Method
|
||||
from mautrix.api import SynapseAdminPath, Method
|
||||
from mautrix.errors import MatrixRequestError
|
||||
from mautrix.client import ClientAPI
|
||||
from mautrix.types import LoginType
|
||||
|
||||
from .base import routes, get_config, get_loop
|
||||
from .responses import resp
|
||||
|
||||
|
||||
def registration_secrets() -> Dict[str, Dict[str, str]]:
|
||||
return get_config()["registration_secrets"]
|
||||
def known_homeservers() -> Dict[str, Dict[str, str]]:
|
||||
return get_config()["homeservers"]
|
||||
|
||||
|
||||
@routes.get("/client/auth/servers")
|
||||
async def get_registerable_servers(_: web.Request) -> web.Response:
|
||||
return web.json_response({key: value["url"] for key, value in registration_secrets().items()})
|
||||
async def get_known_servers(_: web.Request) -> web.Response:
|
||||
return web.json_response({key: value["url"] for key, value in known_homeservers().items()})
|
||||
|
||||
|
||||
AuthRequestInfo = NamedTuple("AuthRequestInfo", api=HTTPAPI, secret=str, username=str,
|
||||
password=str, user_type=str)
|
||||
class AuthRequestInfo(NamedTuple):
|
||||
client: ClientAPI
|
||||
secret: str
|
||||
username: str
|
||||
password: str
|
||||
user_type: str
|
||||
|
||||
|
||||
async def read_client_auth_request(request: web.Request) -> Tuple[Optional[AuthRequestInfo],
|
||||
Optional[web.Response]]:
|
||||
server_name = request.match_info.get("server", None)
|
||||
server = registration_secrets().get(server_name, None)
|
||||
server = known_homeservers().get(server_name, None)
|
||||
if not server:
|
||||
return None, resp.server_not_found
|
||||
try:
|
||||
|
@ -59,10 +65,10 @@ async def read_client_auth_request(request: web.Request) -> Tuple[Optional[AuthR
|
|||
return None, resp.username_or_password_missing
|
||||
try:
|
||||
base_url = server["url"]
|
||||
secret = server["secret"]
|
||||
except KeyError:
|
||||
return None, resp.invalid_server
|
||||
api = HTTPAPI(base_url, "", loop=get_loop())
|
||||
secret = server.get("secret")
|
||||
api = ClientAPI(base_url=base_url, loop=get_loop())
|
||||
user_type = body.get("user_type", "bot")
|
||||
return AuthRequestInfo(api, secret, username, password, user_type), None
|
||||
|
||||
|
@ -88,9 +94,12 @@ async def register(request: web.Request) -> web.Response:
|
|||
info, err = await read_client_auth_request(request)
|
||||
if err is not None:
|
||||
return err
|
||||
api, secret, username, password, user_type = info
|
||||
client: ClientAPI
|
||||
client, secret, username, password, user_type = info
|
||||
if not secret:
|
||||
return resp.registration_secret_not_found
|
||||
path = SynapseAdminPath.v1.register
|
||||
res = await api.request(Method.GET, path)
|
||||
res = await client.api.request(Method.GET, path)
|
||||
content = {
|
||||
"nonce": res["nonce"],
|
||||
"username": username,
|
||||
|
@ -100,7 +109,7 @@ async def register(request: web.Request) -> web.Response:
|
|||
"user_type": user_type,
|
||||
}
|
||||
try:
|
||||
return web.json_response(await api.request(Method.POST, path, content=content))
|
||||
return web.json_response(await client.api.request(Method.POST, path, content=content))
|
||||
except MatrixRequestError as e:
|
||||
return web.json_response({
|
||||
"errcode": e.errcode,
|
||||
|
@ -114,18 +123,13 @@ async def login(request: web.Request) -> web.Response:
|
|||
info, err = await read_client_auth_request(request)
|
||||
if err is not None:
|
||||
return err
|
||||
api, _, username, password, _ = info
|
||||
device_id = ''.join(random.choices(string.ascii_uppercase + string.digits, k=8))
|
||||
client = info.client
|
||||
try:
|
||||
return web.json_response(await api.request(Method.POST, Path.login, content={
|
||||
"type": "m.login.password",
|
||||
"identifier": {
|
||||
"type": "m.id.user",
|
||||
"user": username,
|
||||
},
|
||||
"password": password,
|
||||
"device_id": f"maubot_{device_id}",
|
||||
}))
|
||||
res = await client.login(identifier=info.username, login_type=LoginType.PASSWORD,
|
||||
password=info.password, device_id=f"maubot_{device_id}",
|
||||
initial_device_display_name="Maubot", store_access_token=False)
|
||||
return web.json_response(res.serialize())
|
||||
except MatrixRequestError as e:
|
||||
return web.json_response({
|
||||
"errcode": e.errcode,
|
||||
|
|
|
@ -180,6 +180,13 @@ class _Response:
|
|||
"errcode": "server_not_found",
|
||||
}, status=HTTPStatus.NOT_FOUND)
|
||||
|
||||
@property
|
||||
def registration_secret_not_found(self) -> web.Response:
|
||||
return web.json_response({
|
||||
"error": "Config does not have a registration secret for that server",
|
||||
"errcode": "registration_secret_not_found",
|
||||
}, status=HTTPStatus.NOT_FOUND)
|
||||
|
||||
@property
|
||||
def plugin_has_no_database(self) -> web.Response:
|
||||
return web.json_response({
|
||||
|
|
Loading…
Reference in a new issue