Archived
200 lines
6.9 KiB
Python
200 lines
6.9 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from urllib.parse import parse_qs
|
|
|
|
from fastapi import APIRouter, HTTPException, Query, Request
|
|
from fastapi.responses import PlainTextResponse
|
|
from starlette.concurrency import run_in_threadpool
|
|
|
|
from ..dependencies import ServiceDep, StoreDep, apply_update_response
|
|
from ..dns import DnsProviderError
|
|
from ..event_statuses import DASHBOARD_HOSTNAME_ACTIVATED
|
|
from ..security import client_ip
|
|
from ..services.ddns import BadIpError
|
|
from .path_params import ManagementSlugPath, UpdateSlugPath
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get(
|
|
"/api/v1/router/register/{dashboard_secret}",
|
|
response_class=PlainTextResponse,
|
|
response_model=None,
|
|
operation_id="registerHostnameFromRouterGet",
|
|
tags=["router"],
|
|
)
|
|
async def register_hostname_get(
|
|
dashboard_secret: ManagementSlugPath,
|
|
request: Request,
|
|
store: StoreDep,
|
|
service: ServiceDep,
|
|
myip: str | None = Query(default=None),
|
|
ipaddr: str | None = Query(default=None),
|
|
myipv6: str | None = Query(default=None),
|
|
ip6addr: str | None = Query(default=None),
|
|
) -> str | PlainTextResponse:
|
|
return await _register_hostname(dashboard_secret, request, store, service, myip, ipaddr, myipv6, ip6addr)
|
|
|
|
|
|
@router.post(
|
|
"/api/v1/router/register/{dashboard_secret}",
|
|
response_class=PlainTextResponse,
|
|
response_model=None,
|
|
operation_id="registerHostnameFromRouterPost",
|
|
tags=["router"],
|
|
)
|
|
async def register_hostname_post(
|
|
dashboard_secret: ManagementSlugPath,
|
|
request: Request,
|
|
store: StoreDep,
|
|
service: ServiceDep,
|
|
myip: str | None = Query(default=None),
|
|
ipaddr: str | None = Query(default=None),
|
|
myipv6: str | None = Query(default=None),
|
|
ip6addr: str | None = Query(default=None),
|
|
) -> str | PlainTextResponse:
|
|
return await _register_hostname(dashboard_secret, request, store, service, myip, ipaddr, myipv6, ip6addr)
|
|
|
|
|
|
async def _register_hostname(
|
|
dashboard_secret: str,
|
|
request: Request,
|
|
store: StoreDep,
|
|
service: ServiceDep,
|
|
myip: str | None,
|
|
ipaddr: str | None,
|
|
myipv6: str | None,
|
|
ip6addr: str | None,
|
|
) -> str | PlainTextResponse:
|
|
myip, ipaddr, myipv6, ip6addr = await _ip_params_from_request(request, myip, ipaddr, myipv6, ip6addr)
|
|
account = await run_in_threadpool(store.get_account_by_management_slug, dashboard_secret)
|
|
if not account or account["disabled"]:
|
|
raise HTTPException(status_code=404, detail="not found")
|
|
hostname = str(account["hostname"])
|
|
if not service.hostname_is_managed(hostname):
|
|
raise HTTPException(status_code=404, detail="not found")
|
|
if not account["accepted_at"]:
|
|
try:
|
|
await run_in_threadpool(service.publish_initial_dns, request, hostname, myip, ipaddr, myipv6, ip6addr)
|
|
except BadIpError as exc:
|
|
return PlainTextResponse(f"badip {exc}", status_code=400)
|
|
except DnsProviderError:
|
|
return PlainTextResponse("911 dns registration failed", status_code=500)
|
|
activated = await run_in_threadpool(store.activate_account, hostname)
|
|
if not activated:
|
|
raise HTTPException(status_code=404, detail="not found")
|
|
await run_in_threadpool(
|
|
store.log_update_event,
|
|
hostname,
|
|
None,
|
|
None,
|
|
DASHBOARD_HOSTNAME_ACTIVATED,
|
|
"hostname registered",
|
|
client_ip(request, service.settings),
|
|
)
|
|
return "good registered"
|
|
|
|
|
|
@router.get(
|
|
"/api/v1/router/update/{update_slug}",
|
|
response_class=PlainTextResponse,
|
|
response_model=None,
|
|
operation_id="updateHostnameFromRouterGet",
|
|
tags=["router"],
|
|
)
|
|
async def update_hostname_get(
|
|
update_slug: UpdateSlugPath,
|
|
request: Request,
|
|
store: StoreDep,
|
|
service: ServiceDep,
|
|
myip: str | None = Query(default=None),
|
|
ipaddr: str | None = Query(default=None),
|
|
myipv6: str | None = Query(default=None),
|
|
ip6addr: str | None = Query(default=None),
|
|
) -> str | PlainTextResponse:
|
|
return await _update_hostname(update_slug, request, store, service, myip, ipaddr, myipv6, ip6addr)
|
|
|
|
|
|
@router.post(
|
|
"/api/v1/router/update/{update_slug}",
|
|
response_class=PlainTextResponse,
|
|
response_model=None,
|
|
operation_id="updateHostnameFromRouterPost",
|
|
tags=["router"],
|
|
)
|
|
async def update_hostname_post(
|
|
update_slug: UpdateSlugPath,
|
|
request: Request,
|
|
store: StoreDep,
|
|
service: ServiceDep,
|
|
myip: str | None = Query(default=None),
|
|
ipaddr: str | None = Query(default=None),
|
|
myipv6: str | None = Query(default=None),
|
|
ip6addr: str | None = Query(default=None),
|
|
) -> str | PlainTextResponse:
|
|
return await _update_hostname(update_slug, request, store, service, myip, ipaddr, myipv6, ip6addr)
|
|
|
|
|
|
async def _update_hostname(
|
|
update_slug: str,
|
|
request: Request,
|
|
store: StoreDep,
|
|
service: ServiceDep,
|
|
myip: str | None,
|
|
ipaddr: str | None,
|
|
myipv6: str | None,
|
|
ip6addr: str | None,
|
|
) -> str | PlainTextResponse:
|
|
myip, ipaddr, myipv6, ip6addr = await _ip_params_from_request(request, myip, ipaddr, myipv6, ip6addr)
|
|
account = await run_in_threadpool(store.get_account_by_slug, update_slug)
|
|
if not account or account["disabled"] or not account["accepted_at"]:
|
|
raise HTTPException(status_code=404, detail="not found")
|
|
if not service.hostname_is_managed(str(account["hostname"])):
|
|
raise HTTPException(status_code=404, detail="not found")
|
|
return await run_in_threadpool(apply_update_response, service, request, str(account["hostname"]), myip, ipaddr, myipv6, ip6addr)
|
|
|
|
|
|
async def _ip_params_from_request(
|
|
request: Request,
|
|
myip: str | None,
|
|
ipaddr: str | None,
|
|
myipv6: str | None,
|
|
ip6addr: str | None,
|
|
) -> tuple[str | None, str | None, str | None, str | None]:
|
|
if request.method != "POST":
|
|
return myip, ipaddr, myipv6, ip6addr
|
|
body_values = await _post_values(request)
|
|
return (
|
|
myip or body_values.get("myip"),
|
|
ipaddr or body_values.get("ipaddr"),
|
|
myipv6 or body_values.get("myipv6"),
|
|
ip6addr or body_values.get("ip6addr"),
|
|
)
|
|
|
|
|
|
async def _post_values(request: Request) -> dict[str, str]:
|
|
content_type = request.headers.get("content-type", "").split(";", 1)[0].strip().lower()
|
|
body = await request.body()
|
|
if not body:
|
|
return {}
|
|
if content_type == "application/json":
|
|
try:
|
|
data = json.loads(body)
|
|
except json.JSONDecodeError:
|
|
return {}
|
|
if not isinstance(data, dict):
|
|
return {}
|
|
return {str(key): str(value) for key, value in data.items() if isinstance(value, str)}
|
|
if content_type == "application/x-www-form-urlencoded":
|
|
try:
|
|
form_body = body.decode("utf-8", errors="strict")
|
|
except UnicodeDecodeError:
|
|
return {}
|
|
try:
|
|
parsed = parse_qs(form_body, keep_blank_values=True, max_num_fields=8)
|
|
except ValueError:
|
|
return {}
|
|
return {key: values[0] for key, values in parsed.items() if values}
|
|
return {}
|