Allow header-based invite auth for seeding over HTTP.
Secure cookies blocked the seed script against the LAN port; X-Stork-Invite + display-name headers fix that.
This commit is contained in:
@@ -1,12 +1,7 @@
|
||||
"""Seed starter first names into a local or remote Stork instance.
|
||||
|
||||
Usage:
|
||||
# against local make run (cookies via invite):
|
||||
STORK_URL=http://127.0.0.1:8094 STORK_INVITE=local-dev-invite \\
|
||||
.venv/bin/python scripts/seed_names.py
|
||||
|
||||
# against prod after deploy:
|
||||
STORK_URL=https://stork.levkin.ca STORK_INVITE="$(ssh root@10.0.10.45 grep STORK_INVITE_TOKEN /opt/stork/.env | cut -d= -f2-)" \\
|
||||
STORK_URL=http://10.0.10.45:8094 STORK_INVITE=... \\
|
||||
.venv/bin/python scripts/seed_names.py
|
||||
"""
|
||||
|
||||
@@ -31,9 +26,11 @@ def main() -> int:
|
||||
print("Set STORK_INVITE", file=sys.stderr)
|
||||
return 1
|
||||
names = json.loads(SEED.read_text())["names"]
|
||||
with httpx.Client(base_url=base, timeout=30.0) as client:
|
||||
session = client.post("/api/session", json={"invite": invite, "display_name": display})
|
||||
session.raise_for_status()
|
||||
headers = {
|
||||
"X-Stork-Invite": invite,
|
||||
"X-Stork-Display-Name": display,
|
||||
}
|
||||
with httpx.Client(base_url=base, timeout=30.0, headers=headers) as client:
|
||||
for item in names:
|
||||
res = client.post("/api/names", json=item)
|
||||
if res.status_code == 400 and "already exists" in res.text:
|
||||
|
||||
+9
-2
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import secrets
|
||||
from pathlib import Path
|
||||
@@ -67,12 +68,18 @@ def _session(
|
||||
stork_voter: str | None = Cookie(default=None),
|
||||
stork_name: str | None = Cookie(default=None),
|
||||
x_stork_invite: str | None = Header(default=None),
|
||||
x_stork_display_name: str | None = Header(default=None),
|
||||
x_stork_voter: str | None = Header(default=None),
|
||||
) -> dict[str, str]:
|
||||
invite = x_stork_invite or stork_invite
|
||||
if not _invite_ok(invite):
|
||||
raise HTTPException(401, "Invite required")
|
||||
voter = (stork_voter or "").strip()
|
||||
name = (stork_name or "").strip()
|
||||
voter = (x_stork_voter or stork_voter or "").strip()
|
||||
name = (x_stork_display_name or stork_name or "").strip()
|
||||
if name and not voter:
|
||||
# API/script clients without cookies (e.g. seed over HTTP while Secure cookies are on).
|
||||
digest = hashlib.sha256(name.lower().encode()).hexdigest()[:24]
|
||||
voter = f"hdr-{digest}"
|
||||
if not voter or not name:
|
||||
raise HTTPException(401, "Session required — open the invite link and enter your name")
|
||||
return {"voter_key": voter, "display_name": name}
|
||||
|
||||
@@ -103,3 +103,19 @@ def test_second_voter_ranking(client: TestClient, authed: TestClient) -> None:
|
||||
listed = client.get("/api/names?kind=first").json()["items"]
|
||||
assert listed[0]["score"] == 2
|
||||
assert listed[0]["vote_count"] == 2
|
||||
|
||||
|
||||
def test_header_auth_without_cookies(client: TestClient) -> None:
|
||||
headers = {
|
||||
"X-Stork-Invite": "test-invite-token",
|
||||
"X-Stork-Display-Name": "Seed Bot",
|
||||
}
|
||||
res = client.post(
|
||||
"/api/names",
|
||||
headers=headers,
|
||||
json={"kind": "first", "spelling": "HeaderOnly"},
|
||||
)
|
||||
assert res.status_code == 200
|
||||
listed = client.get("/api/names?kind=first", headers=headers)
|
||||
assert listed.status_code == 200
|
||||
assert any(n["spelling"] == "HeaderOnly" for n in listed.json()["items"])
|
||||
|
||||
Reference in New Issue
Block a user