add authentication

This commit is contained in:
odysseusmax 2021-05-22 19:30:08 +05:30
parent 85032a0c50
commit 6bbca80da9
19 changed files with 461 additions and 217 deletions

View File

@ -48,9 +48,13 @@ pip3 install -U -r requirements.txt
| `SESSION_STRING` (required) | String obtained by running `$ python3 app/generate_session_string.py`. (Login with the telegram account which is a participant of the given channel (or chat).
| `PORT` (optional) | Port on which app should listen to, defaults to 8080.
| `HOST` (optional) | Host name on which app should listen to, defaults to 0.0.0.0.
| `DEBUG` (optional) | Give some value to set logging level to debug, info by default.
| `BLOCK_DOWNLOADS` (optional) | Enable downloads or not. If provided, downloads will be disabled.
| `DEBUG` (optional) | Give `true` to set logging level to debug, info by default.
| `BLOCK_DOWNLOADS` (optional) | Enable downloads or not. If any value is provided, downloads will be disabled.
| `RESULTS_PER_PAGE` (optional) | Number of results to be returned per page defaults to 20.
| `USERNAME` (optional) | Username for authentication, defaults to `''`.
| `PASSWORD` (optional) | Username for authentication, defaults to `''`.
| `SESSION_COOKIE_LIFETIME` (optional) | Number of minutes, for which authenticated session is valid for, after which user has to login again. defaults to 60.
| `SECRET_KEY` (optional) | Long string for signing the session cookies, required if authentication is enabled.
* **Setting value for `INDEX_SETTINGS`**

View File

@ -44,7 +44,18 @@ host = os.environ.get("HOST", "0.0.0.0")
debug = bool(os.environ.get("DEBUG"))
block_downloads = bool(os.environ.get("BLOCK_DOWNLOADS"))
results_per_page = int(os.environ.get("RESULTS_PER_PAGE", "20"))
logo_folder = Path(
"/Temp/logo/" if platform.system() == 'Windows' else '/tmp/logo'
)
logo_folder = Path("/Temp/logo/" if platform.system() == "Windows" else "/tmp/logo")
logo_folder.mkdir(exist_ok=True)
username = os.environ.get("USERNAME", "")
password = os.environ.get("PASSWORD", "")
authenticated = username and password
SESSION_COOKIE_LIFETIME = int(os.environ.get("SESSION_COOKIE_LIFETIME") or "60")
try:
SECRET_KEY = os.environ["SECRET_KEY"]
except (KeyError, ValueError):
if authenticated:
traceback.print_exc()
print("\n\nPlease set the SECRET_KEY environment variable correctly")
sys.exit(1)
else:
SECRET_KEY = ""

View File

@ -8,22 +8,54 @@ from aiohttp import web
from .telegram import Client
from .routes import setup_routes
from .views import Views
from .config import host, port, session_string, api_id, api_hash, debug
from .views import Views, middleware_factory
from .config import (
host,
port,
session_string,
api_id,
api_hash,
authenticated,
username,
password,
SESSION_COOKIE_LIFETIME,
SECRET_KEY,
)
log = logging.getLogger(__name__)
def inspect_rep():
@web.middleware
async def factory(request, handler):
print("Incoming Cookies", request.cookies)
response = await handler(request)
print("Outgoing Cookies", response.cookies)
return response
return factory
class Indexer:
TEMPLATES_ROOT = pathlib.Path(__file__).parent / 'templates'
TEMPLATES_ROOT = pathlib.Path(__file__).parent / "templates"
def __init__(self):
self.server = web.Application()
self.server = web.Application(
middlewares=[
inspect_rep(),
middleware_factory(),
]
)
self.loop = asyncio.get_event_loop()
self.tg_client = Client(session_string, api_id, api_hash)
self.server["is_authenticated"] = authenticated
self.server["username"] = username
self.server["password"] = password
self.server["SESSION_COOKIE_LIFETIME"] = SESSION_COOKIE_LIFETIME
self.server["SECRET_KEY"] = SECRET_KEY
async def startup(self):
await self.tg_client.start()
@ -36,12 +68,10 @@ class Indexer:
self.server.on_cleanup.append(self.cleanup)
async def cleanup(self, *args):
await self.tg_client.disconnect()
log.debug("telegram client disconnected!")
def run(self):
self.loop.run_until_complete(self.startup())
web.run_app(self.server, host=host, port=port)

View File

@ -18,7 +18,12 @@ async def setup_routes(app, handler):
index_channel = index_settings["index_channel"]
exclude_chats = index_settings["exclude_chats"]
include_chats = index_settings["include_chats"]
routes = [web.get("/", h.home)]
routes = [
web.get("/", h.home, name="home"),
web.get("/login", h.login_get, name="login_page"),
web.post("/login", h.login_post, name="login_handle"),
web.get("/logout", h.logout_get, name="logout"),
]
if index_all:
# print(await client.get_dialogs())
# dialogs = await client.get_dialogs()

View File

@ -1,8 +1,10 @@
<footer class="text-center text-black w-full my-4 py-4 bg-gray-300 xl:text-xl">
<a href="https://github.com/odysseusmax/tg-index" target="_blank"> @odysseusmax </a>
</footer>
</div>
</body>
</html>
</div>
<footer class="w-full my-4 py-4 bg-gray-300 xl:text-xl">
<div class="mx-auto text-center text-black max-w-screen-xl">
<a href="https://github.com/odysseusmax/tg-index" target="_blank"> @odysseusmax </a>
</div>
</footer>
</body>
</html>

View File

@ -1,20 +1,26 @@
<!doctype html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
<script src="https://cdn.fluidplayer.com/v3/current/fluidplayer.min.js"></script>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
{% if title %} {{title}} {% else %} Telegram Index {% endif %}
</title>
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
<script src="https://cdn.fluidplayer.com/v3/current/fluidplayer.min.js"></script>
</head>
<body>
<title>
{% if title %} {{title}} {% else %} Telegram Index {% endif %}
</title>
<div class="m-auto w-full xl:max-w-screen-xl min-h-screen">
</head>
<header class="flex justify-between bg-red-600 text-white mb-2 p-4 w-full sticky top-0 shadow">
<a href="/" class="text-left text-xl lg:text-2xl xl:text-3xl"> Telegram Index </a>
</header>
<body>
<header class="bg-red-600 text-white mb-2 p-4 w-full shadow">
<div class="mx-auto flex justify-between content-center max-w-screen-xl">
<a href="/" class="text-left text-xl lg:text-2xl xl:text-3xl"> Telegram Index </a>
{% if authenticated %} <a href="/logout" class="text-right"> Logout </a> {% else %} {%
endif %}
</div>
</header>
<div class="m-auto w-full min-h-screen max-w-screen-xl">

View File

@ -1,20 +1,21 @@
{% include 'header.html' %}
<h1 class=" my-2 text-2xl text-center font-bold text-green-400">
Available Sources
</h1>
<h1 class=" my-2 text-2xl text-center font-bold text-green-400">
Available Sources
</h1>
<div class="mx-auto my-2 p-2 w-full">
<div class="block p-4 md:flex md:flex-wrap md:justify-center w-full text-center break-words">
{% for chat in chats %}
<a title="{{chat.name}}" href="/{{chat.page_id}}" class="flex flex-col justify-around w-full min-h-full md:w-2/5 lg:w-1/5 rounded my-2 md:mx-1 shadow-md hover:shadow-lg hover:border hover:border-blue-300 hover:bg-blue-100">
<div class="mx-auto my-2 p-2 w-full">
<div class="block p-4 md:flex md:flex-wrap md:justify-items-center w-full text-center break-words">
{% for chat in chats %}
<a title="{{chat.name}}" href="/{{chat.page_id}}"
class="mx-auto flex flex-col justify-items-center w-2/3 min-h-full md:w-2/5 lg:w-1/4 rounded my-2 p-2 shadow-md hover:shadow-lg hover:border hover:border-blue-300 hover:bg-blue-100">
<img src="/{{chat.page_id}}/logo?big=1" class="w-full rounded-full ">
<div class="p-4 rounded">{{chat.name}}</div>
<img src="/{{chat.page_id}}/logo?big=1" class="w-full rounded-full ">
<div class="p-4 rounded">{{chat.name}}</div>
</a>
{% endfor %}
</div>
</a>
{% endfor %}
</div>
</div>
{% include 'footer.html' %}
{% include 'footer.html' %}

44
app/templates/login.html Normal file
View File

@ -0,0 +1,44 @@
{% include 'header.html' %}
<div class="min-h-full flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div class="max-w-md w-full space-y-8">
<div>
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
Sign in to view the contents
</h2>
</div>
{% if error %}
<div>
<h2 class="p-2 text-center text-xl text-red-500 rounded border border-l-4 border-red-500">
{{ error }}
</h2>
</div>
{% endif %}
<form class="mt-8 space-y-6" action="/login" method="POST">
<input type="hidden" name="remember" value="true">
<div class="rounded-md shadow-sm -space-y-px">
<div>
<label for="email-address" class="sr-only">Username</label>
<input id="email-address" name="username" type="text" required
class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-900 placeholder-gray-700 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm mb-2"
placeholder="Username">
</div>
<div>
<label for="password" class="sr-only">Password</label>
<input id="password" name="password" type="password" autocomplete="current-password" required
class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-900 placeholder-gray-700 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm mt-2"
placeholder="Password">
</div>
</div>
<div>
<button type="submit"
class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Sign in
</button>
</div>
</form>
</div>
</div>
{% include 'footer.html' %}

View File

@ -1,8 +1,6 @@
import random
import string
from telethon.utils import get_display_name
from .home_view import HomeView
from .wildcard_view import WildcardView
from .download import Download
@ -10,30 +8,44 @@ from .index_view import IndexView
from .info_view import InfoView
from .logo_view import LogoView
from .thumbnail_view import ThumbnailView
from .login_view import LoginView
from .logout_view import LogoutView
from .middlewhere import middleware_factory
class Views(HomeView, Download,
IndexView, InfoView,
LogoView, ThumbnailView,
WildcardView):
class Views(
HomeView,
Download,
IndexView,
InfoView,
LogoView,
ThumbnailView,
WildcardView,
LoginView,
LogoutView,
):
def __init__(self, client):
self.client = client
self.alias_ids = []
self.chat_ids = []
self.chat_ids = {}
def generate_alias_id(self, chat):
chat_id = chat.id
title = chat.title
while True:
alias_id = ''.join([random.choice(string.ascii_letters + string.digits) for _ in range(len(str(chat_id)))])
if alias_id in self.alias_ids:
alias_id = "".join(
[
random.choice(string.ascii_letters + string.digits)
for _ in range(len(str(chat_id)))
]
)
if alias_id in self.chat_ids:
continue
self.alias_ids.append(alias_id)
self.chat_ids.append({
'chat_id': chat_id,
'alias_id': alias_id,
'title': title
})
self.chat_ids[alias_id] = {
"chat_id": chat_id,
"alias_id": alias_id,
"title": title,
}
return alias_id

View File

@ -10,33 +10,35 @@ log = logging.getLogger(__name__)
class Download:
async def download_get(self, req):
return await self.handle_request(req)
async def download_head(self, req):
return await self.handle_request(req, head=True)
async def handle_request(self, req, head=False):
if block_downloads:
return web.Response(status=403, text="403: Forbiden" if not head else None)
file_id = int(req.match_info["id"])
alias_id = req.match_info['chat']
chat = [i for i in self.chat_ids if i['alias_id'] == alias_id][0]
chat_id = chat['chat_id']
alias_id = req.match_info["chat"]
chat = self.chat_ids[alias_id]
chat_id = chat["chat_id"]
try:
message = await self.client.get_messages(entity=chat_id, ids=file_id)
except:
except Exception:
log.debug(f"Error in getting message {file_id} in {chat_id}", exc_info=True)
message = None
if not message or not message.file:
log.debug(f"no result for {file_id} in {chat_id}")
return web.Response(status=410, text="410: Gone. Access to the target resource is no longer available!" if not head else None)
return web.Response(
status=410,
text="410: Gone. Access to the target resource is no longer available!"
if not head
else None,
)
media = message.media
size = message.file.size
@ -52,14 +54,14 @@ class Download:
return web.Response(
status=416,
text="416: Range Not Satisfiable" if not head else None,
headers = {
"Content-Range": f"bytes */{size}"
}
headers={"Content-Range": f"bytes */{size}"},
)
if not head:
body = self.client.download(media, size, offset, limit)
log.info(f"Serving file in {message.id} (chat {chat_id}) ; Range: {offset} - {limit}")
log.info(
f"Serving file in {message.id} (chat {chat_id}) ; Range: {offset} - {limit}"
)
else:
body = None
@ -68,11 +70,7 @@ class Download:
"Content-Range": f"bytes {offset}-{limit}/{size}",
"Content-Length": str(limit - offset),
"Accept-Ranges": "bytes",
"Content-Disposition": f'attachment; filename="{file_name}"'
"Content-Disposition": f'attachment; filename="{file_name}"',
}
return web.Response(
status=206 if offset else 200,
body=body,
headers=headers
)
return web.Response(status=206 if offset else 200, body=body, headers=headers)

