bulid 63
This commit is contained in:
139
hockey_data/auth_router.py
Normal file
139
hockey_data/auth_router.py
Normal file
@@ -0,0 +1,139 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import html
|
||||
import os
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
|
||||
from .auth_bridge import HOCKEY_COOKIE_NAME, HOCKEY_SESSION_SECONDS, HockeyAuthDependencies
|
||||
|
||||
|
||||
def create_hockey_auth_router(auth: HockeyAuthDependencies) -> APIRouter:
|
||||
router = APIRouter(tags=["Hockey authentication"])
|
||||
|
||||
@router.get("/login", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def login_page(request: Request, reason: str = ""):
|
||||
if await auth.optional_user(request):
|
||||
return RedirectResponse("/", status_code=303)
|
||||
messages = {
|
||||
"expired": "Сессия завершена. Войдите снова.",
|
||||
"logout": "Вы вышли из хоккейной панели.",
|
||||
}
|
||||
return _login_response(message=messages.get(reason, ""))
|
||||
|
||||
@router.post("/login", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def login_submit(request: Request):
|
||||
try:
|
||||
content_length = int(request.headers.get("content-length", "0") or 0)
|
||||
except ValueError:
|
||||
content_length = 16_385
|
||||
if content_length > 16_384:
|
||||
return _login_response(error="Слишком большой запрос.", status_code=413)
|
||||
values = parse_qs((await request.body()).decode("utf-8", errors="replace"))
|
||||
username = (values.get("username") or [""])[0]
|
||||
password = (values.get("password") or [""])[0]
|
||||
|
||||
try:
|
||||
result = auth.adapter.login(username, password)
|
||||
except HTTPException as error:
|
||||
return _login_response(
|
||||
error=str(error.detail),
|
||||
username=username,
|
||||
status_code=error.status_code,
|
||||
)
|
||||
if result is None:
|
||||
return _login_response(
|
||||
error="Неверный логин или пароль.",
|
||||
username=username,
|
||||
status_code=401,
|
||||
)
|
||||
|
||||
token, _user = result
|
||||
response = RedirectResponse("/", status_code=303)
|
||||
secure_setting = os.getenv("HOCKEY_COOKIE_SECURE", "").strip().lower()
|
||||
secure = (
|
||||
secure_setting in {"1", "true", "yes", "on"}
|
||||
if secure_setting
|
||||
else request.url.scheme == "https"
|
||||
)
|
||||
response.set_cookie(
|
||||
HOCKEY_COOKIE_NAME,
|
||||
token,
|
||||
httponly=True,
|
||||
secure=secure,
|
||||
samesite="lax",
|
||||
max_age=HOCKEY_SESSION_SECONDS,
|
||||
path="/",
|
||||
)
|
||||
return response
|
||||
|
||||
@router.post("/logout", include_in_schema=False)
|
||||
async def logout(request: Request):
|
||||
auth.adapter.logout(request.cookies.get(HOCKEY_COOKIE_NAME, ""))
|
||||
response = RedirectResponse("/login?reason=logout", status_code=303)
|
||||
response.delete_cookie(HOCKEY_COOKIE_NAME, path="/")
|
||||
return response
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _login_response(
|
||||
*,
|
||||
error: str = "",
|
||||
message: str = "",
|
||||
username: str = "",
|
||||
status_code: int = 200,
|
||||
) -> HTMLResponse:
|
||||
error_html = f'<div class="notice error">{html.escape(error)}</div>' if error else ""
|
||||
message_html = (
|
||||
f'<div class="notice success">{html.escape(message)}</div>' if message else ""
|
||||
)
|
||||
page = f"""<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Вход — Хоккейная панель</title>
|
||||
<style>
|
||||
:root {{ color-scheme: dark; font-family: Inter, Segoe UI, Arial, sans-serif; }}
|
||||
* {{ box-sizing: border-box; }}
|
||||
body {{ margin: 0; min-height: 100vh; display: grid; place-items: center; color: #eef5ff;
|
||||
background: radial-gradient(circle at 20% 10%, #173e69 0, transparent 35%),
|
||||
linear-gradient(145deg, #06101c, #0a1c2f 55%, #07111d); }}
|
||||
main {{ width: min(430px, calc(100vw - 32px)); padding: 38px; border: 1px solid #31506f;
|
||||
border-radius: 22px; background: rgba(8, 24, 40, .94); box-shadow: 0 28px 90px #0009; }}
|
||||
.mark {{ display: inline-flex; padding: 7px 11px; border-radius: 999px; color: #8acbff;
|
||||
background: #103554; font-size: 12px; font-weight: 800; letter-spacing: .12em; }}
|
||||
h1 {{ margin: 20px 0 8px; font-size: 29px; }}
|
||||
p {{ margin: 0 0 26px; color: #9fb2c5; line-height: 1.5; }}
|
||||
label {{ display: grid; gap: 8px; margin: 16px 0; color: #c9d8e6; font-size: 13px; font-weight: 700; }}
|
||||
input {{ width: 100%; border: 1px solid #35526d; border-radius: 12px; padding: 13px 14px;
|
||||
color: white; background: #071421; outline: none; font: inherit; }}
|
||||
input:focus {{ border-color: #3ea6ff; box-shadow: 0 0 0 3px #168ee633; }}
|
||||
button {{ width: 100%; margin-top: 12px; border: 0; border-radius: 12px; padding: 14px;
|
||||
color: #031321; background: linear-gradient(135deg, #61c4ff, #35e0c1); font-weight: 900;
|
||||
cursor: pointer; }}
|
||||
.notice {{ margin: 16px 0; border-radius: 10px; padding: 11px 13px; font-size: 13px; }}
|
||||
.error {{ color: #ffd4d4; background: #5d202b; }} .success {{ color: #caffeb; background: #174c3e; }}
|
||||
footer {{ margin-top: 23px; color: #71879b; text-align: center; font-size: 12px; }}
|
||||
</style>
|
||||
</head>
|
||||
<body><main>
|
||||
<span class="mark">STAT2TV · HOCKEY</span>
|
||||
<h1>Вход в хоккейную панель</h1>
|
||||
<p>Используйте действующий аккаунт WFL. После входа откроется рабочий интерфейс хоккея.</p>
|
||||
{message_html}{error_html}
|
||||
<form method="post" action="/login" autocomplete="on">
|
||||
<label>Логин<input name="username" value="{html.escape(username)}" autocomplete="username" required autofocus></label>
|
||||
<label>Пароль<input name="password" type="password" autocomplete="current-password" required></label>
|
||||
<button type="submit">Войти</button>
|
||||
</form>
|
||||
<footer>Аккаунты проверяются в WFL · пароль не сохраняется в хоккейной базе</footer>
|
||||
</main></body></html>"""
|
||||
return HTMLResponse(
|
||||
page,
|
||||
status_code=status_code,
|
||||
headers={"Cache-Control": "no-store", "Pragma": "no-cache"},
|
||||
)
|
||||
Reference in New Issue
Block a user