diff --git a/README.md b/README.md
index e9b510c..d63308f 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@ pip3 install -U -r requirements.txt
| `PASSWORD` (optional) | Password for authentication, defaults to `''`.
| `SHORT_URL_LEN` (optional) | Url length for aliases
| `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.
+| `SECRET_KEY` (optional) | 32 characters long string for signing the session cookies, required if authentication is enabled.
* **Setting value for `INDEX_SETTINGS`**
diff --git a/app/config.py b/app/config.py
index a513e53..c74cf30 100644
--- a/app/config.py
+++ b/app/config.py
@@ -55,6 +55,8 @@ authenticated = username and password
SESSION_COOKIE_LIFETIME = int(os.environ.get("SESSION_COOKIE_LIFETIME") or "60")
try:
SECRET_KEY = os.environ["SECRET_KEY"]
+ if len(SECRET_KEY) != 32:
+ raise ValueError("SECRET_KEY should be exactly 32 charaters long")
except (KeyError, ValueError):
if authenticated:
traceback.print_exc()
diff --git a/app/main.py b/app/main.py
index d9dbc8b..52b51fc 100644
--- a/app/main.py
+++ b/app/main.py
@@ -5,6 +5,8 @@ import logging
import aiohttp_jinja2
import jinja2
from aiohttp import web
+from aiohttp_session import session_middleware
+from aiohttp_session.cookie_storage import EncryptedCookieStorage
from .telegram import Client
from .routes import setup_routes
@@ -33,6 +35,13 @@ class Indexer:
def __init__(self):
self.server = web.Application(
middlewares=[
+ session_middleware(
+ EncryptedCookieStorage(
+ secret_key=SECRET_KEY.encode(),
+ max_age=60 * SESSION_COOKIE_LIFETIME,
+ cookie_name="TG_INDEX_SESSION"
+ )
+ ),
middleware_factory(),
]
)
@@ -42,8 +51,6 @@ class Indexer:
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()
diff --git a/app/routes.py b/app/routes.py
index 487fd88..758bce0 100644
--- a/app/routes.py
+++ b/app/routes.py
@@ -7,6 +7,7 @@ from .config import index_settings
log = logging.getLogger(__name__)
+
async def setup_routes(app, handler):
h = handler
client = h.client
@@ -22,17 +23,17 @@ async def setup_routes(app, handler):
web.post("/login", h.login_post, name="login_handle"),
web.get("/logout", h.logout_get, name="logout"),
]
+
def get_common_routes(p):
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+}/download", h.download_get),
- web.head(p + r"/{id:\d+}/download", h.download_head),
- web.get(p + r"/{id:\d+}/thumbnail", h.thumbnail_get),
- web.get(p + r"/{id:\d+}/v.mp4", h.download_get),
- web.head(p + r"/{id:\d+}/v.mp4", h.download_head),
- ]
+ 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),
+ ]
+
if index_all:
# print(await client.get_dialogs())
# dialogs = await client.get_dialogs()
@@ -56,9 +57,7 @@ async def setup_routes(app, handler):
alias_id = h.generate_alias_id(chat)
p = "/{chat:" + alias_id + "}"
- routes.extend(
- get_common_routes(p)
- )
+ routes.extend(get_common_routes(p))
log.debug(f"Index added for {chat.id} at /{alias_id}")
else:
@@ -66,9 +65,7 @@ async def setup_routes(app, handler):
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(p)) # 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/templates/index.html b/app/templates/index.html
index 56a221e..649c241 100644
--- a/app/templates/index.html
+++ b/app/templates/index.html
@@ -12,7 +12,7 @@
-
{{name}}
+
{{name}}
@@ -52,57 +52,59 @@
href="{{item.url}}"
class="text-sm flex flex-col items-center justify-center w-full min-h-full md:w-1/5 lg:w-1/6 rounded my-4 md:mx-1 shadow hover:shadow-md border-solid ">
-
{{item.file_id}}
-
{% if item.media %}
-
+
+
+
+
+ {{item.insight}}
+
+ {% if not block_downloads %}
+
+
+
+
-
-
+ {% if 'video' in item.mime_type %}
+
+
+
+
- {{item.insight}}
+
+
+ {% endif %}
+ {% endif %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
{% else %}
{{item.insight}}
{% endif %}
+
{{item.file_id}}
diff --git a/app/templates/js/playlist.js b/app/templates/js/playlist.js
index ff73986..0fe3cec 100644
--- a/app/templates/js/playlist.js
+++ b/app/templates/js/playlist.js
@@ -1,26 +1,9 @@
-function singleItemPlaylist(file,name){
- let hostUrl = 'http://' + window.location.host
+function singleItemPlaylist(file,name,basicAuth){
+ let hostUrl = `http://${basicAuth}${window.location.host}`
let pd = ""
pd += '#EXTM3U\n'
pd += `#EXTINF: ${name}\n`
pd += `${hostUrl}/${file}\n`
let blob = new Blob([pd], { endings: "native" });
saveAs(blob, `${name}.m3u`);
-}
-
-// function createPlaylist(indexSite, id, playlistName = "Playlist", duration = 60) {
-// let pd = ""
-// name = playlistName
-// pd += '#EXTM3U\n'
-// pd += `#EXTINF: ${duration * 60} | ${name}\n`
-// pd += `${indexSite}/${id}/v.mp4\n`
-// return pd
-// }
-
-// function playlist(id, name) {
-// hostUrl = 'https://' + window.location.host
-// playlistData = createPlaylist(hostUrl, id, name);
-// let blob = new Blob([playlistData], { endings: "native" });
-// saveAs(blob, `${name}.m3u`);
-// }
-
+}
diff --git a/app/util.py b/app/util.py
index 7be54e4..91a2cbb 100644
--- a/app/util.py
+++ b/app/util.py
@@ -1,14 +1,19 @@
+from urllib.parse import quote
+
+
def get_file_name(message):
if message.file.name:
- return message.file.name.replace('\n', ' ')
- ext = message.file.ext or ""
- return f"{message.date.strftime('%Y-%m-%d_%H:%M:%S')}{ext}"
+ name = message.file.name
+ else:
+ ext = message.file.ext or ""
+ name = f"{message.date.strftime('%Y-%m-%d_%H:%M:%S')}{ext}"
+ return quote(name)
def get_human_size(num):
base = 1024.0
- sufix_list = ['B','KiB','MiB','GiB','TiB','PiB','EiB','ZiB', 'YiB']
+ sufix_list = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]
for unit in sufix_list:
if abs(num) < base:
return f"{round(num, 2)} {unit}"
- num /= base
\ No newline at end of file
+ num /= base
diff --git a/app/views/index_view.py b/app/views/index_view.py
index faa7fbb..9e1a048 100644
--- a/app/views/index_view.py
+++ b/app/views/index_view.py
@@ -4,7 +4,7 @@ import aiohttp_jinja2
from telethon.tl import types
-from app.config import results_per_page
+from app.config import results_per_page, block_downloads
from app.util import get_file_name, get_human_size
@@ -51,16 +51,18 @@ class IndexView:
for m in messages:
entry = None
if m.file and not isinstance(m.media, types.MessageMediaWebPage):
+ filename = get_file_name(m)
+ insight = m.text[:60] if m.text else filename
entry = dict(
file_id=m.id,
media=True,
thumbnail=f"/{alias_id}/{m.id}/thumbnail",
mime_type=m.file.mime_type,
- insight=get_file_name(m),
+ filename=filename,
+ insight=insight,
human_size=get_human_size(m.file.size),
url=f"/{alias_id}/{m.id}/view",
- download=f"{alias_id}/{m.id}/download",
- vlc = f"{alias_id}/{m.id}/v.mp4",
+ download=f"{alias_id}/{m.id}/{filename}",
)
elif m.message:
entry = dict(
@@ -100,4 +102,8 @@ class IndexView:
"logo": f"/{alias_id}/logo",
"title": "Index of " + chat["title"],
"authenticated": req.app["is_authenticated"],
+ "block_downloads": block_downloads,
+ "m3u_option": ""
+ if not req.app["is_authenticated"]
+ else f"{req.app['is_authenticated']}:{req.app['is_authenticated']}@",
}
diff --git a/app/views/info_view.py b/app/views/info_view.py
index 31091ee..e275f8b 100644
--- a/app/views/info_view.py
+++ b/app/views/info_view.py
@@ -78,7 +78,7 @@ class InfoView:
"thumbnail": f"/{alias_id}/{file_id}/thumbnail",
"download_url": "#"
if block_downloads
- else f"/{alias_id}/{file_id}/download",
+ else f"/{alias_id}/{file_id}/{file_name}",
"page_id": alias_id,
"block_downloads": block_downloads,
}
diff --git a/app/views/login_view.py b/app/views/login_view.py
index 1229259..2288e33 100644
--- a/app/views/login_view.py
+++ b/app/views/login_view.py
@@ -1,9 +1,8 @@
import time
-import hmac
-import hashlib
from aiohttp import web
import aiohttp_jinja2
+from aiohttp_session import new_session
class LoginView:
@@ -20,32 +19,21 @@ class LoginView:
if "username" not in post_data:
loc = location.update_query({"error": "Username missing"})
- raise web.HTTPFound(location=loc)
+ return web.HTTPFound(location=loc)
if "password" not in post_data:
loc = location.update_query({"error": "Password missing"})
- raise web.HTTPFound(location=loc)
+ return web.HTTPFound(location=loc)
authenticated = (post_data["username"] == req.app["username"]) and (
post_data["password"] == req.app["password"]
)
if not authenticated:
loc = location.update_query({"error": "Wrong Username or Passowrd"})
- raise web.HTTPFound(location=loc)
+ return web.HTTPFound(location=loc)
- resp = web.Response(status=302, headers={"Location": redirect_to})
- 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
+ session = await new_session(req)
+ print(session)
+ session["logged_in"] = True
+ session["logged_in_at"] = time.time()
+ return web.HTTPFound(location=redirect_to)
diff --git a/app/views/logout_view.py b/app/views/logout_view.py
index 6dbe71a..db851c9 100644
--- a/app/views/logout_view.py
+++ b/app/views/logout_view.py
@@ -1,11 +1,10 @@
+from aiohttp_session import get_session
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
+ session = await get_session(req)
+ session["logged_in"] = False
+
+ return web.HTTPFound(req.app.router["home"].url_for())
diff --git a/app/views/middlewhere.py b/app/views/middlewhere.py
index 57a4dc0..4e5232d 100644
--- a/app/views/middlewhere.py
+++ b/app/views/middlewhere.py
@@ -1,14 +1,42 @@
import time
-import hmac
-import hashlib
import logging
from aiohttp.web import middleware, HTTPFound
+from aiohttp import BasicAuth, hdrs
+from aiohttp_session import get_session
log = logging.getLogger(__name__)
+def _do_basic_auth_check(request):
+ auth_header = request.headers.get(hdrs.AUTHORIZATION)
+ if not auth_header:
+ return
+
+ try:
+ auth = BasicAuth.decode(auth_header=auth_header)
+ except ValueError:
+ auth = None
+
+ if not auth:
+ return
+
+ if auth.login is None or auth.password is None:
+ return
+
+ return True
+
+
+async def _do_cookies_auth_check(request):
+ session = await get_session(request)
+ if not session.get("logged_in", False):
+ return
+
+ session["last_at"] = time.time()
+ return True
+
+
def middleware_factory():
@middleware
async def factory(request, handler):
@@ -16,36 +44,21 @@ def middleware_factory():
"/login",
"/logout",
]:
- cookies = request.cookies
url = request.app.router["login_page"].url_for()
if str(request.rel_url) != "/":
url = url.with_query(redirect_to=str(request.rel_url))
- if any(x not in cookies for x in ("_tgindex_session", "_tgindex_secret")):
- raise HTTPFound(url)
+ basic_auth_check_resp = _do_basic_auth_check(request)
- 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)
+ if basic_auth_check_resp is not None:
+ return await handler(request)
- 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)
+ cookies_auth_check_resp = await _do_cookies_auth_check(request)
+
+ if cookies_auth_check_resp is not None:
+ return await handler(request)
+
+ return HTTPFound(url)
return await handler(request)
diff --git a/requirements.txt b/requirements.txt
index 42ed3cf..81a07c8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,3 +3,4 @@ aiohttp-jinja2
telethon>=1.16.4
cryptg
pillow
+aiohttp_session[secure]