View File

@ -3,17 +3,19 @@ import aiohttp_jinja2
class HomeView:
@aiohttp_jinja2.template('home.html')
@aiohttp_jinja2.template("home.html")
async def home(self, req):
if len(self.chat_ids) == 1:
raise web.HTTPFound(f"{self.chat_ids[0]['alias_id']}")
chats = []
for chat in self.chat_ids:
chats.append({
'page_id': chat['alias_id'],
'name': chat['title'],
'url': f"/{chat['alias_id']}"
})
return {'chats': chats}
return {
"chats": [
{
"page_id": chat["alias_id"],
"name": chat["title"],
"url": f"/{chat['alias_id']}",
}
for _, chat in self.chat_ids.items()
],
"authenticated": req.app["is_authenticated"],
}

View File

@ -12,36 +12,39 @@ log = logging.getLogger(__name__)
class IndexView:
@aiohttp_jinja2.template('index.html')
@aiohttp_jinja2.template("index.html")
async def index(self, req):
alias_id = req.match_info['chat']
chat = [i for i in self.chat_ids if i['alias_id'] == alias_id][0]
log_msg = ''
alias_id = req.match_info["chat"]
chat = self.chat_ids[alias_id]
log_msg = ""
try:
offset_val = int(req.query.get('page', '1'))
except:
offset_val = int(req.query.get("page", "1"))
except Exception:
offset_val = 1
log_msg += f"page: {offset_val} | "
try:
search_query = req.query.get('search', '')
except:
search_query = ''
search_query = req.query.get("search", "")
except Exception:
search_query = ""
log_msg += f"search query: {search_query} | "
offset_val = 0 if offset_val <=1 else offset_val-1
offset_val = 0 if offset_val <= 1 else offset_val - 1
try:
kwargs = {
'entity': chat['chat_id'],
'limit': results_per_page,
'add_offset': results_per_page*offset_val
"entity": chat["chat_id"],
"limit": results_per_page,
"add_offset": results_per_page * offset_val,
}
if search_query:
kwargs.update({'search': search_query})
kwargs.update({"search": search_query})
messages = (await self.client.get_messages(**kwargs)) or []
except:
except Exception:
log.debug("failed to get messages", exc_info=True)
messages = []
log_msg += f"found {len(messages)} results | "
log.debug(log_msg)
results = []
@ -53,47 +56,46 @@ class IndexView:
media=True,
thumbnail=f"/{alias_id}/{m.id}/thumbnail",
mime_type=m.file.mime_type,
insight = get_file_name(m),
insight=get_file_name(m),
human_size=get_human_size(m.file.size),
url=f"/{alias_id}/{m.id}/view"
url=f"/{alias_id}/{m.id}/view",
)
elif m.message:
entry = dict(
file_id=m.id,
media=False,
mime_type='text/plain',
insight = m.raw_text[:100],
url=f"/{alias_id}/{m.id}/view"
mime_type="text/plain",
insight=m.raw_text[:100],
url=f"/{alias_id}/{m.id}/view",
)
if entry:
results.append(entry)
prev_page = False
next_page = False
if offset_val:
query = {'page':offset_val}
if search_query:
query.update({'search':search_query})
prev_page = {
'url': str(req.rel_url.with_query(query)),
'no': offset_val
}
if len(messages)==results_per_page:
query = {'page':offset_val+2}
prev_page = None
next_page = None
if offset_val:
query = {"page": offset_val}
if search_query:
query.update({'search':search_query})
next_page = {
'url': str(req.rel_url.with_query(query)),
'no': offset_val+2
query.update({"search": search_query})
prev_page = {"url": str(req.rel_url.with_query(query)), "no": offset_val}
if len(messages) == results_per_page:
query = {"page": offset_val + 2}
if search_query:
query.update({"search": search_query})
next_page = {
"url": str(req.rel_url.with_query(query)),
"no": offset_val + 2,
}
return {
'item_list':results,
'prev_page': prev_page,
'cur_page' : offset_val+1,
'next_page': next_page,
'search': search_query,
'name' : chat['title'],
'logo': f"/{alias_id}/logo",
'title' : "Index of " + chat['title']
"item_list": results,
"prev_page": prev_page,
"cur_page": offset_val + 1,
"next_page": next_page,
"search": search_query,
"name": chat["title"],
"logo": f"/{alias_id}/logo",
"title": "Index of " + chat["title"],
"authenticated": req.app["is_authenticated"],
}

View File

@ -13,80 +13,95 @@ log = logging.getLogger(__name__)
class InfoView:
@aiohttp_jinja2.template('info.html')
@aiohttp_jinja2.template("info.html")
async def info(self, req):
file_id = int(req.match_info["id"])
alias_id = req.match_info['chat']
chat = [i for i in self.chat_ids if i['alias_id'] == alias_id][0]
chat_id = chat['chat_id']
alias_id = req.match_info["chat"]
chat = self.chat_ids[alias_id]
chat_id = chat["chat_id"]
try:
message = await self.client.get_messages(entity=chat_id, ids=file_id)
except:
except Exception:
log.debug(f"Error in getting message {file_id} in {chat_id}", exc_info=True)
message = None
if not message or not isinstance(message, Message):
log.debug(f"no valid entry for {file_id} in {chat_id}")
return {
'found':False,
'reason' : "Resource you are looking for cannot be retrived!",
"found": False,
"reason": "Resource you are looking for cannot be retrived!",
"authenticated": req.app["is_authenticated"],
}
return_val = {}
return_val = {
"authenticated": req.app["is_authenticated"],
}
reply_btns = []
if message.reply_markup:
if isinstance(message.reply_markup, types.ReplyInlineMarkup):
for button_row in message.reply_markup.rows:
btns = []
for button in button_row.buttons:
if isinstance(button, types.KeyboardButtonUrl):
btns.append({'url': button.url, 'text': button.text})
reply_btns.append(btns)
reply_btns = [
[
{"url": button.url, "text": button.text}
for button in button_row.buttons
if isinstance(button, types.KeyboardButtonUrl)
]
for button_row in message.reply_markup.rows
]
if message.file and not isinstance(message.media, types.MessageMediaWebPage):
file_name = get_file_name(message)
human_file_size = get_human_size(message.file.size)
media = {
'type':message.file.mime_type
}
if 'video/' in message.file.mime_type:
media['video'] = True
elif 'audio/' in message.file.mime_type:
media['audio'] = True
elif 'image/' in message.file.mime_type:
media['image'] = True
media = {"type": message.file.mime_type}
if "video/" in message.file.mime_type:
media["video"] = True
elif "audio/" in message.file.mime_type:
media["audio"] = True
elif "image/" in message.file.mime_type:
media["image"] = True
if message.text:
caption = message.raw_text
else:
caption = ''
caption_html = Markup.escape(caption).__str__().replace('\n', '<br>')
return_val = {
'found': True,
'name': file_name,
'file_id': file_id,
'human_size': human_file_size,
'media': media,
'caption_html': caption_html,
'title': f"Download | {file_name} | {human_file_size}",
'reply_btns': reply_btns,
'thumbnail': f"/{alias_id}/{file_id}/thumbnail",
'download_url': '#' if block_downloads else f"/{alias_id}/{file_id}/download",
'page_id': alias_id,
'block_downloads': block_downloads
}
caption = ""
caption_html = Markup.escape(caption).__str__().replace("\n", "<br>")
return_val.update(
{
"found": True,
"name": file_name,
"file_id": file_id,
"human_size": human_file_size,
"media": media,
"caption_html": caption_html,
"title": f"Download | {file_name} | {human_file_size}",
"reply_btns": reply_btns,
"thumbnail": f"/{alias_id}/{file_id}/thumbnail",
"download_url": "#"
if block_downloads
else f"/{alias_id}/{file_id}/download",
"page_id": alias_id,
"block_downloads": block_downloads,
}
)
elif message.message:
text = message.raw_text
text_html = Markup.escape(text).__str__().replace('\n', '<br>')
return_val = {
'found': True,
'media': False,
'text_html': text_html,
'reply_btns': reply_btns,
'page_id': alias_id
}
text_html = Markup.escape(text).__str__().replace("\n", "<br>")
return_val.update(
{
"found": True,
"media": False,
"text_html": text_html,
"reply_btns": reply_btns,
"page_id": alias_id,
}
)
else:
return_val = {
'found':False,
'reason' : "Some kind of resource that I cannot display",
}
return_val.update(
{
"found": False,
"reason": "Some kind of resource that I cannot display",
}
)
log.debug(f"data for {file_id} in {chat_id} returned as {return_val}")
return return_val

49
app/views/login_view.py Normal file
View File

@ -0,0 +1,49 @@
import time
import hmac
import hashlib
from aiohttp import web
import aiohttp_jinja2
class LoginView:
@aiohttp_jinja2.template("login.html")
async def login_get(self, req):
return dict(authenticated=False, **req.query)
async def login_post(self, req):
post_data = await req.post()
location = req.app.router["login_page"].url_for()
if "username" not in post_data:
loc = location.with_query({"error": "Username missing"})
raise web.HTTPFound(location=loc)
if "password" not in post_data:
loc = location.with_query({"error": "Password missing"})
raise web.HTTPFound(location=loc)
authenticated = (post_data["username"] == req.app["username"]) and (
post_data["password"] == req.app["password"]
)
if not authenticated:
loc = location.with_query({"error": "Wrong Username or Passowrd"})
raise web.HTTPFound(location=loc)
resp = web.Response(
status=302, headers={"Location": str(req.app.router["home"].url_for())}
)
now = time.time()
resp.set_cookie(
name="_tgindex_session",
value=str(now),
max_age=60 * req.app["SESSION_COOKIE_LIFETIME"],
)
digest = hmac.new(
req.app["SECRET_KEY"].encode(), str(now).encode(), hashlib.sha256
).hexdigest()
resp.set_cookie(
name="_tgindex_secret",
value=digest,
max_age=60 * req.app["SESSION_COOKIE_LIFETIME"],
)
return resp

View File

@ -1,7 +1,6 @@
import logging
from PIL import Image, ImageDraw
import random
import os
from aiohttp import web
from telethon.tl import types
@ -13,31 +12,32 @@ log = logging.getLogger(__name__)
class LogoView:
async def logo(self, req):
alias_id = req.match_info['chat']
chat = [i for i in self.chat_ids if i['alias_id'] == alias_id][0]
chat_id = chat['chat_id']
alias_id = req.match_info["chat"]
chat = self.chat_ids[alias_id]
chat_id = chat["chat_id"]
chat_name = "Image not available"
logo_path = logo_folder.joinpath(f"{alias_id}.jpg")
if not logo_path.exists():
try:
photo = await self.client.get_profile_photos(chat_id)
except:
log.debug(f"Error in getting profile picture in {chat_id}", exc_info=True)
except Exception:
log.debug(
f"Error in getting profile picture in {chat_id}", exc_info=True
)
photo = None
if not photo:
W, H = (160, 160)
color = tuple([random.randint(0, 255) for i in range(3)])
im = Image.new("RGB", (W,H), color)
im = Image.new("RGB", (W, H), color)
draw = ImageDraw.Draw(im)
w, h = draw.textsize(chat_name)
draw.text(((W-w)/2,(H-h)/2), chat_name, fill="white")
draw.text(((W - w) / 2, (H - h) / 2), chat_name, fill="white")
im.save(logo_path)
else:
photo = photo[0]
pos = -1 if req.query.get('big', None) else int(len(photo.sizes)/2)
pos = -1 if req.query.get("big", None) else int(len(photo.sizes) / 2)
size = self.client._get_thumb(photo.sizes, pos)
if isinstance(size, (types.PhotoCachedSize, types.PhotoStrippedSize)):
await self.client._download_cached_photo_size(size, logo_path)
@ -46,11 +46,11 @@ class LogoView:
id=photo.id,
access_hash=photo.access_hash,
file_reference=photo.file_reference,
thumb_size=size.type
thumb_size=size.type,
)
await self.client.download_file(media, logo_path)
with open(logo_path, 'rb') as fp:
with open(logo_path, "rb") as fp:
body = fp.read()
return web.Response(
@ -58,6 +58,6 @@ class LogoView:
body=body,
headers={
"Content-Type": "image/jpeg",
"Content-Disposition": 'inline; filename="logo.jpg"'
}
"Content-Disposition": 'inline; filename="logo.jpg"',
},
)

