LAN admin on 10.0.0.128 plus quick campaign mint form (#12)
Closes #11. ## Summary - Serve Django admin on LAN IP `10.0.0.128` (not `piha.li`). Compose now publishes `0.0.0.0:8005` and passes `SHORT_ADMIN_HOSTS` into the container. - Admin index has a campaign mint form: domain, campaign, source, metric. Save builds `https://{domain}/?utm_campaign=&utm_source=&utm_medium=` (metric) and shows a copyable short URL. - Public `/admin/` on `piha.lc` / `piha.li` stays 404. ## Test plan - [ ] `cd site && uv run python manage.py test` - [ ] Recreate compose (`docker compose up --build`) so `WEB_BIND` / `SHORT_ADMIN_HOSTS` take effect - [ ] From another LAN machine: `http://10.0.0.128:8005/admin/` (staff login) shows the mint form - [ ] Save a link for an allowlisted domain, copy the short URL, confirm it 302s - [ ] `https://piha.li/admin` still 404 Reviewed-on: #12
This commit was merged in pull request #12.
This commit is contained in:
+35
-1
@@ -7,7 +7,7 @@ import hmac
|
||||
import logging
|
||||
import secrets
|
||||
from datetime import datetime
|
||||
from urllib.parse import urlsplit, urlunsplit
|
||||
from urllib.parse import urlencode, urlsplit, urlunsplit
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import IntegrityError
|
||||
@@ -45,6 +45,40 @@ def host_allowed(hostname: str, allowed: list[str]) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def normalize_destination_host(raw: str) -> str:
|
||||
"""Strip scheme/path from a domain field. Raise ValidationError if empty."""
|
||||
raw = (raw or "").strip()
|
||||
if not raw:
|
||||
raise ValidationError("invalid url")
|
||||
if raw.startswith("//"):
|
||||
raise ValidationError("invalid url")
|
||||
if "://" not in raw:
|
||||
raw = "https://" + raw
|
||||
try:
|
||||
parts = urlsplit(raw)
|
||||
except ValueError as exc:
|
||||
raise ValidationError("invalid url") from exc
|
||||
hostname = (parts.hostname or "").lower().rstrip(".")
|
||||
if not hostname:
|
||||
raise ValidationError("invalid url")
|
||||
if parts.username or parts.password:
|
||||
raise ValidationError("invalid url")
|
||||
return hostname
|
||||
|
||||
|
||||
def build_tracked_url(*, domain: str, campaign: str, source: str, metric: str) -> str:
|
||||
"""Build an allowlisted https URL with UTM query params."""
|
||||
hostname = normalize_destination_host(domain)
|
||||
query = urlencode(
|
||||
{
|
||||
"utm_campaign": campaign.strip(),
|
||||
"utm_source": source.strip(),
|
||||
"utm_medium": metric.strip(),
|
||||
}
|
||||
)
|
||||
return validate_target_url(urlunsplit(("https", hostname, "/", query, "")))
|
||||
|
||||
|
||||
def validate_target_url(raw: str) -> str:
|
||||
"""Return a canonical https URL or raise ValidationError."""
|
||||
if not raw or not isinstance(raw, str):
|
||||
|
||||
Reference in New Issue
Block a user