add basic auth support and few under the hood changes
This commit is contained in:
parent
9eabc78872
commit
11f33611ae
|
@ -51,7 +51,7 @@ pip3 install -U -r requirements.txt
|
||||||
| `PASSWORD` (optional) | Password for authentication, defaults to `''`.
|
| `PASSWORD` (optional) | Password for authentication, defaults to `''`.
|
||||||
| `SHORT_URL_LEN` (optional) | Url length for aliases
|
| `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.
|
| `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`**
|
* **Setting value for `INDEX_SETTINGS`**
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,8 @@ authenticated = username and password
|
||||||
SESSION_COOKIE_LIFETIME = int(os.environ.get("SESSION_COOKIE_LIFETIME") or "60")
|
SESSION_COOKIE_LIFETIME = int(os.environ.get("SESSION_COOKIE_LIFETIME") or "60")
|
||||||
try:
|
try:
|
||||||
SECRET_KEY = os.environ["SECRET_KEY"]
|
SECRET_KEY = os.environ["SECRET_KEY"]
|
||||||
|
if len(SECRET_KEY) != 32:
|
||||||
|
raise ValueError("SECRET_KEY should be exactly 32 charaters long")
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
if authenticated:
|
if authenticated:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
11
app/main.py
11
app/main.py
|
@ -5,6 +5,8 @@ import logging
|
||||||
import aiohttp_jinja2
|
import aiohttp_jinja2
|
||||||
import jinja2
|
import jinja2
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
from aiohttp_session import session_middleware
|
||||||
|
from aiohttp_session.cookie_storage import EncryptedCookieStorage
|
||||||
|
|
||||||
from .telegram import Client
|
from .telegram import Client
|
||||||
from .routes import setup_routes
|
from .routes import setup_routes
|
||||||
|
@ -33,6 +35,13 @@ class Indexer:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.server = web.Application(
|
self.server = web.Application(
|
||||||
middlewares=[
|
middlewares=[
|
||||||
|
session_middleware(
|
||||||
|
EncryptedCookieStorage(
|
||||||
|
secret_key=SECRET_KEY.encode(),
|
||||||
|
max_age=60 * SESSION_COOKIE_LIFETIME,
|
||||||
|
cookie_name="TG_INDEX_SESSION"
|
||||||
|
)
|
||||||
|
),
|
||||||
middleware_factory(),
|
middleware_factory(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -42,8 +51,6 @@ class Indexer:
|
||||||
self.server["is_authenticated"] = authenticated
|
self.server["is_authenticated"] = authenticated
|
||||||
self.server["username"] = username
|
self.server["username"] = username
|
||||||
self.server["password"] = password
|
self.server["password"] = password
|
||||||
self.server["SESSION_COOKIE_LIFETIME"] = SESSION_COOKIE_LIFETIME
|
|
||||||
self.server["SECRET_KEY"] = SECRET_KEY
|
|
||||||
|
|
||||||
async def startup(self):
|
async def startup(self):
|
||||||
await self.tg_client.start()
|
await self.tg_client.start()
|
||||||
|
|
|
@ -7,6 +7,7 @@ from .config import index_settings
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def setup_routes(app, handler):
|
async def setup_routes(app, handler):
|
||||||
h = handler
|
h = handler
|
||||||
client = h.client
|
client = h.client
|
||||||
|
@ -22,17 +23,17 @@ async def setup_routes(app, handler):
|
||||||
web.post("/login", h.login_post, name="login_handle"),
|
web.post("/login", h.login_post, name="login_handle"),
|
||||||
web.get("/logout", h.logout_get, name="logout"),
|
web.get("/logout", h.logout_get, name="logout"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_common_routes(p):
|
def get_common_routes(p):
|
||||||
return [
|
return [
|
||||||
web.get(p, h.index),
|
web.get(p, h.index),
|
||||||
web.get(p + r"/logo", h.logo),
|
web.get(p + r"/logo", h.logo),
|
||||||
web.get(p + r"/{id:\d+}/view", h.info),
|
web.get(p + r"/{id:\d+}/view", h.info),
|
||||||
web.get(p + r"/{id:\d+}/download", h.download_get),
|
web.get(p + r"/{id:\d+}/thumbnail", h.thumbnail_get),
|
||||||
web.head(p + r"/{id:\d+}/download", h.download_head),
|
web.get(p + r"/{id:\d+}/{filename}", h.download_get),
|
||||||
web.get(p + r"/{id:\d+}/thumbnail", h.thumbnail_get),
|
web.head(p + r"/{id:\d+}/{filename}", h.download_head),
|
||||||
web.get(p + r"/{id:\d+}/v.mp4", h.download_get),
|
]
|
||||||
web.head(p + r"/{id:\d+}/v.mp4", h.download_head),
|
|
||||||
]
|
|
||||||
if index_all:
|
if index_all:
|
||||||
# print(await client.get_dialogs())
|
# print(await client.get_dialogs())
|
||||||
# dialogs = 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)
|
alias_id = h.generate_alias_id(chat)
|
||||||
p = "/{chat:" + alias_id + "}"
|
p = "/{chat:" + alias_id + "}"
|
||||||
routes.extend(
|
routes.extend(get_common_routes(p))
|
||||||
get_common_routes(p)
|
|
||||||
)
|
|
||||||
log.debug(f"Index added for {chat.id} at /{alias_id}")
|
log.debug(f"Index added for {chat.id} at /{alias_id}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -66,9 +65,7 @@ async def setup_routes(app, handler):
|
||||||
chat = await client.get_entity(chat_id)
|
chat = await client.get_entity(chat_id)
|
||||||
alias_id = h.generate_alias_id(chat)
|
alias_id = h.generate_alias_id(chat)
|
||||||
p = "/{chat:" + alias_id + "}"
|
p = "/{chat:" + alias_id + "}"
|
||||||
routes.extend(
|
routes.extend(get_common_routes(p)) # returns list() of common routes
|
||||||
get_common_routes(p) # returns list() of common routes
|
|
||||||
)
|
|
||||||
log.debug(f"Index added for {chat.id} at /{alias_id}")
|
log.debug(f"Index added for {chat.id} at /{alias_id}")
|
||||||
routes.append(web.view(r"/{wildcard:.*}", h.wildcard))
|
routes.append(web.view(r"/{wildcard:.*}", h.wildcard))
|
||||||
app.add_routes(routes)
|
app.add_routes(routes)
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<div
|
<div
|
||||||
class="m-2 block md:flex items-center justify-center md:justify-start text-2xl md:text-right font-bold text-blue-500">
|
class="m-2 block md:flex items-center justify-center md:justify-start text-2xl md:text-right font-bold text-blue-500">
|
||||||
<img class="mx-auto md:ml-0 md:mr-1 my-2 w-16 h-16 rounded-full bg-black outline-none" src="{{logo}}">
|
<img class="mx-auto md:ml-0 md:mr-1 my-2 w-16 h-16 rounded-full bg-black outline-none" src="{{logo}}">
|
||||||
<h1> {{name}} </h1>
|
<a href="javascript:window.location.href=window.location.href"> {{name}} </a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="m-2">
|
<div class="m-2">
|
||||||
|
@ -52,57 +52,59 @@
|
||||||
href="{{item.url}}"
|
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 ">
|
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 ">
|
||||||
|
|
||||||
<div class="bg-blue-500 rounded text-white my-1 py-0 px-1">{{item.file_id}}</div>
|
|
||||||
|
|
||||||
{% if item.media %}
|
{% if item.media %}
|
||||||
|
|
||||||
<a href="{{item.url}}"><img src="{{item.thumbnail}}" class="w-full rounded shadow-inner"></a>
|
<a href="{{item.url}}"><img src="{{item.thumbnail}}" class="w-full rounded shadow-inner"></a>
|
||||||
|
|
||||||
|
<!-- Buttons container -->
|
||||||
|
<span class="item-buttons my-1 rounded shadow-inner">
|
||||||
|
|
||||||
<!-- Buttons container -->
|
<div class="p-4 text-dark py-0 px-2 my-1 ">{{item.insight}}</div>
|
||||||
<span class="item-buttons my-1 rounded shadow-inner">
|
|
||||||
|
|
||||||
<div class="p-4 text-dark py-0 px-2 my-1 ">{{item.insight}}</div>
|
{% if not block_downloads %}
|
||||||
|
<!-- Direct file download button -->
|
||||||
|
<a href="{{item.download}}"
|
||||||
|
class=" hover:bg-blue-300 text-gray-900 font-semibold py-1 px-2 rounded inline-flex items-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M15 13l-3 3m0 0l-3-3m3 3V8m0 13a9 9 0 110-18 9 9 0 010 18z" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
|
||||||
<!-- Direct file download button -->
|
{% if 'video' in item.mime_type %}
|
||||||
<a href="{{item.download}}"
|
<!-- Kodi/media player supported url ending with v.mp4 -->
|
||||||
class=" hover:bg-blue-300 text-gray-900 font-semibold py-1 px-2 rounded inline-flex items-center">
|
<a title="v.mp4" href="{{item.download}}"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24"
|
class=" hover:bg-blue-300 text-gray-900 font-semibold py-1 px-2 rounded inline-flex items-center">
|
||||||
stroke="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
stroke="currentColor">
|
||||||
d="M15 13l-3 3m0 0l-3-3m3 3V8m0 13a9 9 0 110-18 9 9 0 010 18z" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
</svg>
|
d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||||
</a>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
|
||||||
<!-- Kodi/media player supported url ending with v.mp4 -->
|
<!-- One click single item playlist Download -->
|
||||||
<a title="v.mp4" href="{{item.vlc}}"
|
<button title="{{item.filename}}.m3u" onclick="singleItemPlaylist('{{item.download}}','{{item.filename}}', '{{m3u_option}}');"
|
||||||
class=" hover:bg-blue-300 text-gray-900 font-semibold py-1 px-2 rounded inline-flex items-center">
|
class=" hover:bg-blue-300 text-gray-900 font-semibold py-1 px-2 rounded inline-flex items-center">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24"
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24"
|
||||||
stroke="currentColor">
|
stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
d="M3 4h13M3 8h9m-9 4h9m5-4v12m0 0l-4-4m4 4l4-4" />
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
</svg>
|
||||||
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
</button>
|
||||||
</svg>
|
{% endif %}
|
||||||
</a>
|
{% endif %}
|
||||||
|
|
||||||
<!-- One click single item playlist Download -->
|
</span>
|
||||||
<button title="{{item.insight}}.m3u" onclick='singleItemPlaylist("{{item.vlc}}","{{item.insight}}")'
|
|
||||||
class=" hover:bg-blue-300 text-gray-900 font-semibold py-1 px-2 rounded inline-flex items-center">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
||||||
d="M3 4h13M3 8h9m-9 4h9m5-4v12m0 0l-4-4m4 4l4-4" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href={{item.url}}>
|
<a href={{item.url}}>
|
||||||
<div class="p-4 rounded shadow-inner rounded text-dark py-0 px-2">{{item.insight}}</div>
|
<div class="p-4 rounded shadow-inner rounded text-dark py-0 px-2">{{item.insight}}</div>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<div class="bg-blue-500 rounded text-white my-1 py-0 px-1">{{item.file_id}}</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function singleItemPlaylist(file,name){
|
function singleItemPlaylist(file,name,basicAuth){
|
||||||
let hostUrl = 'http://' + window.location.host
|
let hostUrl = `http://${basicAuth}${window.location.host}`
|
||||||
let pd = ""
|
let pd = ""
|
||||||
pd += '#EXTM3U\n'
|
pd += '#EXTM3U\n'
|
||||||
pd += `#EXTINF: ${name}\n`
|
pd += `#EXTINF: ${name}\n`
|
||||||
|
@ -7,20 +7,3 @@ function singleItemPlaylist(file,name){
|
||||||
let blob = new Blob([pd], { endings: "native" });
|
let blob = new Blob([pd], { endings: "native" });
|
||||||
saveAs(blob, `${name}.m3u`);
|
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`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
13
app/util.py
13
app/util.py
|
@ -1,13 +1,18 @@
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
|
||||||
def get_file_name(message):
|
def get_file_name(message):
|
||||||
if message.file.name:
|
if message.file.name:
|
||||||
return message.file.name.replace('\n', ' ')
|
name = message.file.name
|
||||||
ext = message.file.ext or ""
|
else:
|
||||||
return f"{message.date.strftime('%Y-%m-%d_%H:%M:%S')}{ext}"
|
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):
|
def get_human_size(num):
|
||||||
base = 1024.0
|
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:
|
for unit in sufix_list:
|
||||||
if abs(num) < base:
|
if abs(num) < base:
|
||||||
return f"{round(num, 2)} {unit}"
|
return f"{round(num, 2)} {unit}"
|
||||||
|
|
|
@ -4,7 +4,7 @@ import aiohttp_jinja2
|
||||||
|
|
||||||
from telethon.tl import types
|
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
|
from app.util import get_file_name, get_human_size
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,16 +51,18 @@ class IndexView:
|
||||||
for m in messages:
|
for m in messages:
|
||||||
entry = None
|
entry = None
|
||||||
if m.file and not isinstance(m.media, types.MessageMediaWebPage):
|
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(
|
entry = dict(
|
||||||
file_id=m.id,
|
file_id=m.id,
|
||||||
media=True,
|
media=True,
|
||||||
thumbnail=f"/{alias_id}/{m.id}/thumbnail",
|
thumbnail=f"/{alias_id}/{m.id}/thumbnail",
|
||||||
mime_type=m.file.mime_type,
|
mime_type=m.file.mime_type,
|
||||||
insight=get_file_name(m),
|
filename=filename,
|
||||||
|
insight=insight,
|
||||||
human_size=get_human_size(m.file.size),
|
human_size=get_human_size(m.file.size),
|
||||||
url=f"/{alias_id}/{m.id}/view",
|
url=f"/{alias_id}/{m.id}/view",
|
||||||
download=f"{alias_id}/{m.id}/download",
|
download=f"{alias_id}/{m.id}/{filename}",
|
||||||
vlc = f"{alias_id}/{m.id}/v.mp4",
|
|
||||||
)
|
)
|
||||||
elif m.message:
|
elif m.message:
|
||||||
entry = dict(
|
entry = dict(
|
||||||
|
@ -100,4 +102,8 @@ class IndexView:
|
||||||
"logo": f"/{alias_id}/logo",
|
"logo": f"/{alias_id}/logo",
|
||||||
"title": "Index of " + chat["title"],
|
"title": "Index of " + chat["title"],
|
||||||
"authenticated": req.app["is_authenticated"],
|
"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']}@",
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ class InfoView:
|
||||||
"thumbnail": f"/{alias_id}/{file_id}/thumbnail",
|
"thumbnail": f"/{alias_id}/{file_id}/thumbnail",
|
||||||
"download_url": "#"
|
"download_url": "#"
|
||||||
if block_downloads
|
if block_downloads
|
||||||
else f"/{alias_id}/{file_id}/download",
|
else f"/{alias_id}/{file_id}/{file_name}",
|
||||||
"page_id": alias_id,
|
"page_id": alias_id,
|
||||||
"block_downloads": block_downloads,
|
"block_downloads": block_downloads,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import time
|
import time
|
||||||
import hmac
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import aiohttp_jinja2
|
import aiohttp_jinja2
|
||||||
|
from aiohttp_session import new_session
|
||||||
|
|
||||||
|
|
||||||
class LoginView:
|
class LoginView:
|
||||||
|
@ -20,32 +19,21 @@ class LoginView:
|
||||||
|
|
||||||
if "username" not in post_data:
|
if "username" not in post_data:
|
||||||
loc = location.update_query({"error": "Username missing"})
|
loc = location.update_query({"error": "Username missing"})
|
||||||
raise web.HTTPFound(location=loc)
|
return web.HTTPFound(location=loc)
|
||||||
|
|
||||||
if "password" not in post_data:
|
if "password" not in post_data:
|
||||||
loc = location.update_query({"error": "Password missing"})
|
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 (
|
authenticated = (post_data["username"] == req.app["username"]) and (
|
||||||
post_data["password"] == req.app["password"]
|
post_data["password"] == req.app["password"]
|
||||||
)
|
)
|
||||||
if not authenticated:
|
if not authenticated:
|
||||||
loc = location.update_query({"error": "Wrong Username or Passowrd"})
|
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})
|
session = await new_session(req)
|
||||||
now = time.time()
|
print(session)
|
||||||
resp.set_cookie(
|
session["logged_in"] = True
|
||||||
name="_tgindex_session",
|
session["logged_in_at"] = time.time()
|
||||||
value=str(now),
|
return web.HTTPFound(location=redirect_to)
|
||||||
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
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
|
from aiohttp_session import get_session
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
|
|
||||||
class LogoutView:
|
class LogoutView:
|
||||||
async def logout_get(self, req):
|
async def logout_get(self, req):
|
||||||
resp = web.Response(
|
session = await get_session(req)
|
||||||
status=302, headers={"Location": str(req.app.router["home"].url_for())}
|
session["logged_in"] = False
|
||||||
)
|
|
||||||
resp.del_cookie(name="_tgindex_session")
|
return web.HTTPFound(req.app.router["home"].url_for())
|
||||||
resp.del_cookie(name="_tgindex_secret")
|
|
||||||
return resp
|
|
||||||
|
|
|
@ -1,14 +1,42 @@
|
||||||
import time
|
import time
|
||||||
import hmac
|
|
||||||
import hashlib
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiohttp.web import middleware, HTTPFound
|
from aiohttp.web import middleware, HTTPFound
|
||||||
|
from aiohttp import BasicAuth, hdrs
|
||||||
|
from aiohttp_session import get_session
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
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():
|
def middleware_factory():
|
||||||
@middleware
|
@middleware
|
||||||
async def factory(request, handler):
|
async def factory(request, handler):
|
||||||
|
@ -16,36 +44,21 @@ def middleware_factory():
|
||||||
"/login",
|
"/login",
|
||||||
"/logout",
|
"/logout",
|
||||||
]:
|
]:
|
||||||
cookies = request.cookies
|
|
||||||
url = request.app.router["login_page"].url_for()
|
url = request.app.router["login_page"].url_for()
|
||||||
if str(request.rel_url) != "/":
|
if str(request.rel_url) != "/":
|
||||||
url = url.with_query(redirect_to=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")):
|
basic_auth_check_resp = _do_basic_auth_check(request)
|
||||||
raise HTTPFound(url)
|
|
||||||
|
|
||||||
tgindex_session = cookies["_tgindex_session"]
|
if basic_auth_check_resp is not None:
|
||||||
tgindex_secret = cookies["_tgindex_secret"]
|
return await handler(request)
|
||||||
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:
|
cookies_auth_check_resp = await _do_cookies_auth_check(request)
|
||||||
created_at = (
|
|
||||||
float(tgindex_session) + request.app["SESSION_COOKIE_LIFETIME"]
|
if cookies_auth_check_resp is not None:
|
||||||
)
|
return await handler(request)
|
||||||
if (
|
|
||||||
time.time()
|
return HTTPFound(url)
|
||||||
> 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 await handler(request)
|
||||||
|
|
||||||
|
|
|
@ -3,3 +3,4 @@ aiohttp-jinja2
|
||||||
telethon>=1.16.4
|
telethon>=1.16.4
|
||||||
cryptg
|
cryptg
|
||||||
pillow
|
pillow
|
||||||
|
aiohttp_session[secure]
|
||||||
|
|
Loading…
Reference in New Issue