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.
|
"""Seed starter first names into a local or remote Stork instance.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
# against local make run (cookies via invite):
|
STORK_URL=http://10.0.10.45:8094 STORK_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-)" \\
|
|
||||||
.venv/bin/python scripts/seed_names.py
|
.venv/bin/python scripts/seed_names.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -31,9 +26,11 @@ def main() -> int:
|
|||||||
print("Set STORK_INVITE", file=sys.stderr)
|
print("Set STORK_INVITE", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
names = json.loads(SEED.read_text())["names"]
|
names = json.loads(SEED.read_text())["names"]
|
||||||
with httpx.Client(base_url=base, timeout=30.0) as client:
|
headers = {
|
||||||
session = client.post("/api/session", json={"invite": invite, "display_name": display})
|
"X-Stork-Invite": invite,
|
||||||
session.raise_for_status()
|
"X-Stork-Display-Name": display,
|
||||||
|
}
|
||||||
|
with httpx.Client(base_url=base, timeout=30.0, headers=headers) as client:
|
||||||
for item in names:
|
for item in names:
|
||||||
res = client.post("/api/names", json=item)
|
res = client.post("/api/names", json=item)
|
||||||
if res.status_code == 400 and "already exists" in res.text:
|
if res.status_code == 400 and "already exists" in res.text:
|
||||||
|
|||||||
+9
-2
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -67,12 +68,18 @@ def _session(
|
|||||||
stork_voter: str | None = Cookie(default=None),
|
stork_voter: str | None = Cookie(default=None),
|
||||||
stork_name: str | None = Cookie(default=None),
|
stork_name: str | None = Cookie(default=None),
|
||||||
x_stork_invite: str | None = Header(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]:
|
) -> dict[str, str]:
|
||||||
invite = x_stork_invite or stork_invite
|
invite = x_stork_invite or stork_invite
|
||||||
if not _invite_ok(invite):
|
if not _invite_ok(invite):
|
||||||
raise HTTPException(401, "Invite required")
|
raise HTTPException(401, "Invite required")
|
||||||
voter = (stork_voter or "").strip()
|
voter = (x_stork_voter or stork_voter or "").strip()
|
||||||
name = (stork_name 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:
|
if not voter or not name:
|
||||||
raise HTTPException(401, "Session required — open the invite link and enter your name")
|
raise HTTPException(401, "Session required — open the invite link and enter your name")
|
||||||
return {"voter_key": voter, "display_name": 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"]
|
listed = client.get("/api/names?kind=first").json()["items"]
|
||||||
assert listed[0]["score"] == 2
|
assert listed[0]["score"] == 2
|
||||||
assert listed[0]["vote_count"] == 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