Skip to content

API Gateway

Hono TypeScript server — Steam OpenID, JWT sessions, admin mutation proxy, hosting API.


Overview

The API Gateway is a Hono TypeScript server running on port 3081. It serves as the trusted intermediary between the React panel and SpacetimeDB, handling authentication, authorization, and mutation proxying.

The gateway connects to SpacetimeDB using the publisher's token, so ctx.sender() in reducers matches ModuleConfig.owner_identity. This is how reducers know the caller is the trusted gateway (via the is_gateway() check).

Architecture

graph LR
    Panel["React Panel (browser)"]

    subgraph Auth["Auth Routes"]
        A1["GET /auth/steam"]
        A2["GET /auth/callback"]
        A3["GET /auth/me"]
        A4["POST /auth/logout"]
    end

    subgraph Admin["Admin Mutation Routes"]
        B1["POST /api/ban"]
        B2["POST /api/unban"]
        B3["POST /api/kick"]
        B4["POST /api/announce"]
        B5["POST /api/motd"]
        B6["POST /api/max-players"]
        B7["POST /api/password"]
        B8["POST /api/whitelist"]
        B9["POST /api/grant-role"]
        B10["POST /api/revoke-role"]
    end

    subgraph Hosting["Hosting API Routes"]
        C1["POST /api/hosting/start"]
        C2["POST /api/hosting/stop"]
        C3["POST /api/hosting/restart"]
        C4["POST /api/hosting/wipe"]
        C5["POST /api/hosting/command"]
    end

    D1["/registry/v1/*"]

    Panel --> Auth
    Panel --> Admin
    Panel --> Hosting
    Panel --> D1

    A1 --> SteamOID["Steam OpenID redirect"]
    A2 --> Verify["Verify assertion, issue JWT"]
    A3 --> Session["Return current session"]
    A4 --> Clear["Clear cookie, end session"]

    B1 --> R1["panel_ban_player reducer"]
    B2 --> R2["panel_unban_player reducer"]
    B3 --> R3["panel_kick_player reducer"]
    B4 --> R4["panel_announcement reducer"]
    B5 --> R5["panel_set_motd reducer"]
    B6 --> R6["panel_set_max_players reducer"]
    B7 --> R7["panel_set_password reducer"]
    B8 --> R8["panel_toggle_whitelist reducer"]
    B9 --> R9["admin_grant_role reducer"]
    B10 --> R10["admin_revoke_role reducer"]

    C1 --> BH1["BisectHosting Starbase API"]
    C2 --> BH2["BisectHosting Starbase API"]
    C3 --> BH3["BisectHosting Starbase API"]
    C4 --> BH4["BisectHosting Starbase API"]
    C5 --> RCON["Server RCON command"]

    D1 --> ModReg["Mod Registry REST API (35+ routes)"]

Environment Variables

Variable Required Description
JWT_SECRET Yes Random 64+ character string for JWT signing (HS256)
STDB_HOST Yes SpacetimeDB WebSocket URL (e.g., ws://localhost:3000)
STDB_TOKEN Yes Output of spacetime token show — publisher credentials
STDB_MODULE Yes Module name (e.g., qs-bridge)
GATEWAY_URL Yes Public URL for Steam OpenID return (e.g., https://panel.qs-zuq.com)
BISECT_API_KEY No BisectHosting Starbase API key (for hosting controls)
PORT No Gateway port (default: 3081)

Authentication Routes

GET /auth/steam

Redirects the browser to Steam's OpenID 2.0 login page. The return URL is {GATEWAY_URL}/auth/callback.

GET /auth/callback

Verifies the Steam OpenID assertion, extracts the Steam64 ID, looks up AdminRole in STDB, creates a PanelSession, and issues a signed JWT cookie.

GET /auth/me

Returns the current session from the JWT cookie:

{
  "playerId": "Steam:76561198XXXXXXXXX",
  "displayName": "PlayerName",
  "adminLevel": 2,
  "expiresAt": "2026-03-27T00:00:00Z"
}

Returns 401 if no valid session exists.

POST /auth/logout

Clears the qs-session cookie and calls panel_end_session reducer.

Admin Mutation Routes

All admin routes require a valid JWT cookie with an appropriate admin level. The gateway:

  1. Validates the JWT cookie
  2. Checks AdminRole level against the required permission
  3. Calls the corresponding STDB reducer via the publisher connection
  4. Returns the reducer result to the client
Route Method Min Role STDB Reducer
/api/ban POST Moderator panel_ban_player
/api/unban POST Moderator panel_unban_player
/api/kick POST Moderator panel_kick_player
/api/announce POST Moderator panel_announcement
/api/motd POST Admin panel_set_motd
/api/max-players POST Admin panel_set_max_players
/api/password POST Admin panel_set_password
/api/whitelist POST Admin panel_toggle_whitelist
/api/grant-role POST Owner admin_grant_role
/api/revoke-role POST Owner admin_revoke_role

Hosting API Routes

Server lifecycle controls via the BisectHosting Starbase API. The gateway holds BISECT_API_KEY server-side — the React app never touches hosting secrets.

Route Method Min Role Description
/api/hosting/start POST Admin Start a game server
/api/hosting/stop POST Admin Stop a game server
/api/hosting/restart POST Admin Restart a game server
/api/hosting/wipe POST Owner Wipe a game server (destructive)
/api/hosting/command POST Admin Send RCON command

Rate limit: 1 lifecycle action per server per 60 seconds.

Vite Dev Proxy

During development, the Vite dev server proxies API requests to the gateway:

// vite.config.ts
export default defineConfig({
  server: {
    port: 3080,
    proxy: {
      '/auth': 'http://localhost:3081',
      '/api': 'http://localhost:3081',
    },
  },
});