user-management-system
User management system (current state)
This note documents the current user management system for developers. It covers authentication, session tokens, account API keys, agent ownership, runtime registration, and the main security boundaries.
It is intentionally definitive and implementation-oriented.
System scope
The current user management model has two identity layers:
- Account identity (email/password login, bearer token session)
- Runtime agent identity (account-owned
agentId+ account API key used by SDKaddPlayer)
World session identity (sid) is separate and is used to scope world mutations.
Most important components (in priority order)
Auth session store (
auth-store.ts,auth-session.ts)- Creates and validates bearer tokens.
- Maps token ->
userIdin Redis with TTL. - Gates account-level API endpoints (
/api/agents,/api/agents/api-key,/api/auth/me, MCP register).
Agent repository (
agent-repository.ts,redis-agent-repository.ts,in-memory-agent-repository.ts)- Stores account-owned agents and account API key metadata.
- Enforces ownership checks (
agent.userId === userId) for management actions. - Verifies account API keys for runtime registration.
PlayWorld ownership gate (
play-world.tsaddPlayer)- Validates
apiKey->userId->agentIdownership before an agent can join the world. - Prevents cross-account agent impersonation at runtime.
- Validates
Session validator for world RPC/mutations (
session-validation.ts+ route-level checks)- Validates
sidfor routes that mutate world state. - Ensures runtime operations target an active session.
- Validates
API key crypto (
api-key-crypto.ts)- Uses
scrypt+ salt for stored key hash. - Uses constant-time comparison for verification.
- Uses SHA-256 lookup index for keyed retrieval path.
- Uses
End-to-end flows (all steps)
A) Account registration and login
- Client calls
POST /api/auth/lookupwith email. - If new account:
POST /api/auth/registerwith email, name, password. - If existing account:
POST /api/auth/loginwith email, password. - Server creates session token (
createSession) and returns bearer token. - Client stores token and sends
Authorization: Bearer <token>on account routes.
B) Account API key lifecycle
- Authenticated user calls
POST /api/agents/api-key. - Server verifies bearer token ->
userId. - Repository creates one account API key (current limit:
MAX_API_KEYS_PER_ACCOUNT = 1). - Plain key is returned once; only hash + lookup index are persisted.
GET /api/agents/api-keyreturns metadata (hasKey,createdAt) only.
C) Agent record lifecycle (account-owned)
- Authenticated user calls
POST /api/agentswithnameandtoolNames. - Repository creates account-owned
agentId(MAX_AGENTS_PER_ACCOUNT = 2). - User lists via
GET /api/agents. - User deletes via
DELETE /api/agents?id=...(ownership check required).
D) Runtime world registration (addPlayer)
- SDK/browser calls
POST /api/agent-play/players?sid=...with:agentId(required)agentregistration (tool contract)apiKey(required when repository is configured)
- Route validates
sidand callsPlayWorld.addPlayer. PlayWorld.addPlayerverifies:- API key -> account
userId agentIdexists- agent belongs to that
userId
- API key -> account
- If valid, agent is added as world occupant and fanout is emitted.
E) World mutation calls from SDK (sdk/rpc)
getWorldSnapshotandgetPlayerChainNodeuse live session scope and do not require querysid.- Mutating ops (
recordInteraction,recordJourney) require validsid. - Route-level validation rejects missing/invalid
sid. PlayWorldapplies mutation and publishes world fanout.
Data model summary
Auth keys (Redis):
auth:email:<normalized-email>->userIdauth:user:<userId>hash includespasswordHashauth:session:<token>->userId(TTL)
Agent/account keys (Redis):
account:<userId>:apiKeyhash (apiKeyHash,lookupIndex,createdAt)lookup:<sha256(apiKey)>->u:<userId>agent:<agentId>hash includesuserId, name, toolNames, countersuser:<userId>:agentsset of ownedagentIds
Security boundaries that currently work
- Password verification uses hashed passwords (not plaintext).
- Session tokens are random and time-bounded (30-day TTL).
- API keys are not stored plaintext; verification uses
scrypt+ timing-safe compare. - Ownership enforcement blocks attaching another user’s agent with your API key.
- Route auth split is explicit:
- bearer token for account management endpoints,
sidfor world session mutation endpoints.
Potential security issues (current risk list)
Long session TTL with no rotation/revocation endpoint
- Tokens are valid for 30 days.
- There is no explicit logout/revoke API to invalidate active tokens server-side.
- Risk: stolen bearer token has long replay window.
Single API key per account, no key rotation workflow
- Operational friction encourages key reuse.
- If key leaks, replacement path is limited and disruptive.
No rate limiting on auth and key-sensitive routes
lookup,login,register, and key-bearing calls can be brute-forced or abused.- Requires edge/API rate limiting to reduce credential-stuffing risk.
No explicit CSRF strategy for browser-origin bearer usage
- Current model is bearer-header based, which is good for non-cookie auth.
- If browser clients persist tokens insecurely, XSS becomes high impact.
User enumeration signal in lookup flow
/api/auth/lookupreturns account existence.- Useful UX, but leaks registration state for arbitrary emails.
No audited authorization policy layer
- Authorization checks are implemented in route handlers and world methods.
- Works now, but policy is distributed across files and may drift as features grow.
Developer checklist before touching user management
- Preserve ownership checks (
apiKey -> userId -> agent.userId) in runtime registration. - Preserve route-level auth checks (
Bearerfor account routes,sidfor world mutation routes). - Never persist plaintext API keys or passwords.
- Keep
getWorldSnapshot/getPlayerChainNodesemantics stable unless intentionally changing client contract. - Add tests for both success and explicit unauthorized/forbidden paths.
Source map
- Auth/session:
packages/web-ui/src/server/auth-store.ts,auth-session.ts - Account/agent APIs:
packages/web-ui/src/app/api/auth/*,packages/web-ui/src/app/api/agents/* - Repository:
packages/web-ui/src/server/agent-play/*agent-repository*.ts - Runtime ownership gate:
packages/web-ui/src/server/agent-play/play-world.ts(addPlayer) - World session validation:
packages/web-ui/src/server/agent-play/session-validation.ts - SDK runtime caller:
packages/sdk/src/lib/remote-play-world.ts