Home Auth, Accounts & Control Plane

Auth, Accounts & Control Plane API


1. Summary

Flatland3 uses three API surfaces, not one protocol for everything:

Surface Protocol Who uses it
Game transport TCP → QUIC; postcard intents + tick stream TUI, bots, agents while playing
Control plane HTTP REST/JSON CLIs, agents, LLM automations, account admin
Internal cluster gRPC (tonic) gateway ↔ region worker ↔ control plane

Human login is email + password. Automation uses scoped API tokens tied to the same account. Item instances use UUID v7. World interaction uses a 1.5 m server-side radius.


2. Locked decisions

# Decision Value
A1 Account auth Email + password (argon2id hashed)
A2 Automation auth Per-account API tokens — create, revoke, scope, label
A3 Item instance IDs UUID v7 (time-ordered, globally unique, Postgres uuid type)
A4 Interaction radius 1.5 m — harvest, use, talk, loot (server validates distance)
A5 Control plane public API HTTP/JSON REST on separate port from game transport
A6 Sim ↔ persistence hot path gRPC (internal only, not exposed to clients)

3. Auth model

3.1 Email + password (humans)

POST /v1/auth/register   { email, password, display_name }
POST /v1/auth/login      { email, password }
POST /v1/auth/logout     Authorization: Bearer <session>
POST /v1/auth/refresh    { refresh_token }   # optional v2
  • Password hashed with argon2id (never stored plain).
  • Login returns a short-lived session token (opaque, stored in Redis, ~24 h TTL, sliding).
  • TUI stores session in local config (~/.config/flatland/session); game Hello carries session token.

3.2 API tokens (CLI, agents, LLM automations)

Users manage tokens from account settings or CLI:

flatland auth login                          # email/password → session
flatland auth token create --name "ci-bot" --scope play,read
flatland auth token list
flatland auth token revoke <token_id>
Property Rule
Storage Hash only in Postgres (like passwords); show plaintext once at creation
Prefix flat_live_ / flat_test_ for log-safe identification
Scopes play, read, write, world:read, world:write (fine-grained later)
TTL Optional expiry; default no expiry, user revokes manually
Binding Token belongs to account; play scope requires character_id at connect time

Environment convention (agent-first):

export FLATLAND_API_TOKEN=flat_live_...
export FLATLAND_CHARACTER_ID=<uuid>
flatland status --json
flatland play --json

LLM agents and CI use API tokens — never email/password in scripts.

3.3 Game connect auth

ClientMessage::Hello extended:

Hello {
    client_name: String,
    protocol_version: u16,
    auth: AuthCredential,
}

enum AuthCredential {
    Session { token: String },
    ApiToken { token: String, character_id: Uuid },
    DevLocal,  // embedded --local only, no auth
}

Gateway validates with control plane (or Redis cache) before spawning entity. Invalid auth → disconnect with error frame.


4. Control plane HTTP API

Base URL: http://127.0.0.1:7380/v1 (dev) — separate from game TCP :7373.

4.1 Why HTTP/JSON for control plane

Concern HTTP/JSON gRPC to agents Game binary protocol
LLM / curl / CI ✓ native awkward wrong tool
OpenAPI / docs harder N/A
Latency for play too slow
Type safety internal

Rule: Anything that is not 30 Hz realtime sim uses HTTP. Playing uses game transport.

4.2 Public endpoints (v1)

Auth & account

Method Path Auth
POST /auth/register none
POST /auth/login none
POST /auth/logout session
GET /me session or token (read)
POST /me/tokens session
GET /me/tokens session
DELETE /me/tokens/{id} session

Characters

Method Path Auth
GET /characters session / token read
POST /characters session
GET /characters/{id} session / token read
GET /characters/{id}/inventory session / token read
GET /characters/{id}/state session / token read

Agent / JSON play bridge (Tier 2b)

Read-only and meta over HTTP; mutating gameplay still goes through game transport with play scope:

Method Path Purpose
GET /characters/{id}/nearby Snapshot of AOI entities (cached ~1 tick)
POST /characters/{id}/intents Optional later — queue intent if HTTP-only agent; prefer game TCP

Recommendation for v1 agents: connect game TCP with API token + use --json on flatland for intent UX. HTTP is for account, inventory queries, and automation setup — not tick stream.

4.3 Response shape (agent-first)

All JSON responses:

{
  "ok": true,
  "data": { ... },
  "error": null
}

CLI: flatland ... --json uses same envelope. Errors include code, message, details.

4.4 Internal gRPC (cluster only)

flatland-control-plane exposes gRPC on :7381 (localhost / private network only):

Service Called by Purpose
AuthService.ValidateCredential gateway Hello auth
CharacterService.Checkpoint region worker position + vitals on tick boundary
InventoryService.Commit region worker harvest mint, trade (ACID)
ItemService.Mint region worker UUID v7 unique instance creation

Region workers never talk to Postgres directly — all durable writes through control plane gRPC.


5. UUID v7 for item instances

Per 18-unique-items-and-procedural-economy.html:

CREATE TABLE item_instances (
    id          UUID PRIMARY KEY,   -- v7 generated server-side
    template_id TEXT NOT NULL,
    owner_id    UUID,               -- character or container
    props       JSONB NOT NULL,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);
  • Mint authority: control plane only (region worker requests mint over gRPC).
  • No client-supplied IDs — prevents collision and cheating.
  • v7 over v4: better Postgres index locality for time-ordered loot/history queries.

6. Interaction radius (1.5 m)

Server validates for Intent::Use, Intent::Harvest, Intent::Talk, loot:

const INTERACTION_RADIUS_M: f32 = 1.5;

fn can_interact(actor: &Transform, target: &WorldCoord) -> bool {
    let dx = actor.position.x - target.x;
    let dy = actor.position.y - target.y;
    let dz = actor.position.z - target.z;
    dx * dx + dy * dy + dz * dz <= INTERACTION_RADIUS_M * INTERACTION_RADIUS_M
}
  • Cylindrical or spherical — use spherical 1.5 m for consistency with AOI docs.
  • Client may show hint in TUI; server is authoritative.
  • Harvest nodes in segment YAML have anchor (x, y, z); must be within radius.

7. Crate layout (updated)

crates/
  control-plane/       # HTTP :7380 + gRPC :7381, sqlx, migrations
  protocol/            # extend Hello auth, Intent::Harvest, Intent::Use
  sim/                 # interaction radius, resource nodes
  ...
tools/
  flatland/            # auth login, --json, play
  flatland-server/     # game TCP :7373 (gateway only)
  flatland-world/      # world ops (later)

Two binaries in prod:

  • flatland-server — game gateway + region worker(s)
  • flatland-control-plane — HTTP + gRPC + Postgres

Dev: docker compose up for Postgres + Redis; both binaries locally.


8. Implementation order (Tier 1 + 2)

Step Deliverable
1 control-plane crate, migrations (accounts, api_tokens, characters)
2 HTTP auth: register, login, token CRUD
3 gRPC ValidateCredential + gateway Hello auth
4 Character create/list, position checkpoint on disconnect
5 Intent::Harvest + 1.5 m check + resource nodes in segment
6 gRPC ItemService.Mint (UUID v7) + inventory persist
7 TUI: login flow, inventory panel, harvest key
8 flatland auth * + flatland --json

9. Security notes

  • Rate-limit /auth/login and token validation (Redis).
  • API token scopes enforced on every HTTP route and at game Hello.
  • Audit log table for token use, mints, trades (Postgres append-only).
  • DevLocal auth disabled in production builds / flag.

Locks Tier 2 decisions from development planning session 2026-07-06.