From e566d5048c0e6a2ff852a6db2c6dba71f80bf690 Mon Sep 17 00:00:00 2001 From: odysseusmax Date: Mon, 14 Jun 2021 21:23:33 +0530 Subject: [PATCH] fix some issues in authentication --- app/main.py | 16 +++++++++------- app/routes.py | 34 +++++++++++++++++++++++----------- app/telegram.py | 20 +++++++++++++++----- app/views/__init__.py | 4 +++- app/views/faviconicon_view.py | 33 +++++++++++++++++++++++++++++++++ app/views/index_view.py | 2 +- app/views/logo_view.py | 1 - app/views/middlewhere.py | 24 ++++++++++++++++++++++-- 8 files changed, 106 insertions(+), 28 deletions(-) create mode 100644 app/views/faviconicon_view.py diff --git a/app/main.py b/app/main.py index 52b51fc..c9ea3ce 100644 --- a/app/main.py +++ b/app/main.py @@ -33,18 +33,20 @@ class Indexer: TEMPLATES_ROOT = pathlib.Path(__file__).parent / "templates" def __init__(self): - self.server = web.Application( - middlewares=[ + middlewares = [] + if authenticated: + middlewares.append( session_middleware( EncryptedCookieStorage( secret_key=SECRET_KEY.encode(), max_age=60 * SESSION_COOKIE_LIFETIME, - cookie_name="TG_INDEX_SESSION" + cookie_name="TG_INDEX_SESSION", ) - ), - middleware_factory(), - ] - ) + ) + ) + + middlewares.append(middleware_factory()) + self.server = web.Application(middlewares=middlewares) self.loop = asyncio.get_event_loop() self.tg_client = Client(session_string, api_id, api_hash) diff --git a/app/routes.py b/app/routes.py index 758bce0..6c3c969 100644 --- a/app/routes.py +++ b/app/routes.py @@ -22,16 +22,30 @@ async def setup_routes(app, handler): 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"), + web.get("/favicon.ico", h.faviconicon, name="favicon"), ] - def get_common_routes(p): + def get_common_routes(alias_id): + p = "/{chat:" + alias_id + "}" return [ - web.get(p, h.index), - web.get(p + r"/logo", h.logo), - web.get(p + r"/{id:\d+}/view", h.info), - web.get(p + r"/{id:\d+}/thumbnail", h.thumbnail_get), - web.get(p + r"/{id:\d+}/{filename}", h.download_get), - web.head(p + r"/{id:\d+}/{filename}", h.download_head), + web.get(p, h.index, name=f"index_{alias_id}"), + web.get(p + r"/logo", h.logo, name=f"logo_{alias_id}"), + web.get(p + r"/{id:\d+}/view", h.info, name=f"info_{alias_id}"), + web.get( + p + r"/{id:\d+}/thumbnail", + h.thumbnail_get, + name=f"thumbnail_get_{alias_id}", + ), + web.get( + p + r"/{id:\d+}/{filename}", + h.download_get, + name=f"download_get_{alias_id}", + ), + web.head( + p + r"/{id:\d+}/{filename}", + h.download_head, + name=f"download_head_{alias_id}", + ), ] if index_all: @@ -56,16 +70,14 @@ async def setup_routes(app, handler): continue alias_id = h.generate_alias_id(chat) - p = "/{chat:" + alias_id + "}" - routes.extend(get_common_routes(p)) + routes.extend(get_common_routes(alias_id)) log.debug(f"Index added for {chat.id} at /{alias_id}") else: for chat_id in include_chats: chat = await client.get_entity(chat_id) alias_id = h.generate_alias_id(chat) - p = "/{chat:" + alias_id + "}" - routes.extend(get_common_routes(p)) # returns list() of common routes + routes.extend(get_common_routes(alias_id)) # returns list() of common routes log.debug(f"Index added for {chat.id} at /{alias_id}") routes.append(web.view(r"/{wildcard:.*}", h.wildcard)) app.add_routes(routes) diff --git a/app/telegram.py b/app/telegram.py index 37bfefe..083ede9 100644 --- a/app/telegram.py +++ b/app/telegram.py @@ -12,27 +12,37 @@ class Client(TelegramClient): self.log = logging.getLogger(__name__) async def download(self, file, file_size, offset, limit): - part_size_kb = utils.get_appropriated_part_size(file_size) - part_size = int(part_size_kb * 1024) + part_size = utils.get_appropriated_part_size(file_size) * 1024 first_part_cut = offset % part_size first_part = math.floor(offset / part_size) last_part_cut = part_size - (limit % part_size) last_part = math.ceil(limit / part_size) part_count = math.ceil(file_size / part_size) part = first_part + self.log.debug( + f"""Request Details + part_size(bytes) = {part_size}, + first_part = {first_part}, cut = {first_part_cut}(length={part_size-first_part_cut}), + last_part = {last_part}, cut = {last_part_cut}(length={last_part_cut}), + parts_count = {part_count} + """ + ) try: async for chunk in self.iter_download( file, offset=first_part * part_size, request_size=part_size ): + self.log.debug(f"Part {part}/{last_part} (total {part_count}) served!") if part == first_part: yield chunk[first_part_cut:] - elif part == last_part - 1: + elif part == last_part: yield chunk[:last_part_cut] + break else: yield chunk - self.log.debug(f"Part {part}/{last_part} (total {part_count}) served!") + part += 1 - self.log.debug("serving finished") + + self.log.debug(f"serving finished") except (GeneratorExit, StopAsyncIteration, asyncio.CancelledError): self.log.debug("file serve interrupted") raise diff --git a/app/views/__init__.py b/app/views/__init__.py index df9ccef..947f08a 100644 --- a/app/views/__init__.py +++ b/app/views/__init__.py @@ -12,6 +12,7 @@ from .logo_view import LogoView from .thumbnail_view import ThumbnailView from .login_view import LoginView from .logout_view import LogoutView +from .faviconicon_view import FaviconIconView from .middlewhere import middleware_factory @@ -25,6 +26,7 @@ class Views( WildcardView, LoginView, LogoutView, + FaviconIconView, ): def __init__(self, client): self.client = client @@ -38,7 +40,7 @@ class Views( while True: orig_id = f"{chat_id}" # the original id unique_hash = hashlib.md5(orig_id.encode()).digest() - alias_id = base64.urlsafe_b64encode(unique_hash).decode()[: self.url_len] + alias_id = base64.b64encode(unique_hash, b"__").decode()[: self.url_len] if alias_id in self.chat_ids: self.url_len += ( diff --git a/app/views/faviconicon_view.py b/app/views/faviconicon_view.py new file mode 100644 index 0000000..2663293 --- /dev/null +++ b/app/views/faviconicon_view.py @@ -0,0 +1,33 @@ +import random +from PIL import Image, ImageDraw, ImageFont + +from aiohttp import web + +from app.config import logo_folder + + +class FaviconIconView: + async def faviconicon(self, req): + favicon_path = logo_folder.joinpath("favicon.ico") + text = "T" + if not favicon_path.exists(): + W, H = (360, 360) + color = tuple((random.randint(0, 255) for _ in range(3))) + im = Image.new("RGB", (W, H), color) + draw = ImageDraw.Draw(im) + font = ImageFont.truetype("arial.ttf", 50) + w, h = draw.textsize(text, font=font) + draw.text(((W - w) / 2, (H - h) / 2), text, fill="white", font=font) + im.save(favicon_path) + + with open(favicon_path, "rb") as fp: + body = fp.read() + + return web.Response( + status=200, + body=body, + headers={ + "Content-Type": "image/jpeg", + "Content-Disposition": 'inline; filename="favicon.ico"', + }, + ) diff --git a/app/views/index_view.py b/app/views/index_view.py index 9e1a048..da34f6a 100644 --- a/app/views/index_view.py +++ b/app/views/index_view.py @@ -105,5 +105,5 @@ class IndexView: "block_downloads": block_downloads, "m3u_option": "" if not req.app["is_authenticated"] - else f"{req.app['is_authenticated']}:{req.app['is_authenticated']}@", + else f"{req.app['username']}:{req.app['password']}@", } diff --git a/app/views/logo_view.py b/app/views/logo_view.py index 7df0ed0..bc1ea25 100644 --- a/app/views/logo_view.py +++ b/app/views/logo_view.py @@ -1,5 +1,4 @@ import logging -import math from PIL import Image, ImageDraw, ImageFont import random diff --git a/app/views/middlewhere.py b/app/views/middlewhere.py index 4e5232d..1464d62 100644 --- a/app/views/middlewhere.py +++ b/app/views/middlewhere.py @@ -1,7 +1,7 @@ import time import logging -from aiohttp.web import middleware, HTTPFound +from aiohttp.web import middleware, HTTPFound, Response from aiohttp import BasicAuth, hdrs from aiohttp_session import get_session @@ -12,6 +12,17 @@ log = logging.getLogger(__name__) def _do_basic_auth_check(request): auth_header = request.headers.get(hdrs.AUTHORIZATION) if not auth_header: + if "download_" in request.match_info.route.name: + return Response( + body=b"", + status=401, + reason="UNAUTHORIZED", + headers={ + hdrs.WWW_AUTHENTICATE: 'Basic realm=""', + hdrs.CONTENT_TYPE: "text/html; charset=utf-8", + hdrs.CONNECTION: "keep-alive", + }, + ) return try: @@ -25,6 +36,12 @@ def _do_basic_auth_check(request): if auth.login is None or auth.password is None: return + if ( + auth.login != request.app["username"] + or auth.password != request.app["password"] + ): + return + return True @@ -50,7 +67,7 @@ def middleware_factory(): basic_auth_check_resp = _do_basic_auth_check(request) - if basic_auth_check_resp is not None: + if basic_auth_check_resp is True: return await handler(request) cookies_auth_check_resp = await _do_cookies_auth_check(request) @@ -58,6 +75,9 @@ def middleware_factory(): if cookies_auth_check_resp is not None: return await handler(request) + if isinstance(basic_auth_check_resp, Response): + return basic_auth_check_resp + return HTTPFound(url) return await handler(request)