This repository has been archived on 2026-10-28. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
karldns.de/router_dyndns/routers/management_shared.py
T

124 lines
3.9 KiB
Python

from __future__ import annotations
from fastapi import HTTPException, Request, Response
from fastapi.responses import HTMLResponse
from starlette.concurrency import run_in_threadpool
from ..ddns_models import DdnsSettings
from ..ddns_security import (
DASHBOARD_UNLOCK_COOKIE,
dashboard_csrf_token,
sign_dashboard_unlock_cookie,
verify_dashboard_unlock_cookie,
)
from ..ddns_service import DdnsService
from ..ddns_store import DdnsStore
from ..store.types import AccountRow
from ..web.templates_management import _render_management_page, _render_management_unlock_page
ManagementAccount = AccountRow
def get_active_management_account(store: DdnsStore, management_slug: str) -> ManagementAccount:
account = store.get_account_by_management_slug(management_slug)
if not account or account["disabled"]:
raise HTTPException(status_code=404, detail="not found")
return account
async def get_active_management_account_async(store: DdnsStore, management_slug: str) -> ManagementAccount:
return await run_in_threadpool(get_active_management_account, store, management_slug)
def has_management_access(request: Request, management_slug: str, account: ManagementAccount) -> bool:
password_hash = account.get("dashboard_password_hash")
if not password_hash:
return True
return verify_dashboard_unlock_cookie(
str(account["hostname"]),
management_slug,
str(password_hash),
request.cookies.get(DASHBOARD_UNLOCK_COOKIE),
)
def locked_management_response(management_slug: str, account: ManagementAccount) -> HTMLResponse:
return HTMLResponse(_render_management_unlock_page(management_slug, account, dashboard_csrf_token(management_slug, account)), status_code=401)
def management_error_response(
service: DdnsService,
settings: DdnsSettings,
management_slug: str,
account: ManagementAccount,
message: str,
) -> HTMLResponse:
return HTMLResponse(
_render_management_page(service, settings, management_slug, account, dashboard_csrf_token(management_slug, account), message=message),
status_code=400,
)
async def management_error_response_async(
service: DdnsService,
settings: DdnsSettings,
management_slug: str,
account: ManagementAccount,
message: str,
) -> HTMLResponse:
return await run_in_threadpool(management_error_response, service, settings, management_slug, account, message)
async def render_management_page_async(
service: DdnsService,
settings: DdnsSettings,
management_slug: str,
account: ManagementAccount,
csrf_token: str,
message: str | None = None,
challenge: dict[str, str | None] | None = None,
created_account: dict[str, str] | None = None,
) -> str:
return await run_in_threadpool(
_render_management_page,
service,
settings,
management_slug,
account,
csrf_token,
message,
challenge,
created_account,
)
def set_unlock_cookie(
response: Response,
settings: DdnsSettings,
management_slug: str,
account: ManagementAccount,
max_age_seconds: int,
) -> None:
password_hash = account.get("dashboard_password_hash")
if not password_hash:
return
response.set_cookie(
DASHBOARD_UNLOCK_COOKIE,
sign_dashboard_unlock_cookie(str(account["hostname"]), management_slug, str(password_hash), max_age_seconds),
max_age=max_age_seconds,
path=f"/m/{management_slug}",
httponly=True,
samesite="lax",
secure=settings.public_base_url.startswith("https://"),
)
def delete_unlock_cookie(response: Response, management_slug: str) -> None:
response.delete_cookie(DASHBOARD_UNLOCK_COOKIE, path=f"/m/{management_slug}", httponly=True, samesite="lax")
def unlock_cookie_seconds(settings: DdnsSettings, stay_unlocked: str) -> int:
if stay_unlocked:
return settings.dashboard_stay_unlocked_seconds
return settings.dashboard_unlock_seconds