Archived
Simplify DDNS flow and polish UI
This commit is contained in:
1 parent
5beee0e5c6
commit
5a90bd9d87
42 files changed
+587
-1418
No files matched your search
@@ -63,11 +63,8 @@ def test_api_docs_include_versioned_ddns_api(tmp_path: Path) -> None:
|
||||
assert "/events" not in schema["paths"]
|
||||
assert "/" not in schema["paths"]
|
||||
assert "/magic" not in schema["paths"]
|
||||
assert "/request-domain" not in schema["paths"]
|
||||
assert "/verify-domain" not in schema["paths"]
|
||||
assert "/accounts" not in schema["paths"]
|
||||
assert "/m/{management_slug}" not in schema["paths"]
|
||||
assert "/d/{claim_secret}" not in schema["paths"]
|
||||
assert any(tag["name"] == "hostnames" for tag in schema["tags"])
|
||||
assert all(tag["name"] != "admin" for tag in schema["tags"])
|
||||
management_tag = next(tag for tag in schema["tags"] if tag["name"] == "management")
|
||||
@@ -113,9 +110,6 @@ def test_public_redoc_only_exposes_api_and_router_endpoints(tmp_path: Path) -> N
|
||||
schema = client.get("/openapi.json").json()
|
||||
|
||||
expected_public_paths = {
|
||||
"/api/v1/domains/challenges",
|
||||
"/api/v1/domains/verify",
|
||||
"/api/v1/hostnames/custom",
|
||||
"/api/v1/hostnames/magic",
|
||||
"/api/v1/management/{management_slug}",
|
||||
"/api/v1/updates/{update_slug}",
|
||||
@@ -144,16 +138,8 @@ def test_openapi_marks_generated_update_links_as_secret(tmp_path: Path) -> None:
|
||||
("path", "payload"),
|
||||
[
|
||||
("/api/v1/hostnames/magic", {"username": "router", "unexpected": True}),
|
||||
("/api/v1/domains/challenges", {"domain": "example.net", "unexpected": True}),
|
||||
("/api/v1/domains/verify", {"domain": "example.net", "claim_secret": "secret", "unexpected": True}),
|
||||
(
|
||||
"/api/v1/hostnames/custom",
|
||||
{"hostname": "home.example.net", "claim_secret": "secret", "unexpected": True},
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def test_public_write_models_reject_extra_json_fields(tmp_path: Path, path: str, payload: dict[str, object]) -> None:
|
||||
app = make_app(
|
||||
DdnsSettings(
|
||||
@@ -235,50 +221,6 @@ def test_api_dns_publish_failure_keeps_record_unchanged(tmp_path: Path, monkeypa
|
||||
assert store.list_update_events()[0]["status"] == "dns_failed"
|
||||
|
||||
|
||||
def test_api_custom_hostname_requires_verified_domain(tmp_path: Path, monkeypatch) -> None:
|
||||
monkeypatch.setattr(ddns_service, "dns_txt_contains", lambda name, expected: True)
|
||||
app = make_app(DdnsSettings(database_path=tmp_path / "ddns.sqlite3", admin_password="admin"))
|
||||
client = TestClient(app)
|
||||
|
||||
challenge = client.post("/api/v1/domains/challenges", json={"domain": "example.net"})
|
||||
assert challenge.status_code == 201
|
||||
challenge_body = challenge.json()
|
||||
|
||||
verified = client.post(
|
||||
"/api/v1/domains/verify",
|
||||
json={"domain": "example.net", "claim_secret": challenge_body["claim_secret"]},
|
||||
)
|
||||
assert verified.status_code == 200
|
||||
assert verified.json()["verified"] is True
|
||||
|
||||
hostname = client.post(
|
||||
"/api/v1/hostnames/custom",
|
||||
json={
|
||||
"hostname": "home.example.net",
|
||||
"claim_secret": challenge_body["claim_secret"],
|
||||
"username": "router",
|
||||
},
|
||||
)
|
||||
|
||||
assert hostname.status_code == 201
|
||||
assert hostname.json()["hostname"] == "home.example.net"
|
||||
|
||||
|
||||
def test_domain_challenge_verification_requires_current_secret_and_owner(tmp_path: Path) -> None:
|
||||
store = DdnsStore(tmp_path / "ddns.sqlite3")
|
||||
first = store.create_domain_challenge("example.net", owner_hostname="owner.example.net")
|
||||
second = store.create_domain_challenge("example.net", owner_hostname="owner.example.net")
|
||||
|
||||
assert store.verify_domain_challenge("example.net", first["claim_secret"], owner_hostname="owner.example.net") is None
|
||||
assert store.verify_domain_challenge("example.net", second["claim_secret"], owner_hostname="other.example.net") is None
|
||||
assert store.get_domain_challenge("example.net", second["claim_secret"], owner_hostname="owner.example.net")["verified_at"] is None
|
||||
|
||||
verified = store.verify_domain_challenge("example.net", second["claim_secret"], owner_hostname="owner.example.net")
|
||||
|
||||
assert verified is not None
|
||||
assert verified["verified_at"]
|
||||
|
||||
|
||||
def test_api_creation_is_rate_limited(tmp_path: Path) -> None:
|
||||
app = make_app(
|
||||
DdnsSettings(
|
||||
@@ -309,47 +251,3 @@ def test_api_invalid_ip_returns_clear_400(tmp_path: Path) -> None:
|
||||
assert response.status_code == 400
|
||||
assert response.json()["detail"] == "myipv6 must be an IPv6 address"
|
||||
assert store.list_records() == []
|
||||
|
||||
|
||||
def test_verified_domain_challenge_cannot_be_overwritten(tmp_path: Path, monkeypatch) -> None:
|
||||
monkeypatch.setattr(ddns_service, "dns_txt_contains", lambda name, expected: True)
|
||||
app = make_app(DdnsSettings(database_path=tmp_path / "ddns.sqlite3", admin_password="admin"))
|
||||
client = TestClient(app)
|
||||
|
||||
challenge = client.post("/api/v1/domains/challenges", json={"domain": "example.net"}).json()
|
||||
verified = client.post(
|
||||
"/api/v1/domains/verify",
|
||||
json={"domain": "example.net", "claim_secret": challenge["claim_secret"]},
|
||||
)
|
||||
overwrite = client.post("/api/v1/domains/challenges", json={"domain": "example.net"})
|
||||
|
||||
assert verified.status_code == 200
|
||||
assert overwrite.status_code == 409
|
||||
|
||||
|
||||
def test_custom_hostname_must_be_inside_publishable_dns_zone(tmp_path: Path, monkeypatch) -> None:
|
||||
monkeypatch.setattr(ddns_service, "dns_txt_contains", lambda name, expected: True)
|
||||
app = make_app(
|
||||
DdnsSettings(
|
||||
database_path=tmp_path / "ddns.sqlite3",
|
||||
admin_password="admin",
|
||||
dns_provider="cloudflare",
|
||||
cloudflare_api_token="token",
|
||||
cloudflare_zone_id="zone",
|
||||
dns_zones={"ddns.example.net"},
|
||||
)
|
||||
)
|
||||
client = TestClient(app)
|
||||
|
||||
challenge = client.post("/api/v1/domains/challenges", json={"domain": "example.net"}).json()
|
||||
client.post(
|
||||
"/api/v1/domains/verify",
|
||||
json={"domain": "example.net", "claim_secret": challenge["claim_secret"]},
|
||||
)
|
||||
hostname = client.post(
|
||||
"/api/v1/hostnames/custom",
|
||||
json={"hostname": "home.example.net", "claim_secret": challenge["claim_secret"]},
|
||||
)
|
||||
|
||||
assert hostname.status_code == 403
|
||||
assert hostname.json()["detail"] == "hostname is outside configured DNS publishing zones"
|
||||
Reference in new issue
Block a user