Archived
Remove admin credential generation flow
This commit is contained in:
1 parent
033b8564dd
commit
59c7618d15
2 files changed
+9
-74
No files matched your search
+2
-67
@@ -334,34 +334,6 @@ def make_app(settings: DdnsSettings | None = None) -> FastAPI:
|
|||||||
store.list_cleanup_runs(),
|
store.list_cleanup_runs(),
|
||||||
store.list_update_events(25),
|
store.list_update_events(25),
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.post("/admin/accounts", include_in_schema=False)
|
|
||||||
async def admin_create_account(request: Request, _: None = Depends(authenticate_admin)) -> Response:
|
|
||||||
form = await _request_form(request, settings)
|
|
||||||
require_admin_csrf(settings, _first_form_value(form, "csrf"))
|
|
||||||
hostname = normalize_hostname(_first_form_value(form, "hostname"), settings)
|
|
||||||
username = _first_form_value(form, "username") or None
|
|
||||||
if not hostname:
|
|
||||||
raise HTTPException(status_code=400, detail="hostname is required")
|
|
||||||
if settings.allowed_hosts and hostname not in settings.allowed_hosts:
|
|
||||||
raise HTTPException(status_code=403, detail="hostname is not allowed")
|
|
||||||
try:
|
|
||||||
account = store.create_account(hostname, username)
|
|
||||||
except sqlite3.IntegrityError as exc:
|
|
||||||
raise HTTPException(status_code=409, detail="hostname already exists") from exc
|
|
||||||
return HTMLResponse(
|
|
||||||
_render_admin_page(
|
|
||||||
service,
|
|
||||||
settings,
|
|
||||||
store.list_accounts(),
|
|
||||||
store.list_domain_challenges(),
|
|
||||||
store.list_cleanup_runs(),
|
|
||||||
store.list_update_events(25),
|
|
||||||
account,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.post("/admin/accounts/delete", include_in_schema=False)
|
@app.post("/admin/accounts/delete", include_in_schema=False)
|
||||||
@@ -414,7 +386,6 @@ def make_app(settings: DdnsSettings | None = None) -> FastAPI:
|
|||||||
store.list_domain_challenges(),
|
store.list_domain_challenges(),
|
||||||
store.list_cleanup_runs(),
|
store.list_cleanup_runs(),
|
||||||
store.list_update_events(25),
|
store.list_update_events(25),
|
||||||
None,
|
|
||||||
notice,
|
notice,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -439,7 +410,6 @@ def make_app(settings: DdnsSettings | None = None) -> FastAPI:
|
|||||||
store.list_domain_challenges(),
|
store.list_domain_challenges(),
|
||||||
store.list_cleanup_runs(),
|
store.list_cleanup_runs(),
|
||||||
store.list_update_events(25),
|
store.list_update_events(25),
|
||||||
None,
|
|
||||||
notice,
|
notice,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -769,10 +739,6 @@ def _credentials_panel(service: DdnsService, created_account: dict[str, str] | N
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def _created_account_panel(service: DdnsService, created_account: dict[str, str] | None) -> str:
|
|
||||||
return _credentials_panel(service, created_account)
|
|
||||||
|
|
||||||
|
|
||||||
def _admin_secret_panel(title: str, rows: list[str]) -> str:
|
def _admin_secret_panel(title: str, rows: list[str]) -> str:
|
||||||
return f"""
|
return f"""
|
||||||
<section class="section success-section">
|
<section class="section success-section">
|
||||||
@@ -913,7 +879,6 @@ def _render_admin_page(
|
|||||||
domain_challenges: list[dict[str, str | None]],
|
domain_challenges: list[dict[str, str | None]],
|
||||||
cleanup_runs: list[dict[str, str | int]],
|
cleanup_runs: list[dict[str, str | int]],
|
||||||
update_events: list[dict[str, str | None]],
|
update_events: list[dict[str, str | None]],
|
||||||
created_account: dict[str, str] | None,
|
|
||||||
notice_html: str | None,
|
notice_html: str | None,
|
||||||
) -> str:
|
) -> str:
|
||||||
csrf = html.escape(admin_csrf_token(settings))
|
csrf = html.escape(admin_csrf_token(settings))
|
||||||
@@ -929,11 +894,6 @@ def _render_admin_page(
|
|||||||
event_rows = "\n".join(_event_row(event) for event in update_events) or """
|
event_rows = "\n".join(_event_row(event) for event in update_events) or """
|
||||||
<tr><td colspan="6" class="empty">No update events yet.</td></tr>
|
<tr><td colspan="6" class="empty">No update events yet.</td></tr>
|
||||||
"""
|
"""
|
||||||
suffix_help = (
|
|
||||||
f"Short names are expanded under {html.escape(settings.hostname_suffix)}."
|
|
||||||
if settings.hostname_suffix
|
|
||||||
else "Use a full hostname, for example home.example.net."
|
|
||||||
)
|
|
||||||
return _page(
|
return _page(
|
||||||
"DynDNS Admin",
|
"DynDNS Admin",
|
||||||
f"""
|
f"""
|
||||||
@@ -942,12 +902,11 @@ def _render_admin_page(
|
|||||||
<section class="section admin-heading">
|
<section class="section admin-heading">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<p class="eyebrow">DynDNS aktiv</p>
|
<p class="eyebrow">DynDNS aktiv</p>
|
||||||
<h1>Router credentials</h1>
|
<h1>Operator console</h1>
|
||||||
<p class="lead">Create, inspect, and retire router update credentials.</p>
|
<p class="lead">Inspect hostnames, verify domain claims, rotate links, run cleanup, and review router update events.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{_created_account_panel(service, created_account)}
|
|
||||||
{notice_html or ""}
|
{notice_html or ""}
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
@@ -966,30 +925,6 @@ def _render_admin_page(
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row g-4 align-items-start">
|
|
||||||
<div class="col-lg-7">
|
|
||||||
<p class="eyebrow">Generate credentials</p>
|
|
||||||
<h2>Router credentials</h2>
|
|
||||||
<p class="section-copy">Generated credentials produce the FRITZ!Box fields: Update-URL, Domainnamen, Benutzername, Kennwort.</p>
|
|
||||||
<p class="small text-secondary mb-0 mt-2">{suffix_help}</p>
|
|
||||||
</div>
|
|
||||||
<form method="post" action="/admin/accounts" class="col-lg-5 card card-body gap-3" aria-label="Create router credentials">
|
|
||||||
<input type="hidden" name="csrf" value="{csrf}">
|
|
||||||
<label>Domainnamen
|
|
||||||
<input class="form-control" name="hostname" placeholder="home.example.net" autocomplete="off" autocapitalize="none" spellcheck="false" required>
|
|
||||||
</label>
|
|
||||||
<label>Benutzername
|
|
||||||
<input class="form-control" name="username" placeholder="auto-generated" autocomplete="off">
|
|
||||||
<span class="form-text m-0">Leave empty to generate one from the hostname.</span>
|
|
||||||
</label>
|
|
||||||
<button type="submit" class="btn btn-primary rounded-pill">Generate credentials</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="d-flex align-items-md-center justify-content-between gap-4 flex-column flex-md-row">
|
<div class="d-flex align-items-md-center justify-content-between gap-4 flex-column flex-md-row">
|
||||||
|
|||||||
+7
-7
@@ -152,7 +152,7 @@ def test_generated_account_cannot_update_other_hostname(tmp_path: Path) -> None:
|
|||||||
assert response.status_code == 401
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
def test_admin_page_shows_account_form(tmp_path: Path) -> None:
|
def test_admin_page_shows_operator_views_without_create_form(tmp_path: Path) -> None:
|
||||||
database_path = tmp_path / "ddns.sqlite3"
|
database_path = tmp_path / "ddns.sqlite3"
|
||||||
store = DdnsStore(database_path)
|
store = DdnsStore(database_path)
|
||||||
store.create_domain_challenge("example.net")
|
store.create_domain_challenge("example.net")
|
||||||
@@ -163,11 +163,11 @@ def test_admin_page_shows_account_form(tmp_path: Path) -> None:
|
|||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.headers["cache-control"] == "no-store"
|
assert response.headers["cache-control"] == "no-store"
|
||||||
assert "Update-URL" in response.text
|
assert "Operator console" in response.text
|
||||||
assert "Domainnamen" in response.text
|
assert "Generate credentials" not in response.text
|
||||||
assert "Benutzername" in response.text
|
assert 'action="/admin/accounts"' not in response.text
|
||||||
assert "Kennwort" in response.text
|
|
||||||
assert "Scheduled cleanup" in response.text
|
assert "Scheduled cleanup" in response.text
|
||||||
|
assert "Active credentials" in response.text
|
||||||
assert "Domain claims" in response.text
|
assert "Domain claims" in response.text
|
||||||
assert "Recent events" in response.text
|
assert "Recent events" in response.text
|
||||||
assert "example.net" in response.text
|
assert "example.net" in response.text
|
||||||
@@ -178,9 +178,9 @@ def test_admin_posts_require_csrf(tmp_path: Path) -> None:
|
|||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
"/admin/accounts",
|
"/admin/cleanup",
|
||||||
auth=("admin", "admin"),
|
auth=("admin", "admin"),
|
||||||
data={"hostname": "home.example.net"},
|
data={},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|||||||
Reference in new issue
Block a user