11
app/views/logout_view.py Normal file
View File

@ -0,0 +1,11 @@
from aiohttp import web
class LogoutView:
async def logout_get(self, req):
resp = web.Response(
status=302, headers={"Location": str(req.app.router["home"].url_for())}
)
resp.del_cookie(name="_tgindex_session")
resp.del_cookie(name="_tgindex_secret")
return resp

49
app/views/middlewhere.py Normal file
View File

@ -0,0 +1,49 @@
import time
import hmac
import hashlib
import logging
from aiohttp.web import middleware, HTTPFound
log = logging.getLogger(__name__)
def middleware_factory():
@middleware
async def factory(request, handler):
if request.app["is_authenticated"] and str(request.rel_url.path) not in [
"/login",
"/logout",
]:
cookies = request.cookies
url = request.app.router["login_page"].url_for()
if any(x not in cookies for x in ("_tgindex_session", "_tgindex_secret")):
raise HTTPFound(url)
tgindex_session = cookies["_tgindex_session"]
tgindex_secret = cookies["_tgindex_secret"]
calculated_digest = hmac.new(
request.app["SECRET_KEY"].encode(),
str(tgindex_session).encode(),
hashlib.sha256,
).hexdigest()
if tgindex_secret != calculated_digest:
raise HTTPFound(url)
try:
created_at = (
float(tgindex_session) + request.app["SESSION_COOKIE_LIFETIME"]
)
if (
time.time()
> created_at + 60 * request.app["SESSION_COOKIE_LIFETIME"]
):
raise HTTPFound(url)
except Exception as e:
log.error(e, exc_info=True)
raise HTTPFound(url)
return await handler(request)
return factory

