Move web stuff to web package
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
from .provisioning import ProvisioningAPI
|
||||
from .public import PublicBridgeWebsite
|
||||
@@ -0,0 +1 @@
|
||||
from .auth_api import AuthAPI
|
||||
@@ -0,0 +1,115 @@
|
||||
# -*- coding: future_fstrings -*-
|
||||
# mautrix-telegram - A Matrix-Telegram puppeting bridge
|
||||
# Copyright (C) 2018 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from abc import abstractmethod
|
||||
import abc
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from telethon.errors import *
|
||||
|
||||
from mautrix_telegram.commands.auth import enter_password
|
||||
from mautrix_telegram.util import format_duration
|
||||
|
||||
|
||||
class AuthAPI(abc.ABC):
|
||||
log = logging.getLogger("mau.public.auth")
|
||||
|
||||
def __init__(self, loop):
|
||||
self.loop = loop
|
||||
|
||||
@abstractmethod
|
||||
def get_login_response(self, status=200, state="", username="", mxid="", message="", error="",
|
||||
errcode=""):
|
||||
raise NotImplementedError()
|
||||
|
||||
async def post_login_phone(self, user, phone):
|
||||
try:
|
||||
await user.client.sign_in(phone or "+123")
|
||||
return self.get_login_response(mxid=user.mxid, state="code", status=200,
|
||||
message="Code requested successfully.")
|
||||
except PhoneNumberInvalidError:
|
||||
return self.get_login_response(mxid=user.mxid, state="request", status=400,
|
||||
error="Invalid phone number.")
|
||||
except PhoneNumberUnoccupiedError:
|
||||
return self.get_login_response(mxid=user.mxid, state="request", status=404,
|
||||
error="That phone number has not been registered.")
|
||||
except PhoneNumberFloodError:
|
||||
return self.get_login_response(
|
||||
mxid=user.mxid, state="request", status=429,
|
||||
error="Your phone number has been temporarily blocked for flooding. "
|
||||
"The ban is usually applied for around a day.")
|
||||
except FloodWaitError as e:
|
||||
return self.get_login_response(
|
||||
mxid=user.mxid, state="request", status=429,
|
||||
error="Your phone number has been temporarily blocked for flooding. "
|
||||
f"Please wait for {format_duration(e.seconds)} before trying again.")
|
||||
except PhoneNumberBannedError:
|
||||
return self.get_login_response(mxid=user.mxid, state="request", status=401,
|
||||
error="Your phone number is banned from Telegram.")
|
||||
except PhoneNumberAppSignupForbiddenError:
|
||||
return self.get_login_response(mxid=user.mxid, state="request", status=401,
|
||||
error="You have disabled 3rd party apps on your account.")
|
||||
except Exception:
|
||||
self.log.exception("Error requesting phone code")
|
||||
return self.get_login_response(mxid=user.mxid, state="request", status=500,
|
||||
error="Internal server error while requesting code.")
|
||||
|
||||
async def post_login_code(self, user, code, password_in_data):
|
||||
try:
|
||||
user_info = await user.client.sign_in(code=code)
|
||||
asyncio.ensure_future(user.post_login(user_info), loop=self.loop)
|
||||
if user.command_status and user.command_status["action"] == "Login":
|
||||
user.command_status = None
|
||||
return self.get_login_response(mxid=user.mxid, state="logged-in", status=200,
|
||||
username=user_info.username)
|
||||
except PhoneCodeInvalidError:
|
||||
return self.get_login_response(mxid=user.mxid, state="code", status=403,
|
||||
error="Incorrect phone code.")
|
||||
except PhoneCodeExpiredError:
|
||||
return self.get_login_response(mxid=user.mxid, state="code", status=403,
|
||||
error="Phone code expired.")
|
||||
except SessionPasswordNeededError:
|
||||
if not password_in_data:
|
||||
if user.command_status and user.command_status["action"] == "Login":
|
||||
user.command_status = {
|
||||
"next": enter_password,
|
||||
"action": "Login (password entry)",
|
||||
}
|
||||
return self.get_login_response(
|
||||
mxid=user.mxid, state="password", status=200,
|
||||
message="Code accepted, but you have 2-factor authentication is enabled.")
|
||||
return None
|
||||
except Exception:
|
||||
self.log.exception("Error sending phone code")
|
||||
return self.get_login_response(mxid=user.mxid, state="code", status=500,
|
||||
error="Internal server error while sending code.")
|
||||
|
||||
async def post_login_password(self, user, password):
|
||||
try:
|
||||
user_info = await user.client.sign_in(password=password)
|
||||
asyncio.ensure_future(user.post_login(user_info), loop=self.loop)
|
||||
if user.command_status and user.command_status["action"] == "Login (password entry)":
|
||||
user.command_status = None
|
||||
return self.get_login_response(mxid=user.mxid, state="logged-in", status=200,
|
||||
username=user_info.username)
|
||||
except (PasswordHashInvalidError, PasswordEmptyError):
|
||||
return self.get_login_response(mxid=user.mxid, state="password", status=400,
|
||||
error="Incorrect password.")
|
||||
except Exception:
|
||||
self.log.exception("Error sending password")
|
||||
return self.get_login_response(mxid=user.mxid, state="password", status=500,
|
||||
error="Internal server error while sending password.")
|
||||
@@ -0,0 +1,27 @@
|
||||
# -*- coding: future_fstrings -*-
|
||||
# mautrix-telegram - A Matrix-Telegram puppeting bridge
|
||||
# Copyright (C) 2018 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from aiohttp import web
|
||||
import logging
|
||||
|
||||
|
||||
class ProvisioningAPI:
|
||||
log = logging.getLogger("mau.provisioning")
|
||||
|
||||
def __init__(self, loop):
|
||||
self.loop = loop
|
||||
|
||||
self.app = web.Application(loop=loop)
|
||||
@@ -0,0 +1,106 @@
|
||||
# -*- coding: future_fstrings -*-
|
||||
# mautrix-telegram - A Matrix-Telegram puppeting bridge
|
||||
# Copyright (C) 2018 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from aiohttp import web
|
||||
from mako.template import Template
|
||||
import asyncio
|
||||
import pkg_resources
|
||||
import logging
|
||||
|
||||
from ...user import User
|
||||
from ..common import AuthAPI
|
||||
|
||||
|
||||
class PublicBridgeWebsite(AuthAPI):
|
||||
log = logging.getLogger("mau.public")
|
||||
|
||||
def __init__(self, loop):
|
||||
super(AuthAPI, self).__init__(loop)
|
||||
|
||||
self.login = Template(
|
||||
pkg_resources.resource_string("mautrix_telegram", "public/login.html.mako"))
|
||||
|
||||
self.app = web.Application(loop=loop)
|
||||
self.app.router.add_route("GET", "/login", self.get_login)
|
||||
self.app.router.add_route("POST", "/login", self.post_login)
|
||||
self.app.router.add_static("/",
|
||||
pkg_resources.resource_filename("mautrix_telegram", "public/"))
|
||||
|
||||
async def get_login(self, request):
|
||||
state = "token" if request.rel_url.query.get("mode", "") == "bot" else "request"
|
||||
|
||||
mxid = request.rel_url.query.get("mxid", None)
|
||||
user = User.get_by_mxid(mxid, create=False) if mxid else None
|
||||
|
||||
if not user:
|
||||
return self.get_login_response(mxid=mxid, state=state)
|
||||
elif not user.puppet_whitelisted:
|
||||
return self.get_login_response(mxid=user.mxid, error="You are not whitelisted.",
|
||||
status=403)
|
||||
await user.ensure_started()
|
||||
if not await user.is_logged_in():
|
||||
return self.get_login_response(mxid=user.mxid, state=state)
|
||||
|
||||
return self.get_login_response(mxid=user.mxid, username=user.username)
|
||||
|
||||
def get_login_response(self, status=200, state="", username="", mxid="", message="", error="",
|
||||
errcode=""):
|
||||
return web.Response(status=status, content_type="text/html",
|
||||
text=self.login.render(username=username, state=state, error=error,
|
||||
message=message, mxid=mxid))
|
||||
|
||||
async def post_login_token(self, user, token):
|
||||
try:
|
||||
user_info = await user.client.sign_in(bot_token=token)
|
||||
asyncio.ensure_future(user.post_login(user_info), loop=self.loop)
|
||||
if user.command_status and user.command_status["action"] == "Login":
|
||||
user.command_status = None
|
||||
return self.get_login_response(mxid=user.mxid, state="logged-in", status=200,
|
||||
username=user_info.username)
|
||||
except Exception:
|
||||
self.log.exception("Error sending bot token")
|
||||
return self.get_login_response(mxid=user.mxid, state="token", status=500,
|
||||
error="Internal server error while sending token.")
|
||||
|
||||
async def post_login(self, request):
|
||||
data = await request.post()
|
||||
if "mxid" not in data:
|
||||
return self.get_login_response(error="Please enter your Matrix ID.", status=400)
|
||||
|
||||
user = await User.get_by_mxid(data["mxid"]).ensure_started(even_if_no_session=True)
|
||||
if not user.puppet_whitelisted:
|
||||
return self.get_login_response(mxid=user.mxid, error="You are not whitelisted.",
|
||||
status=403)
|
||||
elif await user.is_logged_in():
|
||||
return self.get_login_response(mxid=user.mxid, username=user.username)
|
||||
|
||||
await user.ensure_started(even_if_no_session=True)
|
||||
|
||||
if "phone" in data:
|
||||
return await self.post_login_phone(user, data["phone"])
|
||||
elif "token" in data:
|
||||
return await self.post_login_token(user, data["token"])
|
||||
elif "code" in data:
|
||||
resp = await self.post_login_code(user, data["code"],
|
||||
password_in_data="password" in data)
|
||||
if resp or "password" not in data:
|
||||
return resp
|
||||
elif "password" not in data:
|
||||
return self.get_login_response(error="No data given.", status=400)
|
||||
|
||||
if "password" in data:
|
||||
return await self.post_login_password(user, data["password"])
|
||||
return self.get_login_response(error="This should never happen.", status=500)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* mautrix-telegram - A Matrix-Telegram puppeting bridge
|
||||
* Copyright (C) 2018 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
form > div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
form[data-status="request"] > div.status-request,
|
||||
form[data-status="code"] > div.status-code,
|
||||
form[data-status="password"] > div.status-password {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin-top: 3rem;
|
||||
max-width: 60rem;
|
||||
}
|
||||
|
||||
.error, .message {
|
||||
border-radius: .25rem;
|
||||
padding: .5rem 1rem;
|
||||
border: 1px solid transparent;
|
||||
margin: .5rem 0;
|
||||
}
|
||||
|
||||
.error {
|
||||
border-color: #f5c6cb;
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.message {
|
||||
border-color: #c3e6cb;
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<!--
|
||||
mautrix-telegram - A Matrix-Telegram puppeting bridge
|
||||
Copyright (C) 2018 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
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Mautrix-Telegram bridge</title>
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
<meta property="og:title" content="Mautrix-Telegram bridge">
|
||||
<meta property="og:description" content="A hybrid puppeting/relaybot Matrix-Telegram bridge">
|
||||
<meta property="og:image" content="favicon.png">
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,700">
|
||||
<link rel="stylesheet" href="//cdn.rawgit.com/necolas/normalize.css/master/normalize.css">
|
||||
<link rel="stylesheet"
|
||||
href="//cdn.rawgit.com/milligram/milligram/master/dist/milligram.min.css">
|
||||
<link rel="stylesheet" href="login.css"/>
|
||||
|
||||
<script>
|
||||
function switchToBotLogin() {
|
||||
const params = new URLSearchParams(location.search.slice(1))
|
||||
params.set("mode", "bot")
|
||||
location.search = "?" + params.toString()
|
||||
console.log(location.search)
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
let params = new URLSearchParams(location.search.slice(1))
|
||||
const mxid = params.get("mxid")
|
||||
params = new URLSearchParams()
|
||||
if (mxid) {
|
||||
params.set("mxid", mxid)
|
||||
}
|
||||
location.replace(location.href.split("?")[0] + "?" + params.toString())
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
% if username:
|
||||
% if state == "logged-in":
|
||||
<h1>Logged in successfully!</h1>
|
||||
<p>
|
||||
Logged in as @${username}.
|
||||
You can now close this page.
|
||||
You should be invited to Telegram portals on Matrix momentarily.
|
||||
</p>
|
||||
% elif state == "bot-logged-in":
|
||||
<h1>Logged in successfully!</h1>
|
||||
<p>
|
||||
Logged in as @${username}.
|
||||
You can now close this page.
|
||||
You should be invited to Telegram portals on Matrix momentarily.
|
||||
</p>
|
||||
% else:
|
||||
<h1>You're already logged in!</h1>
|
||||
<p>
|
||||
You're logged in as @${username}.
|
||||
</p>
|
||||
<p>
|
||||
If you want to log in with another account, log out using the <code>logout</code>
|
||||
management command first.
|
||||
</p>
|
||||
% endif
|
||||
% else:
|
||||
<h1>Log in to Telegram</h1>
|
||||
% if error:
|
||||
<div class="error">${error}</div>
|
||||
% endif
|
||||
% if message:
|
||||
<div class="message">${message}</div>
|
||||
% endif
|
||||
<form method="post">
|
||||
<fieldset>
|
||||
<label for="mxid">Matrix ID</label>
|
||||
<input type="text" id="mxid" name="mxid" placeholder="Enter Matrix ID"
|
||||
value="${mxid}"/>
|
||||
% if state == "request":
|
||||
<label for="value">Phone number</label>
|
||||
<input type="tel" id="value" name="phone" placeholder="Enter phone number"/>
|
||||
<button type="submit">Request code</button>
|
||||
<button class="button-clear" type="button" onclick="switchToBotLogin()">
|
||||
Use bot token
|
||||
</button>
|
||||
% elif state == "token":
|
||||
<label for="value">Bot token</label>
|
||||
<input type="text" id="value" name="token" placeholder="Enter bot API token"/>
|
||||
<button type="submit">Sign in</button>
|
||||
% elif state == "code":
|
||||
<label for="value">Phone code</label>
|
||||
<input type="number" id="value" name="code" placeholder="Enter phone code"/>
|
||||
<button type="submit">Sign in</button>
|
||||
% elif state == "password":
|
||||
<label for="value">Password</label>
|
||||
<input type="password" id="value" name="password"
|
||||
placeholder="Enter password"/>
|
||||
<button type="submit">Sign in</button>
|
||||
% endif
|
||||
% if state != "request":
|
||||
<div class="float-right">
|
||||
<button class="button-clear" type="button" onclick="goBack()">
|
||||
Go back
|
||||
</button>
|
||||
</div>
|
||||
% endif
|
||||
</fieldset>
|
||||
</form>
|
||||
% endif
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user