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:
- Validates the JWT cookie
- Checks
AdminRolelevel against the required permission - Calls the corresponding STDB reducer via the publisher connection
- 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',
},
},
});
Related Pages¶
- Authentication — Steam OpenID flow details
- Reducers Reference — STDB reducers called by the gateway
- BisectHosting Integration — hosting API details