Players

Overview

Player accounts and ship ownership.

Authentication

  • Username/password authentication
  • Passwords stored using secure hashing (argon2)
  • JWT tokens issued on successful login
  • Token used for REST API and WebSocket authentication

Authentication Flow

  1. Player submits username/password to /api/auth/login
  2. Server validates credentials
  3. Server checks if player’s ship exists in physics service
  4. If ship missing, server calls physics.SpawnShip() to re-create it
  5. Server returns JWT token
  6. Player includes token in Authorization: Bearer <token> header for REST calls
  7. Player passes token as query parameter when opening WebSocket connection
  8. Token expires after 24 hours

Ship re-spawn on login: If a player’s ship data is missing from Redis (e.g., due to data loss, manual cleanup, or server reset), the players service automatically re-spawns the ship during authentication. This ensures players can always log in and play without manual intervention.

Active ship assignment (#771): When a player has ships but no active ship set in Redis (e.g., after data loss), the first ship is assigned as active using SET NX (set-if-not-exists). This prevents a race condition where concurrent login requests could overwrite each other’s assignment. If SET NX returns false (another request won the race), the existing value is read back and used instead.

Token Refresh

  • While connected via WebSocket, api-gateway auto-refreshes token before expiry
  • api-gateway calls players.RefreshToken() via gRPC to generate new JWT
  • api-gateway sends token_refresh message to client 1 hour before expiry
  • Client stores new token for future reconnections
  • If disconnected for >24 hours, player must re-authenticate via login

Token Refresh Failure Handling

Scenario Behavior
Refresh succeeds Client stores new token, continues normally
Refresh fails (server error) Server retries 3 times at 5-minute intervals
All retries fail Connection continues until token expires
Token expires while connected Server sends E005, disconnects client
Client reconnects after disconnect Must re-authenticate via login

The 1-hour refresh window provides 60 minutes for up to 3 retry attempts (at 0, 5, and 10 minutes) before token expiration.

JWT Signing Key Rotation

Routine rotation:

  1. Update Kubernetes secret galaxy-secrets with new JWT signing key
  2. Perform rolling restart of api-gateway and players services
  3. Old tokens continue working until expiry (max 24 hours)
  4. New tokens issued with new key

Emergency rotation (key compromise):

  1. Update Kubernetes secret with new key
  2. Restart all services immediately (not rolling)
  3. All existing tokens invalidated instantly
  4. All connected players disconnected with E005
  5. Players must re-authenticate via login

No dual-key support in MVP — rotation invalidates old key immediately on restart.

Registration Flow

When a new player registers, the following sequence occurs:

Step Service Action
1 api-gateway Receives POST /api/auth/register (with optional system_id)
2 api-gateway Calls players.Register(username, password, system_id) via gRPC
3 players Validates username format (3-20 chars, alphanumeric + underscore)
4 players Validates password length (minimum 8 characters)
5 players Checks username not already taken
6 players Generates player_id and ship_id (UUIDs)
7 players Hashes password with argon2
8 players Inserts record into PostgreSQL (within transaction)
9 players Calls physics.SpawnShip(ship_id, player_id, username, system_id)
10 physics Creates ship in system’s default spawn location
11 physics Increments game:total_spawns atomically
12 physics Publishes ship.spawned event to Redis Stream
13 physics Returns success with initial ShipState
14 players Commits PostgreSQL transaction
15 players Generates JWT token
16 players Returns RegisterResponse with player_id, ship_id, token
17 api-gateway Returns token to client

Failure handling:

Failure Point Behavior
Validation fails (steps 3-5) Return error immediately (E010, E011, E012)
PostgreSQL insert fails Return E008 server error
physics.SpawnShip fails (any exception) Rollback PostgreSQL transaction, return E008
After physics succeeds Transaction commits; registration complete

Registration is atomic: either both account and ship exist, or neither does. The spawn failure rollback catches any exception (not just gRPC errors) to handle network timeouts, serialization failures, and other transient issues (#772).

JWT Token Preconditions

Before generating a JWT token (at registration, login, or refresh), the players service must verify that ship_id is a valid UUID and not null. If ship_id is missing or null, the service must return an error (E008) rather than encoding a literal "None" string into the token. This applies to all token-issuing code paths: register, authenticate, and refresh_token.

Player Properties

Property Type Description
id uuid Unique player identifier
username string Unique display name
password_hash string Hashed password
created_at timestamp Account creation time

Password Reset

  • No self-service password reset (MVP)
  • Players who forget passwords must contact an administrator
  • Admins can reset player passwords via admin dashboard
  • New password provided to player out-of-band (e.g., direct message)

Future releases may add email-based self-service reset.

Account Deletion

  • Player can delete their account via web client
  • Ship is removed immediately upon account deletion
  • Username becomes available for reuse
  • Action is irreversible

Ship Ownership

Initial Release

  • One ship per player (each player controls exactly one ship)
  • Up to 16 concurrent connections (MVP infrastructure limit)
  • Ship assigned automatically at account creation
  • Starting location: Near the system’s default jump gate or station (Sol: Earth orbit, Alpha Centauri: Chiron orbit)
  • All new players start in the Sol system (server default)

Connection Limits

Limit Type Value
Registered accounts Soft Unlimited
Concurrent connections Hard 16
  • Players can register even when 16 are online (account stored in PostgreSQL)
  • When connecting with 16 already online: WebSocket rejected with E013 “Server full”
  • Client displays: “Server is full, please try again later”
  • Ship state preserved in Redis while disconnected

Future Releases

  • Multiple players
  • Multiple ships per player
  • Ship acquisition mechanics

Back to top

Galaxy — Kubernetes-based multiplayer space game

This site uses Just the Docs, a documentation theme for Jekyll.