Files
John SmithandClaude b7bb40b160 feat(gateway): durability-bridge auth + header-first tokens on legacy routes (iteration 8)
persist/hydrate require auth with x-card-style participation gating
(gm/admin any session, others via bound lobby roster, unbound fail closed).
All remaining token:Query routes migrated to _require_auth (Bearer preferred);
EnginePersistRequest.owner_user_id spoofing removed — ownership from verified
token. Sweep documented remaining unauthenticated LLM-spend/agent routes for a
coordinated follow-up. 11 new tests red-first; pytest 618 passed / 22 skipped.

Co-Authored-By: Claude <[email protected]>
2026-08-24 19:26:51 -04:00

113 lines
3.8 KiB
Python

"""Tests for orchestrator-backed identity & sessions (/api/v1/auth/*)."""
import pytest
from fastapi.testclient import TestClient
from vtt_orchestrator.server import app
client = TestClient(app)
@pytest.fixture()
def fresh_account(request):
email = f"player_{abs(hash(request.node.name)) % 10**8}@aethertable.io"
password = "secret-dice"
resp = client.post(
"/api/v1/auth/signup",
json={
"email": email,
"display_name": "Test Hero",
"password": password,
"role": "player",
},
)
assert resp.status_code == 200, resp.text
return {"email": email, "password": password}
def test_signup_login_and_session_roundtrip(fresh_account):
login = client.post(
"/api/v1/auth/login",
json={"email": fresh_account["email"], "password": fresh_account["password"]},
)
assert login.status_code == 200
body = login.json()
assert body["user"]["role"] == "player"
assert body["expires_in"] > 0
session = client.get("/api/v1/auth/session", params={"token": body["token"]})
assert session.status_code == 200
assert session.json()["valid"] is True
assert session.json()["user"]["email"] == fresh_account["email"]
def test_wrong_password_rejected():
resp = client.post(
"/api/v1/auth/login",
json={"email": "[email protected]", "password": "wrong-password"},
)
assert resp.status_code == 401
def test_gm_seed_account_is_env_gated(monkeypatch):
"""The hardcoded dev backdoor is gone: seed credentials must come from
AETHERTABLE_SEED_GM_* env vars and nothing seeds without them."""
from vtt_orchestrator import server as server_mod
monkeypatch.setattr(server_mod, "SEED_GM_EMAIL", "")
monkeypatch.setattr(server_mod, "SEED_GM_PASSWORD", "")
denied = client.post(
"/api/v1/auth/login", json={"email": "[email protected]", "password": "provisioned"}
)
assert denied.status_code == 401
email = f"seeded_gm_{abs(hash('env')) % 10**8}@example.com"
monkeypatch.setattr(server_mod, "SEED_GM_EMAIL", email)
monkeypatch.setattr(server_mod, "SEED_GM_PASSWORD", "provisioned-secret")
seeded = client.post(
"/api/v1/auth/login", json={"email": email, "password": "provisioned-secret"}
)
assert seeded.status_code == 200
assert seeded.json()["user"]["role"] == "gm"
def test_lore_assertion_requires_auth(fresh_account):
"""Unauthenticated writes to shared lore canon are refused."""
payload = {
"proposing_entity_id": "npc_test_1",
"subject_node_id": "Tavern_Room",
"predicate_relation": "RULES",
"object_node_id": "Keeper_Garrick",
"context_sentence": "The keeper rules the tavern.",
"epistemic_tier": "SUBJECTIVE_RUMOR",
}
anon = client.post("/api/v1/lore/assert", json=payload)
assert anon.status_code == 401, "missing token must be rejected with 401"
login = client.post(
"/api/v1/auth/login",
json={"email": fresh_account["email"], "password": fresh_account["password"]},
)
token = login.json()["token"]
ok = client.post("/api/v1/lore/assert", params={"token": token}, json=payload)
assert ok.status_code == 200
def test_tampered_token_rejected(fresh_account):
login = client.post(
"/api/v1/auth/login",
json={"email": fresh_account["email"], "password": fresh_account["password"]},
)
token = login.json()["token"]
tampered = token[:-4] + ("0000" if not token.endswith("0000") else "1111")
resp = client.get("/api/v1/auth/session", params={"token": tampered})
assert resp.status_code == 401
def test_duplicate_signup_conflict(fresh_account):
resp = client.post(
"/api/v1/auth/signup",
json={"email": fresh_account["email"], "password": fresh_account["password"]},
)
assert resp.status_code == 409