View File

@ -11,21 +11,23 @@ log = logging.getLogger(__name__)
class ThumbnailView:
async def thumbnail_get(self, req):
file_id = int(req.match_info["id"])
alias_id = req.match_info['chat']
chat = [i for i in self.chat_ids if i['alias_id'] == alias_id][0]
chat_id = chat['chat_id']
alias_id = req.match_info["chat"]
chat = self.chat_ids[alias_id]
chat_id = chat["chat_id"]
try:
message = await self.client.get_messages(entity=chat_id, ids=file_id)
except:
except Exception:
log.debug(f"Error in getting message {file_id} in {chat_id}", exc_info=True)
message = None
if not message or not message.file:
log.debug(f"no result for {file_id} in {chat_id}")
return web.Response(status=410, text="410: Gone. Access to the target resource is no longer available!")
return web.Response(
status=410,
text="410: Gone. Access to the target resource is no longer available!",
)
if message.document:
media = message.document
@ -43,10 +45,13 @@ class ThumbnailView:
im.save(temp, "PNG")
body = temp.getvalue()
else:
thumb_pos = int(len(thumbnails)/2)
thumb_pos = int(len(thumbnails) / 2)
thumbnail = self.client._get_thumb(thumbnails, thumb_pos)
if not thumbnail or isinstance(thumbnail, types.PhotoSizeEmpty):
return web.Response(status=410, text="410: Gone. Access to the target resource is no longer available!")
return web.Response(
status=410,
text="410: Gone. Access to the target resource is no longer available!",
)
if isinstance(thumbnail, (types.PhotoCachedSize, types.PhotoStrippedSize)):
body = self.client._download_cached_photo_size(thumbnail, bytes)
@ -55,17 +60,16 @@ class ThumbnailView:
id=media.id,
access_hash=media.access_hash,
file_reference=media.file_reference,
thumb_size=thumbnail.type
thumb_size=thumbnail.type,
)
body = self.client.iter_download(actual_file)
r = web.Response(
return web.Response(
status=200,
body=body,
headers={
"Content-Type": "image/jpeg",
"Content-Disposition": 'inline; filename="thumbnail.jpg"'
}
"Content-Disposition": 'inline; filename="thumbnail.jpg"',
},
)
return r

View File

@ -2,6 +2,5 @@ from aiohttp import web
class WildcardView:
async def wildcard(self, req):
raise web.HTTPFound('/')
raise web.HTTPFound("/")