Вход в хоккейную панель
Используйте действующий аккаунт WFL. После входа откроется рабочий интерфейс хоккея.
{message_html}{error_html}from __future__ import annotations import html import math import os from urllib.parse import parse_qs from fastapi import APIRouter, HTTPException, Request from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse from pydantic import BaseModel, Field from .async_work import run_db from .auth_bridge import HOCKEY_COOKIE_NAME, HOCKEY_SESSION_SECONDS, HockeyAuthDependencies class SessionActivityPayload(BaseModel): idle_ms: int = Field(default=0, ge=0, le=HOCKEY_SESSION_SECONDS * 1000) def _set_session_cookie(response, request: Request, token: str, max_age: int) -> None: 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=max_age, path="/") 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": "Сессия завершена. Войдите снова.", "inactive": "Вы вышли из сессии после двух часов без действий. Войдите снова.", "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 = await run_db(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) _set_session_cookie(response, request, token, HOCKEY_SESSION_SECONDS) return response async def session_response(request: Request, idle_ms: int | None = None): token = request.cookies.get(HOCKEY_COOKIE_NAME, "") result = await run_db(auth.adapter.session_status, token, idle_ms=idle_ms) if result is None: return JSONResponse({"detail": "Сессия завершена. Войдите снова."}, status_code=401, headers={"Cache-Control": "no-store"}) response = JSONResponse(result, headers={"Cache-Control": "no-store"}) if idle_ms is not None: # Renew the browser cookie together with the database deadline. # A fixed Max-Age on login used to eject active operators after 4h. _set_session_cookie(response, request, token, math.ceil(result["expires_in_seconds"])) return response @router.get("/api/hockey/auth/session") async def session_status(request: Request): return await session_response(request) @router.post("/api/hockey/auth/activity") async def session_activity(request: Request, payload: SessionActivityPayload): return await session_response(request, payload.idle_ms) @router.post("/logout", include_in_schema=False) async def logout(request: Request): await run_db(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'
Используйте действующий аккаунт WFL. После входа откроется рабочий интерфейс хоккея.
{message_html}{error_html}