Move web stuff to web package

This commit is contained in:
Tulir Asokan
2018-06-24 21:39:01 +03:00
parent 5d48040eb8
commit fa30cb5c1f
10 changed files with 23 additions and 25 deletions
+2
View File
@@ -0,0 +1,2 @@
from .provisioning import ProvisioningAPI
from .public import PublicBridgeWebsite
+1
View File
@@ -0,0 +1 @@
from .auth_api import AuthAPI
+115
View File
@@ -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)
+106
View File
@@ -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

+50
View File
@@ -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;
}
+125
View File
@@ -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>