from __future__ import annotations import sqlite3 from fastapi import APIRouter, HTTPException, Request from fastapi.responses import HTMLResponse from starlette.concurrency import run_in_threadpool from ..ddns_security import client_ip, dashboard_csrf_token, require_dashboard_csrf from ..ddns_service import normalize_domain from ..dependencies import ServiceDep, SettingsDep, StoreDep from ..event_statuses import ( DASHBOARD_CUSTOM_HOSTNAME_CREATED, DASHBOARD_DOMAIN_CLAIM_CREATED, DASHBOARD_DOMAIN_CLAIM_PENDING, DASHBOARD_DOMAIN_CLAIM_VERIFIED, ) from ..web.forms import first_form_value, request_form from ..web.templates_public import _render_public_home from .management_shared import ( get_active_management_account_async, has_management_access, locked_management_response, render_management_page_async, ) from .path_params import ClaimSecretPath, ManagementSlugPath router = APIRouter() @router.get("/d/{claim_secret}", response_class=HTMLResponse, include_in_schema=False) async def manage_domain_claim(claim_secret: ClaimSecretPath, settings: SettingsDep, store: StoreDep, service: ServiceDep) -> str: challenge = await run_in_threadpool(store.get_domain_challenge_by_secret, claim_secret) if not challenge: raise HTTPException(status_code=404, detail="not found") return await run_in_threadpool(_render_public_home, service, settings, None, None, challenge) @router.post("/m/{management_slug}/domains/request", response_class=HTMLResponse, include_in_schema=False) async def request_dashboard_domain( management_slug: ManagementSlugPath, request: Request, settings: SettingsDep, store: StoreDep, service: ServiceDep, ) -> str: account = await get_active_management_account_async(store, management_slug) if not has_management_access(request, settings, management_slug, account): return locked_management_response(management_slug, account) form = await request_form(request, settings) require_dashboard_csrf(management_slug, account, first_form_value(form, "csrf")) try: challenge = await run_in_threadpool( service.create_domain_challenge, first_form_value(form, "domain"), str(account["hostname"]), ) except sqlite3.IntegrityError as exc: raise HTTPException(status_code=409, detail="domain is already verified") from exc await run_in_threadpool( store.log_update_event, str(account["hostname"]), None, None, DASHBOARD_DOMAIN_CLAIM_CREATED, "domain claim created", client_ip(request, settings), ) return await render_management_page_async( service, settings, management_slug, account, dashboard_csrf_token(management_slug, account), message="Claim created. Add the TXT record, then verify it.", challenge=challenge, ) @router.post("/m/{management_slug}/domains/verify", response_class=HTMLResponse, include_in_schema=False) async def verify_dashboard_domain( management_slug: ManagementSlugPath, request: Request, settings: SettingsDep, store: StoreDep, service: ServiceDep, ) -> str: account = await get_active_management_account_async(store, management_slug) if not has_management_access(request, settings, management_slug, account): return locked_management_response(management_slug, account) form = await request_form(request, settings) require_dashboard_csrf(management_slug, account, first_form_value(form, "csrf")) domain = normalize_domain(first_form_value(form, "domain")) claim_secret = first_form_value(form, "claim_secret") found, challenge = await run_in_threadpool(service.verify_domain, domain, claim_secret, str(account["hostname"])) message = "Domain verified. You can create a hostname now." if found else "TXT record not found yet. Try again in a few minutes." await run_in_threadpool( store.log_update_event, str(account["hostname"]), None, None, DASHBOARD_DOMAIN_CLAIM_VERIFIED if found else DASHBOARD_DOMAIN_CLAIM_PENDING, "domain claim verified" if found else "domain claim TXT not found", client_ip(request, settings), ) return await render_management_page_async( service, settings, management_slug, account, dashboard_csrf_token(management_slug, account), message=message, challenge=challenge, ) @router.post("/m/{management_slug}/domains/hostnames", response_class=HTMLResponse, include_in_schema=False) async def create_dashboard_custom_hostname( management_slug: ManagementSlugPath, request: Request, settings: SettingsDep, store: StoreDep, service: ServiceDep, ) -> str: account = await get_active_management_account_async(store, management_slug) if not has_management_access(request, settings, management_slug, account): return locked_management_response(management_slug, account) form = await request_form(request, settings) require_dashboard_csrf(management_slug, account, first_form_value(form, "csrf")) created = await run_in_threadpool( service.create_custom_account, first_form_value(form, "hostname"), first_form_value(form, "claim_secret"), None, str(account["hostname"]), ) await run_in_threadpool( store.log_update_event, str(account["hostname"]), None, None, DASHBOARD_CUSTOM_HOSTNAME_CREATED, "custom hostname created", client_ip(request, settings), ) return await render_management_page_async( service, settings, management_slug, account, dashboard_csrf_token(management_slug, account), message="Hostname created. Copy these router settings now.", created_account=created, )