Files
vortex/tui/app/vortex_tui/session.py
T

83 lines
2.9 KiB
Python
Raw Normal View History

"""Credentials loading and Matrix client construction."""
import json
import os
import sys
import time
from pathlib import Path
from nio import AsyncClient, AsyncClientConfig, LoginResponse
def store_dir() -> Path:
return Path(os.environ.get("STORE_DIR", "/shared/tui-store"))
def load_credentials(retries: int = 10, delay: float = 3.0) -> dict:
"""Read CREDENTIALS_FILE, waiting a bit in case bootstrap is still provisioning."""
path = Path(os.environ.get("CREDENTIALS_FILE", "/shared/credentials.json"))
for attempt in range(retries):
try:
return json.loads(path.read_text())
except (OSError, json.JSONDecodeError):
if attempt < retries - 1:
print(
f"vortex-tui: waiting for {path} (bootstrap still provisioning?)...",
file=sys.stderr,
)
time.sleep(delay)
sys.exit(
f"vortex-tui: credentials file {path} missing or invalid after "
f"{retries} attempts. Run the bootstrap first, or set CREDENTIALS_FILE."
)
async def build_client(creds: dict) -> AsyncClient:
"""Return a logged-in AsyncClient with its own persisted E2EE device.
Order: STORE_DIR/session.json (our own device) -> password login (persist
new device) -> bootstrap access_token fallback.
"""
store = store_dir()
store.mkdir(parents=True, exist_ok=True)
homeserver = (
os.environ.get("MATRIX_HOMESERVER")
or creds.get("homeserver")
or "http://localhost:8008"
)
user_id = creds["user_id"]
# encryption_enabled defaults to True only when olm is importable,
# so this works with or without the [e2e] extra installed.
client = AsyncClient(
homeserver,
user_id,
store_path=str(store),
config=AsyncClientConfig(store_sync_tokens=True),
)
session_file = store / "session.json"
if session_file.exists():
s = json.loads(session_file.read_text())
client.restore_login(s["user_id"], s["device_id"], s["access_token"])
return client
if creds.get("password"):
resp = await client.login(creds["password"], device_name="vortex-tui")
if isinstance(resp, LoginResponse):
session_file.write_text(
json.dumps(
{
"user_id": resp.user_id,
"device_id": resp.device_id,
"access_token": resp.access_token,
}
)
)
return client
if creds.get("access_token"):
# Fall back to the bootstrap token (shares the bootstrap device).
client.restore_login(
user_id, creds.get("device_id") or "VORTEXTUI", creds["access_token"]
)
return client
await client.close()
raise RuntimeError("no usable password or access_token in credentials file")