Archived
Polish KarlDNS production UI and docs
This commit is contained in:
1 parent
ca92dc211a
commit
5beee0e5c6
61 files changed
+1566
-523
No files matched your search
+97
-33
@@ -28,12 +28,12 @@ def test_self_service_managed_hostname_generates_random_account(tmp_path: Path)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Update-URL:" in response.text
|
||||
assert "One-box update URL:" in response.text
|
||||
assert "Full update URL:" in response.text
|
||||
assert "CNAME target:" in response.text
|
||||
assert "Benutzername (optional):" in response.text
|
||||
assert "Kennwort (optional):" in response.text
|
||||
assert "Copy complete FRITZ!Box fields" in response.text
|
||||
assert "Copy CNAME record" in response.text
|
||||
assert "Copy router fields" in response.text
|
||||
assert "Copy CNAME" in response.text
|
||||
assert "Manual updates" in response.text
|
||||
assert "curl -sS" in response.text
|
||||
assert "203.0.113.10" in response.text
|
||||
@@ -61,15 +61,15 @@ def test_magic_hostname_without_login_generates_update_and_management_links(tmp_
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Update-URL:" in response.text
|
||||
assert "One-box update URL:" in response.text
|
||||
assert "Full update URL:" in response.text
|
||||
assert "CNAME target:" in response.text
|
||||
assert "Benutzername (optional):" in response.text
|
||||
assert "Kennwort (optional):" in response.text
|
||||
assert "Private status page:" in response.text
|
||||
assert "Copy complete FRITZ!Box fields" in response.text
|
||||
assert "Copy CNAME record" in response.text
|
||||
assert "FRITZ!Box replaces" in response.text
|
||||
assert "Copy curl examples" in response.text
|
||||
assert "Private dashboard:" in response.text
|
||||
assert "Copy router fields" in response.text
|
||||
assert "Copy CNAME" in response.text
|
||||
assert "FRITZ!Box fills in" in response.text
|
||||
assert "Copy curl" in response.text
|
||||
assert response.text.count("Secret") >= 3
|
||||
assert response.text.count("Public") >= 3
|
||||
assert ".ddns.example.net" in response.text
|
||||
@@ -94,12 +94,13 @@ def test_magic_management_link_can_delete_hostname(tmp_path: Path) -> None:
|
||||
|
||||
management = client.get(f"/m/{account['management_slug']}")
|
||||
assert management.status_code == 200
|
||||
assert '<link href="/admin.css?v=2" rel="stylesheet">' in management.text
|
||||
assert "home.ddns.example.net" in management.text
|
||||
assert "CNAME target:" in management.text
|
||||
assert "Benutzername (optional):" in management.text
|
||||
assert "Kennwort (optional):" in management.text
|
||||
assert "Copy complete FRITZ!Box fields" in management.text
|
||||
assert "Copy CNAME record" in management.text
|
||||
assert "Copy router fields" in management.text
|
||||
assert "Copy CNAME" in management.text
|
||||
assert "Manual updates" in management.text
|
||||
assert "curl -sS" in management.text
|
||||
assert "Public:" in management.text
|
||||
@@ -174,7 +175,7 @@ def test_management_dashboard_password_locks_dashboard_only(tmp_path: Path) -> N
|
||||
data={"csrf": _csrf(locked.text), "dashboard_password": "wrong"},
|
||||
)
|
||||
assert bad_unlock.status_code == 401
|
||||
assert "Incorrect dashboard password." in bad_unlock.text
|
||||
assert "Incorrect password." in bad_unlock.text
|
||||
assert "Update-URL:" not in bad_unlock.text
|
||||
|
||||
good_unlock = locked_client.post(
|
||||
@@ -312,7 +313,7 @@ def test_existing_dashboard_password_requires_current_password_to_change_or_remo
|
||||
|
||||
assert change.status_code == 400
|
||||
assert remove.status_code == 400
|
||||
assert "Current dashboard password is incorrect." in change.text
|
||||
assert "Current password is incorrect." in change.text
|
||||
assert app.state.store.verify_dashboard_password("home.ddns.example.net", "correct horse")
|
||||
|
||||
|
||||
@@ -331,21 +332,21 @@ def test_management_dashboard_rotates_update_url_and_router_credentials(tmp_path
|
||||
|
||||
dashboard = client.get(f"/m/{account['management_slug']}")
|
||||
assert dashboard.status_code == 200
|
||||
assert "Rotate Update-URL" in dashboard.text
|
||||
assert "Rotate router credentials" in dashboard.text
|
||||
assert "New update URL" in dashboard.text
|
||||
assert "New password" in dashboard.text
|
||||
assert "data-secret-toggle" in dashboard.text
|
||||
assert 'type="password"' in dashboard.text
|
||||
|
||||
rotated_update = client.post(f"/m/{account['management_slug']}/rotate", data={"csrf": _csrf(dashboard.text), "action": "update"})
|
||||
assert rotated_update.status_code == 200
|
||||
assert "Update-URL rotated." in rotated_update.text
|
||||
assert "Update URL changed. Update your router now." in rotated_update.text
|
||||
assert "data-secret-input" in rotated_update.text
|
||||
assert client.get(f"/u/{account['update_slug']}", params={"myip": "203.0.113.9"}).status_code == 404
|
||||
|
||||
rotated_password = client.post(f"/m/{account['management_slug']}/rotate", data={"csrf": _csrf(rotated_update.text), "action": "password"})
|
||||
assert rotated_password.status_code == 200
|
||||
assert "Router credentials rotated" in rotated_password.text
|
||||
assert "One-box update URL:" in rotated_password.text
|
||||
assert "Router password changed" in rotated_password.text
|
||||
assert "Full update URL:" in rotated_password.text
|
||||
assert "Kennwort (optional):" in rotated_password.text
|
||||
assert "data-secret-toggle" in rotated_password.text
|
||||
assert client.get(
|
||||
@@ -434,7 +435,7 @@ def test_management_dashboard_custom_domain_claim_flow(tmp_path: Path, monkeypat
|
||||
dashboard = client.get(f"/m/{account['management_slug']}")
|
||||
challenge = client.post(f"/m/{account['management_slug']}/domains/request", data={"csrf": _csrf(dashboard.text), "domain": "example.net"})
|
||||
assert challenge.status_code == 200
|
||||
assert "Domain claim created" in challenge.text
|
||||
assert "Claim created" in challenge.text
|
||||
assert "TXT name" in challenge.text
|
||||
assert "TXT value" in challenge.text
|
||||
assert "Claim secret" in challenge.text
|
||||
@@ -449,17 +450,17 @@ def test_management_dashboard_custom_domain_claim_flow(tmp_path: Path, monkeypat
|
||||
)
|
||||
assert verified.status_code == 200
|
||||
assert "Domain verified" in verified.text
|
||||
assert "Create custom hostname" in verified.text
|
||||
assert "Create hostname" in verified.text
|
||||
|
||||
created = client.post(
|
||||
f"/m/{account['management_slug']}/domains/hostnames",
|
||||
data={"csrf": _csrf(verified.text), "hostname": "home.example.net", "claim_secret": claim_secret},
|
||||
)
|
||||
assert created.status_code == 200
|
||||
assert "Custom hostname created" in created.text
|
||||
assert "Hostname created" in created.text
|
||||
assert "home.example.net" in created.text
|
||||
assert "Update-URL:" in created.text
|
||||
assert "Private status page:" in created.text
|
||||
assert "Private dashboard:" in created.text
|
||||
|
||||
|
||||
def test_dashboard_domain_claims_are_bound_to_creating_dashboard(tmp_path: Path, monkeypatch) -> None:
|
||||
@@ -557,7 +558,7 @@ def test_custom_domain_flow_works_without_login(tmp_path: Path, monkeypatch) ->
|
||||
|
||||
verify = client.post("/verify-domain", data={"domain": "example.net", "claim_secret": claim_secret})
|
||||
assert verify.status_code == 200
|
||||
assert "Create credentials" in verify.text
|
||||
assert "Create hostname" in verify.text
|
||||
|
||||
created = client.post(
|
||||
"/accounts",
|
||||
@@ -584,7 +585,7 @@ def test_public_home_hides_admin_link_and_uses_automatic_router_login(tmp_path:
|
||||
assert 'href="/admin"' not in response.text
|
||||
assert "Router login name" not in response.text
|
||||
assert 'name="username"' not in response.text
|
||||
assert "generates the hostname, secret update URL, and optional router compatibility credentials automatically" in response.text
|
||||
assert "You get a hostname, a private update URL, and router fields if your router needs them." in response.text
|
||||
assert "Linux cron updater" in response.text
|
||||
assert "curl -fsSL https://karldns.de/install.sh | sudo sh" in response.text
|
||||
assert "curl -fsSL https://karldns.de/uninstall.sh | sudo sh" in response.text
|
||||
@@ -617,27 +618,56 @@ def test_application_pages_use_stricter_script_csp(tmp_path: Path) -> None:
|
||||
response = client.get("/")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert '<script src="/app.js" defer></script>' in response.text
|
||||
assert '<link href="/app.css" rel="stylesheet">' in response.text
|
||||
assert '<script src="/theme.js?v=2"></script>' in response.text
|
||||
assert '<script src="/clipboard.js?v=2" defer></script>' in response.text
|
||||
assert '<script src="/admin-tables.js?v=2" defer></script>' not in response.text
|
||||
assert '<link href="/bootstrap-subset.css?v=3" rel="stylesheet">' in response.text
|
||||
assert '<link href="/app.css?v=2" rel="stylesheet">' in response.text
|
||||
assert '<link href="/admin.css" rel="stylesheet">' not in response.text
|
||||
assert "cdn.jsdelivr.net/npm/bootstrap" not in response.text
|
||||
assert "<style>" not in response.text
|
||||
assert "script-src 'self'" in response.headers["content-security-policy"]
|
||||
assert "script-src 'self' 'unsafe-inline'" not in response.headers["content-security-policy"]
|
||||
assert "style-src 'self' https://cdn.jsdelivr.net" in response.headers["content-security-policy"]
|
||||
assert "script-src 'self' 'unsafe-inline'" in response.headers["content-security-policy"]
|
||||
assert "style-src 'self'" in response.headers["content-security-policy"]
|
||||
assert "style-src 'self' 'unsafe-inline'" not in response.headers["content-security-policy"]
|
||||
assert client.get("/app.js").status_code == 200
|
||||
bootstrap_css = client.get("/bootstrap-subset.css")
|
||||
assert bootstrap_css.status_code == 200
|
||||
assert "text/css" in bootstrap_css.headers["content-type"]
|
||||
assert bootstrap_css.headers["cache-control"] == "public, max-age=31536000, immutable"
|
||||
assert client.get("/theme.js").status_code == 200
|
||||
assert client.get("/clipboard.js").status_code == 200
|
||||
assert client.get("/admin-tables.js").status_code == 200
|
||||
app_css = client.get("/app.css")
|
||||
assert app_css.status_code == 200
|
||||
assert "text/css" in app_css.headers["content-type"]
|
||||
assert app_css.headers["cache-control"] == "public, max-age=3600"
|
||||
assert app_css.headers["cache-control"] == "public, max-age=31536000, immutable"
|
||||
admin_css = client.get("/admin.css")
|
||||
assert admin_css.status_code == 200
|
||||
assert "text/css" in admin_css.headers["content-type"]
|
||||
assert admin_css.headers["cache-control"] == "public, max-age=31536000, immutable"
|
||||
|
||||
|
||||
def test_footer_links_and_privacy_page_are_available(tmp_path: Path) -> None:
|
||||
app = make_app(DdnsSettings(database_path=tmp_path / "ddns.sqlite3", admin_password="admin"))
|
||||
app = make_app(
|
||||
DdnsSettings(
|
||||
database_path=tmp_path / "ddns.sqlite3",
|
||||
admin_password="admin",
|
||||
public_base_url="https://karldns.de",
|
||||
)
|
||||
)
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert '<link rel="canonical" href="https://karldns.de/">' in response.text
|
||||
assert 'property="og:title" content="KarlDNS"' in response.text
|
||||
assert 'property="og:url" content="https://karldns.de/"' in response.text
|
||||
assert 'property="og:image" content="https://karldns.de/social-preview.png"' in response.text
|
||||
assert 'property="og:image:width" content="1200"' in response.text
|
||||
assert 'property="og:image:height" content="630"' in response.text
|
||||
assert 'name="twitter:card" content="summary_large_image"' in response.text
|
||||
assert 'application/ld+json' in response.text
|
||||
assert "NetworkingApplication" in response.text
|
||||
assert "Made by Karl" in response.text
|
||||
assert 'href="https://github.com/StasonJatham/karldns.de"' in response.text
|
||||
assert 'href="https://karl.fail"' in response.text
|
||||
@@ -646,12 +676,41 @@ def test_footer_links_and_privacy_page_are_available(tmp_path: Path) -> None:
|
||||
|
||||
privacy = client.get("/privacy")
|
||||
assert privacy.status_code == 200
|
||||
assert '<link rel="canonical" href="https://karldns.de/privacy">' in privacy.text
|
||||
assert "Privacy and legal information for KarlDNS" in privacy.text
|
||||
assert "WebPage" in privacy.text
|
||||
assert "Legal and privacy" in privacy.text
|
||||
assert "Data processed" in privacy.text
|
||||
assert "hashed router passwords" in privacy.text
|
||||
assert "protected router passwords" in privacy.text
|
||||
assert "Made by Karl" in privacy.text
|
||||
|
||||
|
||||
def test_public_robots_and_sitemap_are_available(tmp_path: Path) -> None:
|
||||
app = make_app(
|
||||
DdnsSettings(
|
||||
database_path=tmp_path / "ddns.sqlite3",
|
||||
admin_password="admin",
|
||||
admin_path="/ops",
|
||||
public_base_url="https://karldns.de",
|
||||
)
|
||||
)
|
||||
client = TestClient(app)
|
||||
|
||||
robots = client.get("/robots.txt")
|
||||
sitemap = client.get("/sitemap.xml")
|
||||
|
||||
assert robots.status_code == 200
|
||||
assert "User-agent: *" in robots.text
|
||||
assert "Disallow: /ops/" in robots.text
|
||||
assert "Disallow: /m/" in robots.text
|
||||
assert "Disallow: /api/" in robots.text
|
||||
assert "Sitemap: https://karldns.de/sitemap.xml" in robots.text
|
||||
assert sitemap.status_code == 200
|
||||
assert "application/xml" in sitemap.headers["content-type"]
|
||||
assert "<loc>https://karldns.de/</loc>" in sitemap.text
|
||||
assert "<loc>https://karldns.de/privacy</loc>" in sitemap.text
|
||||
|
||||
|
||||
def test_web_icon_assets_and_manifest_are_exposed(tmp_path: Path) -> None:
|
||||
app = make_app(DdnsSettings(database_path=tmp_path / "ddns.sqlite3", admin_password="admin"))
|
||||
client = TestClient(app)
|
||||
@@ -666,11 +725,16 @@ def test_web_icon_assets_and_manifest_are_exposed(tmp_path: Path) -> None:
|
||||
assert 'href="/site.webmanifest"' in response.text
|
||||
for path in [
|
||||
"/favicon.ico",
|
||||
"/bootstrap-subset.css",
|
||||
"/favicon-32x32.png",
|
||||
"/favicon-16x16.png",
|
||||
"/apple-touch-icon.png",
|
||||
"/logo-nav.webp",
|
||||
"/logo.webp",
|
||||
"/logo-192.png",
|
||||
"/logo-512.png",
|
||||
"/social-preview.png",
|
||||
"/social-preview.webp",
|
||||
]:
|
||||
asset = client.get(path)
|
||||
assert asset.status_code == 200
|
||||
|
||||
Reference in new issue
Block a user