diff --git a/router_dyndns/ddns.py b/router_dyndns/ddns.py index 4727b4b..d43ebb3 100644 --- a/router_dyndns/ddns.py +++ b/router_dyndns/ddns.py @@ -725,9 +725,10 @@ def _custom_domain_flow(service: DdnsService, challenge: dict[str, str | None] | 2

Add the TXT record

{status}

- {_copy_row("TXT name", verification_name)} - {_copy_row("TXT value", token)} - {_copy_row("Private claim link", claim_url)} +
TXT name and value are public DNS records. The private claim link is secret.
+ {_copy_row("TXT name", verification_name, "Public")} + {_copy_row("TXT value", token, "Public")} + {_copy_row("Private claim link", claim_url, "Secret")}
@@ -813,13 +814,14 @@ def _credentials_panel(service: DdnsService, created_account: dict[str, str] | N

Credentials generated

{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_row("Update-URL:", update_url)} - {_copy_row("Domainnamen:", hostname)} - {_copy_row("CNAME target:", hostname)} - {_copy_row("Benutzername:", username)} - {_copy_row("Kennwort:", password)} - {_copy_row("Private status page:", management_url)} + {_copy_row("Update-URL:", update_url, "Secret")} + {_copy_row("Domainnamen:", hostname, "Public")} + {_copy_row("CNAME target:", hostname, "Public")} + {_copy_row("Benutzername:", username, "Public")} + {_copy_row("Kennwort:", password, "Secret")} + {_copy_row("Private status page:", management_url, "Secret")}
@@ -841,18 +843,32 @@ def _admin_secret_panel(title: str, rows: list[str]) -> str: """ -def _copy_row(label: str, value: str) -> str: +def _copy_row(label: str, value: str, sensitivity: str | None = None) -> str: safe_label = html.escape(label) safe_value = html.escape(value) + badge = "" + if sensitivity: + safe_sensitivity = html.escape(sensitivity) + badge_class = "text-bg-warning" if sensitivity.lower() == "secret" else "text-bg-secondary" + badge = f'{safe_sensitivity}' return f"""
- {safe_label} + {safe_label}{badge} {safe_value}
""" +def _secret_notice() -> str: + return """ +
+ Public: Domainnamen and CNAME target can be shared in DNS. + Secret: Update-URL, Kennwort, private status page, and domain claim links control the hostname. +
+ """ + + def _message_band(message: str | None) -> str: if not message: return "" @@ -900,13 +916,14 @@ def _render_management_page(service: DdnsService, settings: DdnsSettings, manage

Router settings

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_row("Update-URL:", update_url)} - {_copy_row("Domainnamen:", hostname)} - {_copy_row("CNAME target:", hostname)} - {_copy_row("Benutzername:", username)} - {_copy_row("Kennwort:", "unchanged; only shown when generated")} + {_copy_row("Update-URL:", update_url, "Secret")} + {_copy_row("Domainnamen:", hostname, "Public")} + {_copy_row("CNAME target:", hostname, "Public")} + {_copy_row("Benutzername:", username, "Public")} + {_copy_row("Kennwort:", "unchanged; only shown when generated", "Secret")}
@@ -1272,6 +1289,7 @@ def _page(title: str, body: str) -> str: .eyebrow {{ color: var(--bs-primary); font-size: 13px; font-weight: 600; letter-spacing: 0; }} .lead {{ max-width: 660px; color: var(--rp-muted); font-size: 19px; line-height: 1.47; letter-spacing: -0.01em; }} .section-dark .lead, .section-dark .section-copy {{ color: #cccccc; }} + .section-dark .text-secondary {{ color: #cccccc !important; }} .section-copy, .intro {{ margin-top: 12px; max-width: 560px; color: var(--rp-muted); font-size: 15px; line-height: 1.5; }} .card {{ background: var(--rp-surface); border-color: var(--rp-line); border-radius: .75rem; }} .section-dark .card {{ background: var(--rp-dark-card); border-color: rgba(255, 255, 255, 0.14); color: #f5f5f7; }} @@ -1285,6 +1303,7 @@ def _page(title: str, body: str) -> str: .btn:focus-visible, a:focus-visible, input:focus-visible {{ outline: 2px solid var(--bs-primary); outline-offset: 3px; }} .btn-light {{ --bs-btn-bg: var(--rp-surface-alt); --bs-btn-border-color: var(--rp-line); --bs-btn-color: var(--rp-ink); --bs-btn-hover-bg: var(--bs-tertiary-bg); --bs-btn-hover-border-color: var(--rp-line); --bs-btn-hover-color: var(--rp-ink); }} .copy-field .input-group-text {{ min-width: 150px; color: var(--rp-muted); font-size: 13px; }} + .copy-label {{ display: inline-flex; align-items: center; justify-content: space-between; gap: 8px; }} .copy-field code {{ min-height: 44px; margin: 0; color: var(--rp-ink); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; line-height: 1.6; background: var(--bs-body-bg); }} .section-dark .copy-field code {{ color: #f5f5f7; background: #1d1d1f; }} .admin-table-scroll {{ max-height: 560px; overflow: auto; }} diff --git a/tests/test_ddns.py b/tests/test_ddns.py index 9bff7f6..cd76457 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 "Public:" in response.text + assert "Secret:" in response.text assert ".ddns.example.net" in response.text assert "/u/" in response.text assert "/m/" in response.text @@ -331,6 +333,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 response.text.count("Secret") >= 3 + assert response.text.count("Public") >= 3 assert ".ddns.example.net" in response.text @@ -345,6 +349,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 "Public:" in management.text + assert "Secret:" in management.text assert "Router updates" in management.text deleted = client.post(f"/m/{account['management_slug']}/delete")