Simplify pending setup secrets

This commit is contained in:
StasonJatham committed 2026-05-21 18:30:15 +02:00
1 parent 5fee087b60
commit 8f07bbde84
6 files changed
+29 -23

No files matched your search

+5
View File
@@ -15,6 +15,7 @@ from .settings import DdnsSettings
DASHBOARD_UNLOCK_COOKIE = "ddns_dashboard_unlock"
DASHBOARD_UNLOCK_DEFAULT_SECONDS = 24 * 60 * 60
DASHBOARD_UNLOCK_STAY_SIGNED_IN_SECONDS = 30 * 24 * 60 * 60
BEARER_SECRET_PREFIX = "karl_"
PRIVATE_PROXY_NETWORKS = tuple(
ip_network(network)
for network in (
@@ -62,6 +63,10 @@ def hash_lookup_token(token: str) -> str:
return hashlib.sha256(token.encode("utf-8")).hexdigest()
def new_bearer_secret() -> str:
return f"{BEARER_SECRET_PREFIX}{secrets.token_urlsafe(32)}"
def sign_dashboard_unlock_cookie(
hostname: str,
management_slug: str,
+5 -6
View File
@@ -1,17 +1,16 @@
from __future__ import annotations
import secrets
from datetime import UTC, datetime
from ..security import hash_lookup_token, hash_secret, verify_secret
from ..security import hash_lookup_token, hash_secret, new_bearer_secret, verify_secret
from .types import AccountRow
class AccountsMixin:
def create_account(self, hostname: str, username: str | None = None, *, accepted: bool = True) -> AccountRow:
now = datetime.now(UTC).isoformat()
update_slug = secrets.token_urlsafe(32)
management_slug = secrets.token_urlsafe(32)
update_slug = new_bearer_secret()
management_slug = new_bearer_secret()
management_hash = hash_lookup_token(management_slug)
accepted_at = now if accepted else None
with self.connect() as conn:
@@ -103,13 +102,13 @@ class AccountsMixin:
conn.execute("DELETE FROM records WHERE hostname = ?", (hostname,))
def rotate_update_slug(self, hostname: str) -> AccountRow | None:
update_slug = secrets.token_urlsafe(32)
update_slug = new_bearer_secret()
with self.connect() as conn:
conn.execute("UPDATE accounts SET update_slug = ? WHERE hostname = ?", (update_slug, hostname))
return self.get_account_by_hostname(hostname)
def rotate_management_slug(self, hostname: str) -> AccountRow | None:
management_slug = secrets.token_urlsafe(32)
management_slug = new_bearer_secret()
management_hash = hash_lookup_token(management_slug)
with self.connect() as conn:
conn.execute("UPDATE accounts SET management_hash = ? WHERE hostname = ?", (management_hash, hostname))
+3 -4
View File
@@ -1,10 +1,9 @@
from __future__ import annotations
import secrets
import sqlite3
from pathlib import Path
from ..security import hash_lookup_token
from ..security import hash_lookup_token, new_bearer_secret
BUSY_TIMEOUT_MS = 10_000
CACHE_SIZE_KIB = 20_000
@@ -160,11 +159,11 @@ def initialize_schema(conn: sqlite3.Connection) -> None:
if schema_version < 2:
conn.execute("UPDATE accounts SET accepted_at = created_at WHERE accepted_at IS NULL")
for row in conn.execute("SELECT hostname FROM accounts WHERE update_slug IS NULL OR update_slug = ''"):
conn.execute("UPDATE accounts SET update_slug = ? WHERE hostname = ?", (secrets.token_urlsafe(32), row[0]))
conn.execute("UPDATE accounts SET update_slug = ? WHERE hostname = ?", (new_bearer_secret(), row[0]))
for row in conn.execute("SELECT hostname FROM accounts WHERE management_hash IS NULL OR management_hash = ''"):
conn.execute(
"UPDATE accounts SET management_hash = ? WHERE hostname = ?",
(hash_lookup_token(secrets.token_urlsafe(32)), row[0]),
(hash_lookup_token(new_bearer_secret()), row[0]),
)
_migrate_accounts_schema(conn)
conn.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_accounts_update_slug ON accounts(update_slug)")
+3 -6
View File
@@ -162,7 +162,6 @@ def _render_pending_management_page(
"management_slug": management_slug,
}
update_url = service.fritz_update_url(account_for_url)
registration_url = service.registration_url({"management_slug": management_slug})
dashboard_url = service.dashboard_url({"management_slug": management_slug})
hostname = escape(raw_hostname)
message_html = _management_message(message)
@@ -188,15 +187,13 @@ def _render_pending_management_page(
<div>
<p class="eyebrow">Save</p>
<h2>Your values.</h2>
<p class="section-copy">This page is private. You can revisit it while the setup is pending. After registration, the dashboard hides secrets by default.</p>
<p class="section-copy"><strong>Public:</strong> hostname and CNAME target. <strong>Secret:</strong> update URL, register URL, and private dashboard.</p>
<p class="section-copy">Save the update URL and dashboard link before you register. After registration, the dashboard hides secrets by default.</p>
<p class="section-copy"><strong>Public:</strong> Domainnamen. <strong>Secret:</strong> Update-URL and private dashboard.</p>
</div>
<div class="d-grid gap-2">
{_copy_row("Update-URL:", update_url, "Secret", mask_secret=False)}
{_copy_row("Domainnamen:", raw_hostname, "Public")}
{_copy_row("Register URL:", registration_url, "Secret", mask_secret=False)}
{_copy_row("Private dashboard:", dashboard_url, "Secret", mask_secret=False)}
{_copy_row("CNAME target:", raw_hostname, "Public")}
</div>
<form method="post" action="/m/{attr(management_slug)}/activate">
<input type="hidden" name="csrf" value="{attr(csrf_token)}">
@@ -219,7 +216,7 @@ def _render_pending_management_page(
</div>
<div>
<h2>Use your own DNS name.</h2>
<p class="section-copy">Create a CNAME at your DNS provider that points to <strong>{hostname}</strong>. KarlDNS only updates this generated hostname.</p>
<p class="section-copy">Create a CNAME at your DNS provider that points to <strong>{hostname}</strong>. This is the same value as Domainnamen.</p>
</div>
</div>
</section>