mirror of
https://github.com/Heretek-AI/AetherTable.git
synced 2026-11-07 17:15:54 -10:00
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]>
113 lines
3.8 KiB
Python
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
|