Improve config comments and errors with mbc auth
This commit is contained in:
parent
61711e8329
commit
8c3e3a3255
@ -32,7 +32,7 @@ enc = functools.partial(quote, safe="")
|
|||||||
friendly_errors = {
|
friendly_errors = {
|
||||||
"server_not_found": "Registration target server not found.\n\n"
|
"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"
|
"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."
|
"leave the `secret` field empty."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,10 @@ class Config(BaseFileConfig):
|
|||||||
base["server.unshared_secret"] = self._new_token()
|
base["server.unshared_secret"] = self._new_token()
|
||||||
else:
|
else:
|
||||||
base["server.unshared_secret"] = shared_secret
|
base["server.unshared_secret"] = shared_secret
|
||||||
copy("registration_secrets")
|
if "registration_secrets" in self:
|
||||||
|
base["homeservers"] = self["registration_secrets"]
|
||||||
|
else:
|
||||||
|
copy("homeservers")
|
||||||
copy("admins")
|
copy("admins")
|
||||||
for username, password in base["admins"].items():
|
for username, password in base["admins"].items():
|
||||||
if password and not bcrypt_regex.match(password):
|
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.
|
# Set to "generate" to generate and save a new token at startup.
|
||||||
unshared_secret: generate
|
unshared_secret: generate
|
||||||
|
|
||||||
# Shared registration secrets to allow registering new users from the management UI
|
# Known homeservers. This is required for the `mbc auth` command and also allows
|
||||||
registration_secrets:
|
# more convenient access from the management UI. This is not required to create
|
||||||
example.com:
|
# 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
|
# Client-server API URL
|
||||||
url: https://example.com
|
url: https://matrix-client.matrix.org
|
||||||
# registration_shared_secret from synapse config
|
# 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
|
# 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.
|
# 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.
|
# 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
|
# 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
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -22,30 +22,36 @@ import string
|
|||||||
import hmac
|
import hmac
|
||||||
|
|
||||||
from aiohttp import web
|
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.errors import MatrixRequestError
|
||||||
|
from mautrix.client import ClientAPI
|
||||||
|
from mautrix.types import LoginType
|
||||||
|
|
||||||
from .base import routes, get_config, get_loop
|
from .base import routes, get_config, get_loop
|
||||||
from .responses import resp
|
from .responses import resp
|
||||||
|
|
||||||
|
|
||||||
def registration_secrets() -> Dict[str, Dict[str, str]]:
|
def known_homeservers() -> Dict[str, Dict[str, str]]:
|
||||||
return get_config()["registration_secrets"]
|
return get_config()["homeservers"]
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/client/auth/servers")
|
@routes.get("/client/auth/servers")
|
||||||
async def get_registerable_servers(_: web.Request) -> web.Response:
|
async def get_known_servers(_: web.Request) -> web.Response:
|
||||||
return web.json_response({key: value["url"] for key, value in registration_secrets().items()})
|
return web.json_response({key: value["url"] for key, value in known_homeservers().items()})
|
||||||
|
|
||||||
|
|
||||||
AuthRequestInfo = NamedTuple("AuthRequestInfo", api=HTTPAPI, secret=str, username=str,
|
class AuthRequestInfo(NamedTuple):
|
||||||
password=str, user_type=str)
|
client: ClientAPI
|
||||||
|
secret: str
|
||||||
|
username: str
|
||||||
|
password: str
|
||||||
|
user_type: str
|
||||||
|
|
||||||
|
|
||||||
async def read_client_auth_request(request: web.Request) -> Tuple[Optional[AuthRequestInfo],
|
async def read_client_auth_request(request: web.Request) -> Tuple[Optional[AuthRequestInfo],
|
||||||
Optional[web.Response]]:
|
Optional[web.Response]]:
|
||||||
server_name = request.match_info.get("server", None)
|
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:
|
if not server:
|
||||||
return None, resp.server_not_found
|
return None, resp.server_not_found
|
||||||
try:
|
try:
|
||||||
@ -59,10 +65,10 @@ async def read_client_auth_request(request: web.Request) -> Tuple[Optional[AuthR
|
|||||||
return None, resp.username_or_password_missing
|
return None, resp.username_or_password_missing
|
||||||
try:
|
try:
|
||||||
base_url = server["url"]
|
base_url = server["url"]
|
||||||
secret = server["secret"]
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None, resp.invalid_server
|
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")
|
user_type = body.get("user_type", "bot")
|
||||||
return AuthRequestInfo(api, secret, username, password, user_type), None
|
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)
|
info, err = await read_client_auth_request(request)
|
||||||
if err is not None:
|
if err is not None:
|
||||||
return err
|
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
|
path = SynapseAdminPath.v1.register
|
||||||
res = await api.request(Method.GET, path)
|
res = await client.api.request(Method.GET, path)
|
||||||
content = {
|
content = {
|
||||||
"nonce": res["nonce"],
|
"nonce": res["nonce"],
|
||||||
"username": username,
|
"username": username,
|
||||||
@ -100,7 +109,7 @@ async def register(request: web.Request) -> web.Response:
|
|||||||
"user_type": user_type,
|
"user_type": user_type,
|
||||||
}
|
}
|
||||||
try:
|
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:
|
except MatrixRequestError as e:
|
||||||
return web.json_response({
|
return web.json_response({
|
||||||
"errcode": e.errcode,
|
"errcode": e.errcode,
|
||||||
@ -114,18 +123,13 @@ async def login(request: web.Request) -> web.Response:
|
|||||||
info, err = await read_client_auth_request(request)
|
info, err = await read_client_auth_request(request)
|
||||||
if err is not None:
|
if err is not None:
|
||||||
return err
|
return err
|
||||||
api, _, username, password, _ = info
|
|
||||||
device_id = ''.join(random.choices(string.ascii_uppercase + string.digits, k=8))
|
device_id = ''.join(random.choices(string.ascii_uppercase + string.digits, k=8))
|
||||||
|
client = info.client
|
||||||
try:
|
try:
|
||||||
return web.json_response(await api.request(Method.POST, Path.login, content={
|
res = await client.login(identifier=info.username, login_type=LoginType.PASSWORD,
|
||||||
"type": "m.login.password",
|
password=info.password, device_id=f"maubot_{device_id}",
|
||||||
"identifier": {
|
initial_device_display_name="Maubot", store_access_token=False)
|
||||||
"type": "m.id.user",
|
return web.json_response(res.serialize())
|
||||||
"user": username,
|
|
||||||
},
|
|
||||||
"password": password,
|
|
||||||
"device_id": f"maubot_{device_id}",
|
|
||||||
}))
|
|
||||||
except MatrixRequestError as e:
|
except MatrixRequestError as e:
|
||||||
return web.json_response({
|
return web.json_response({
|
||||||
"errcode": e.errcode,
|
"errcode": e.errcode,
|
||||||
|
@ -180,6 +180,13 @@ class _Response:
|
|||||||
"errcode": "server_not_found",
|
"errcode": "server_not_found",
|
||||||
}, status=HTTPStatus.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
|
@property
|
||||||
def plugin_has_no_database(self) -> web.Response:
|
def plugin_has_no_database(self) -> web.Response:
|
||||||
return web.json_response({
|
return web.json_response({
|
||||||
|
Loading…
Reference in New Issue
Block a user