diff --git a/router_dyndns/ddns.py b/router_dyndns/ddns.py index d43ebb3..91a409f 100644 --- a/router_dyndns/ddns.py +++ b/router_dyndns/ddns.py @@ -719,6 +719,7 @@ def _custom_domain_flow(service: DdnsService, challenge: dict[str, str | None] | verification_name = service.verification_name(str(challenge["domain"])) claim_url = service.domain_claim_url(challenge) status = html.escape(message or "Add this TXT record at your DNS provider, then check when it has propagated.") + txt_bundle = f"TXT name: {verification_name}\nTXT value: {token}" challenge_html = f"""
@@ -726,6 +727,7 @@ def _custom_domain_flow(service: DdnsService, challenge: dict[str, str | None] |

Add the TXT record

{status}

TXT name and value are public DNS records. The private claim link is secret.
+ {_copy_button("Copy TXT record", txt_bundle)} {_copy_row("TXT name", verification_name, "Public")} {_copy_row("TXT value", token, "Public")} {_copy_row("Private claim link", claim_url, "Secret")} @@ -808,6 +810,8 @@ def _credentials_panel(service: DdnsService, created_account: dict[str, str] | N password = created_account["password"] update_url = service.fritz_update_url(created_account) management_url = service.magic_management_url(created_account) + cname_record = f"your-subdomain.example.com. CNAME {hostname}." + fritzbox_fields = _fritzbox_fields(update_url, hostname, username, password, management_url) return f"""
@@ -815,6 +819,10 @@ def _credentials_panel(service: DdnsService, created_account: dict[str, str] | N

{html.escape(hostname)}

Save this now. The password and private status page are only shown on this screen. To use your own DNS name, create a CNAME from that name to this hostname.

{_secret_notice()} +
+ {_copy_button("Copy FRITZ!Box fields", fritzbox_fields)} + {_copy_button("Copy CNAME record", cname_record)} +
{_copy_row("Update-URL:", update_url, "Secret")} {_copy_row("Domainnamen:", hostname, "Public")} @@ -860,6 +868,31 @@ def _copy_row(label: str, value: str, sensitivity: str | None = None) -> str: """ +def _copy_button(label: str, value: str) -> str: + safe_label = html.escape(label) + safe_value = html.escape(value) + return f'' + + +def _fritzbox_fields( + update_url: str, + hostname: str, + username: str, + password: str, + management_url: str | None = None, +) -> str: + lines = [ + f"Update-URL: {update_url}", + f"Domainnamen: {hostname}", + f"CNAME target: {hostname}", + f"Benutzername: {username}", + f"Kennwort: {password}", + ] + if management_url: + lines.append(f"Private status page: {management_url}") + return "\n".join(lines) + + def _secret_notice() -> str: return """
@@ -893,6 +926,8 @@ def _render_management_page(service: DdnsService, settings: DdnsSettings, manage ipv4 = html.escape(str(account.get("ipv4") or "-")) ipv6 = html.escape(str(account.get("ipv6") or "-")) updated = html.escape(str(account.get("updated_at") or "Never")) + fritzbox_fields = _fritzbox_fields(update_url, hostname, username, "unchanged; only shown when generated") + cname_record = f"your-subdomain.example.com. CNAME {hostname}." history_rows = "\n".join(_user_event_row(event) for event in service.store.list_update_events_for_hostname(str(account["hostname"]), 20)) or """ No router updates yet. """ @@ -917,6 +952,10 @@ def _render_management_page(service: DdnsService, settings: DdnsSettings, manage

FRITZ!Box fields

Use these values in the FRITZ!Box custom DynDNS provider fields. If you use your own DNS name, point it at this hostname with a CNAME.

{_secret_notice()} +
+ {_copy_button("Copy FRITZ!Box fields", fritzbox_fields)} + {_copy_button("Copy CNAME record", cname_record)} +
{_copy_row("Update-URL:", update_url, "Secret")} diff --git a/tests/test_ddns.py b/tests/test_ddns.py index cd76457..7478e1c 100644 --- a/tests/test_ddns.py +++ b/tests/test_ddns.py @@ -309,6 +309,8 @@ def test_self_service_managed_hostname_generates_random_account(tmp_path: Path) assert response.status_code == 200 assert "Update-URL:" in response.text assert "CNAME target:" in response.text + assert "Copy FRITZ!Box fields" in response.text + assert "Copy CNAME record" in response.text assert "Public:" in response.text assert "Secret:" in response.text assert ".ddns.example.net" in response.text @@ -333,6 +335,8 @@ def test_magic_hostname_without_login_generates_update_and_management_links(tmp_ assert "Update-URL:" in response.text assert "CNAME target:" in response.text assert "Private status page:" in response.text + assert "Copy FRITZ!Box fields" in response.text + assert "Copy CNAME record" in response.text assert response.text.count("Secret") >= 3 assert response.text.count("Public") >= 3 assert ".ddns.example.net" in response.text @@ -349,6 +353,8 @@ def test_magic_management_link_can_delete_hostname(tmp_path: Path) -> None: assert management.status_code == 200 assert "home.ddns.example.net" in management.text assert "CNAME target:" in management.text + assert "Copy FRITZ!Box fields" in management.text + assert "Copy CNAME record" in management.text assert "Public:" in management.text assert "Secret:" in management.text assert "Router updates" in management.text