v0.5.0 user support with auth added, tests and doc updated

This commit is contained in:
2025-11-05 02:56:41 -05:00
parent 59486bfe32
commit a3f4db96fa
25 changed files with 2409 additions and 1172 deletions

View File

@ -4,12 +4,92 @@ Base URL: `http://localhost:8080/api/v1`
Content-Type: `application/json` (required for POST/PUT)
## Endpoints
## Authentication
The API supports optional JWT authentication for user accounts. When authenticated, games are associated with the user account.
### Register User
`POST /auth/register`
Creates new user account and returns JWT token.
**Request:**
```json
{
"username": "alice",
"email": "alice@example.com",
"password": "SecurePass123"
}
```
- `username` (string, required): 1-40 characters, alphanumeric and underscore only
- `email` (string, optional): Valid email address
- `password` (string, required): Minimum 8 characters, must contain letter and number
**Response (201):**
```json
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"userId": "550e8400-e29b-41d4-a716-446655440000",
"username": "alice",
"email": "alice@example.com",
"expiresAt": "2025-01-14T10:30:00Z"
}
```
### Login
`POST /auth/login`
Authenticates user and returns JWT token.
**Request:**
```json
{
"identifier": "alice",
"password": "SecurePass123"
}
```
- `identifier` (string, required): Username or email address
- `password` (string, required): User password
**Response (200):**
```json
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"userId": "550e8400-e29b-41d4-a716-446655440000",
"username": "alice",
"email": "alice@example.com",
"expiresAt": "2025-01-14T10:30:00Z"
}
```
### Get Current User
`GET /auth/me`
Returns authenticated user information. Requires authentication.
**Headers:**
```
Authorization: Bearer <token>
```
**Response (200):**
```json
{
"userId": "550e8400-e29b-41d4-a716-446655440000",
"username": "alice",
"email": "alice@example.com",
"createdAt": "2025-01-07T10:30:00Z"
}
```
## Game Endpoints
### Health Check
`GET /health`
Returns server status.
Returns server and storage status.
**Response (200):**
```json
@ -22,13 +102,18 @@ Returns server status.
Storage states:
- `"disabled"` - No storage path configured
- `"ok"` - Database operational
- `"ok"` - Database operational with auth enabled
- `"degraded"` - Write failures detected, operating memory-only
### Create Game
`POST /games`
Creates new game with specified players.
Creates new game with specified players. Optional authentication associates game with user.
**Headers (optional):**
```
Authorization: Bearer <token>
```
**Request:**
```json
@ -47,11 +132,6 @@ Creates new game with specified players.
}
```
- `type` (integer, required): 1=human, 2=computer
- `level` (integer, 0-20): Engine skill level for computer players
- `searchTime` (integer, 100-10000ms): Engine thinking time for computer players
- `fen` (string): Starting position in FEN notation (default: standard position)
**Response (201):**
```json
{
@ -61,37 +141,19 @@ Creates new game with specified players.
"state": "ongoing",
"moves": [],
"players": {
"white": {"id": "...", "color": 1, "type": 1},
"black": {"id": "...", "color": 2, "type": 2, "level": 15, "searchTime": 1000}
"white": {"id": "550e8400-...", "color": 1, "type": 1},
"black": {"id": "ai-player-...", "color": 2, "type": 2, "level": 15, "searchTime": 1000}
}
}
```
Note: When authenticated, human player IDs match the user's ID. Anonymous players receive unique UUIDs.
### Get Game
`GET /games/{gameId}`
Returns current game state.
**Response (200):**
```json
{
"gameId": "...",
"fen": "...",
"turn": "w",
"state": "ongoing",
"moves": ["e2e4", "e7e5"],
"players": {...},
"lastMove": {
"move": "e7e5",
"playerColor": "b",
"score": 25,
"depth": 12
}
}
```
States: `ongoing`, `pending` (computer thinking), `white wins`, `black wins`, `draw`, `stalemate`
### Make Move
`POST /games/{gameId}/moves`
@ -107,53 +169,27 @@ Submits human move or triggers computer move.
{"move": "cccc"}
```
Returns updated game state (200) or error (400).
### Undo Moves
`POST /games/{gameId}/undo`
Reverts moves from history.
**Request:**
```json
{"count": 2}
```
- `count` (integer, 1-300): Number of moves to undo (default: 1)
### Configure Players
`PUT /games/{gameId}/players`
Changes player configuration mid-game.
**Request:**
```json
{
"white": {"type": 2, "level": 5, "searchTime": 100},
"black": {"type": 1}
}
```
### Get Board
`GET /games/{gameId}/board`
Returns ASCII board visualization.
**Response (200):**
```json
{
"fen": "...",
"board": " a b c d e f g h\n8 r n b q k b n r 8\n..."
}
```
### Delete Game
`DELETE /games/{gameId}`
Removes game from memory. Returns 204 on success.
## Error Format
```json
{
"error": "Description",
@ -170,10 +206,23 @@ Error codes:
- `RATE_LIMIT_EXCEEDED` - Request limit exceeded
- `INVALID_REQUEST` - Malformed request
- `INVALID_CONTENT_TYPE` - Missing/wrong Content-Type header
- `INVALID_FEN` - Invalid FEN format
- `INTERNAL_ERROR` - Server error
## Rate Limiting
- Standard: 10 request/second/IP
- Standard: 10 requests/second/IP (general endpoints)
- Development (`-dev`): 20 requests/second/IP
- Registration: 5 requests/minute/IP
- Login: 10 requests/minute/IP
Exceeding limit returns 429 status.
Exceeding limit returns 429 status.
## JWT Token Format
Tokens are HS256-signed JWTs valid for 7 days. Include in Authorization header:
```
Authorization: Bearer <token>
```
Token claims include `sub` (user ID), `username`, `email`, and `exp` (expiration).

View File

@ -3,33 +3,57 @@
## Components
### Transport Layer (`internal/http`)
Fiber web server handling HTTP requests/responses. Implements routing, rate limiting, content-type validation, request parsing. Translates HTTP to internal Command objects.
Fiber web server handling HTTP requests/responses. Implements routing, rate limiting, content-type validation, JWT authentication middleware, request parsing. Translates HTTP to internal Command objects.
### Processing Layer (`internal/processor`)
Central command handler containing business logic. Single `Execute(Command)` entry point decouples transport from logic. Uses synchronous UCI engine for validation, asynchronous EngineQueue for computer moves.
Central command handler containing business logic. Single `Execute(Command)` entry point decouples transport from logic. Uses synchronous UCI engine for validation, asynchronous EngineQueue for computer moves. Commands include optional user context for authenticated operations.
### Service Layer (`internal/service`)
In-memory state storage without chess logic. Thread-safe game map protected by RWMutex. Manages game lifecycle, snapshots, and player configuration. Coordinates with storage layer for optional persistence.
In-memory state storage with authentication support. Thread-safe game map protected by RWMutex. Manages game lifecycle, snapshots, player configuration, user accounts, and JWT token generation. Coordinates with storage layer for persistence of both games and users.
### Storage Layer (`internal/storage`)
Optional SQLite persistence with async write pattern. Buffered channel (1000 ops) processes writes sequentially in background. Graceful degradation on write failures. WAL mode for development environments.
SQLite persistence with async writes for games, synchronous writes for authentication operations. Buffered channel (1000 ops) processes game writes sequentially in background. User operations use direct database access for consistency. Graceful degradation on write failures. WAL mode for development environments.
### Authentication Module (`internal/service/user.go`, `internal/http/auth.go`)
- **Password Hashing**: Argon2id for secure password storage
- **JWT Management**: HS256 tokens with 7-day expiration
- **User Operations**: Registration, login, profile management
- **Session Tracking**: Last login timestamps
### Supporting Modules
- **Engine** (`internal/engine`): UCI protocol wrapper for Stockfish process communication
- **Game** (`internal/game`): Game state with snapshot history
- **Game** (`internal/game`): Game state with snapshot history and player associations
- **Board** (`internal/board`): FEN parsing and ASCII generation
- **Core** (`internal/core`): Shared types, API models, error constants
- **CLI** (`cmd/chessd/cli`): Database management commands
- **CLI** (`cmd/chessd/cli`): Database and user management commands
## Request Flow
### Human Move
1. HTTP handler receives `POST /games/{id}/moves` with UCI move
2. Creates MakeMoveCommand, calls `processor.Execute()`
3. Processor validates move via locked validation engine
4. If legal, gets new FEN from engine
5. Calls `service.ApplyMove()` to update state
6. Returns GameResponse
### User Registration
1. HTTP handler receives `POST /auth/register` with credentials
2. Validates username format and password strength
3. Service layer hashes password with Argon2id
4. Creates user record with unique ID (collision detection)
5. Generates JWT token
6. Returns token and user information
### Authenticated Game Creation
1. HTTP handler receives `POST /games` with optional Bearer token
2. Middleware validates JWT if present
3. Creates CreateGameCommand with user ID context
4. Processor creates game with user ID for human players
5. Service associates game with authenticated user
6. Returns game with player IDs matching user
### Human Move (Authenticated)
1. HTTP handler receives `POST /games/{id}/moves` with move
2. Optional JWT validation for user verification
3. Creates MakeMoveCommand, calls `processor.Execute()`
4. Processor validates move via locked validation engine
5. If legal, gets new FEN from engine
6. Calls `service.ApplyMove()` to update state
7. Persists move with player identification
8. Returns GameResponse
### Computer Move
1. HTTP handler receives `POST /games/{id}/moves` with `{"move": "cccc"}`
@ -41,7 +65,14 @@ Optional SQLite persistence with async write pattern. Buffered channel (1000 ops
## Persistence Flow
### Write Operations
### User Write Operations (Synchronous)
1. Service layer calls storage method directly (CreateUser, UpdateUserPassword, etc.)
2. Operations use database transactions for consistency
3. Unique constraint checks within transaction
4. Immediate commit or rollback
5. Returns success or specific error (duplicate username, etc.)
### Game Write Operations (Asynchronous)
1. Service layer calls storage method (RecordNewGame, RecordMove, DeleteUndoneMoves)
2. Operation queued to buffered channel (non-blocking)
3. Writer goroutine processes queue sequentially
@ -49,9 +80,10 @@ Optional SQLite persistence with async write pattern. Buffered channel (1000 ops
5. Failures trigger degradation to memory-only mode
### Query Operations
1. CLI invokes Store.QueryGames with filters
1. CLI invokes Store.QueryGames or Store.GetUserByUsername with filters
2. Direct database read (no queue)
3. Results formatted as tabular output
3. Case-insensitive matching for usernames/emails
4. Results formatted as tabular output
## Concurrency
@ -59,43 +91,78 @@ Optional SQLite persistence with async write pattern. Buffered channel (1000 ops
- **Game State**: Single RWMutex protects game map (concurrent reads, serial writes)
- **Engine Workers**: Fixed pool (2 workers) with dedicated Stockfish processes
- **Validation Engine**: Single mutex-protected instance for synchronous validation
- **Storage Writer**: Single goroutine processes write queue sequentially
- **Storage Writer**: Single goroutine processes game write queue sequentially
- **User Operations**: Direct database access with transaction isolation
- **PID Lock**: File-based exclusive lock prevents multiple instances
## Data Structures
### Game Snapshot
### User Record
```go
type UserRecord struct {
UserID string
Username string
Email string
PasswordHash string
CreatedAt time.Time
LastLoginAt *time.Time
}
```
### Game Snapshot with User Context
```go
type Snapshot struct {
FEN string
PreviousMove string
NextTurnColor Color
PlayerID string
PlayerID string // User ID or generated UUID
}
```
### Command Pattern
Commands encapsulate operations with type and arguments, processed by single Execute method.
### JWT Claims
```go
{
"sub": "user-id",
"username": "alice",
"email": "alice@example.com",
"exp": 1234567890
}
```
### Command Pattern with User Context
Commands encapsulate operations with type, arguments, and optional user ID for authenticated requests.
### Player Configuration
Players identified by UUID, configured with type (human/computer), skill level, and search time.
Players identified by UUID (authenticated users) or generated IDs (anonymous), configured with type (human/computer), skill level, and search time.
### Storage Schema
```sql
-- User authentication table
users (
user_id TEXT PRIMARY KEY,
username TEXT UNIQUE NOT NULL COLLATE NOCASE,
email TEXT COLLATE NOCASE,
password_hash TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_login_at DATETIME
)
-- Game storage with player associations
games (
game_id TEXT PRIMARY KEY,
initial_fen TEXT,
white_player_id TEXT,
white_player_id TEXT, -- User ID or generated UUID
white_type INTEGER,
white_level INTEGER,
white_search_time INTEGER,
black_player_id TEXT,
black_player_id TEXT, -- User ID or generated UUID
black_type INTEGER,
black_level INTEGER,
black_search_time INTEGER,
start_time_utc DATETIME
)
-- Move history
moves (
move_id INTEGER PRIMARY KEY,
game_id TEXT,
@ -106,4 +173,26 @@ moves (
move_time_utc DATETIME,
FOREIGN KEY (game_id) REFERENCES games(game_id)
)
```
```
## Security Architecture
### Authentication Flow
1. Password validation enforces minimum complexity
2. Argon2id hashing prevents rainbow table attacks
3. JWT tokens expire after 7 days
4. Case-insensitive username/email matching prevents enumeration
5. Constant-time password verification prevents timing attacks
### Rate Limiting Strategy
- General API: 10 req/s per IP (20 in dev mode)
- Registration: 5 req/min per IP (prevent spam accounts)
- Login: 10 req/min per IP (prevent brute force)
- Game operations unaffected for authenticated users
### Data Protection
- Passwords never stored in plaintext
- JWT secret rotates on restart (or fixed in dev mode)
- User IDs use UUIDs with collision detection
- Transactions ensure data consistency
- Case-insensitive queries prevent duplicate accounts

View File

@ -4,13 +4,14 @@
- Go 1.24+
- Stockfish in PATH
- SQLite3
- Git
- curl, jq (for testing)
## Building
```bash
git clone https://git.lixen.com/lixen/chess
#git clone https://git.lixen.com/lixen/chess # Mirror
git clone https://github.com/lixenwraith/chess
cd chess
go build ./cmd/chessd
```
@ -23,65 +24,141 @@ go build ./cmd/chessd
- `-serve`: Enable embedded web UI server
- `-web-host`: Web UI server host (default: localhost)
- `-web-port`: Web UI server port (default: 9090)
- `-dev`: Development mode with relaxed rate limits
- `-storage-path`: SQLite database file path (enables persistence)
- `-dev`: Development mode with relaxed rate limits and fixed JWT secret
- `-storage-path`: SQLite database file path (enables persistence and authentication)
- `-pid`: PID file path for process tracking
- `-pid-lock`: Enable exclusive locking (requires -pid)
### Modes
```bash
# In-memory only
# In-memory only (no persistence or auth)
./chessd
# With persistence
# With persistence and authentication
./chessd -storage-path ./db/chess.db
# Singleton enforcement (requires same PID file path across instances)
./chessd -pid /var/run/chessd.pid -pid-lock
# Development with all features
./chessd -dev -storage-path chess.db -pid /tmp/chessd.pid
./chessd -dev -storage-path chess.db -pid /tmp/chessd.pid -serve
# Initialize database with user tables
./chessd db init -path chess.db
```
## Database Management
### CLI Commands
### Schema Initialization
```bash
# Initialize database schema
# Create all tables (users, games, moves)
./chessd db init -path chess.db
```
# Query games
./chessd db query -path chess.db [-gameId ID] [-playerId ID]
### User Management CLI
```bash
# Add user with password
./chessd db user add -path chess.db -username alice -password SecurePass123
# Delete database
# Add user with email
./chessd db user add -path chess.db -username bob -email bob@example.com -password BobPass456
# Interactive password input
./chessd db user add -path chess.db -username charlie -interactive
# List all users
./chessd db user list -path chess.db
# Update password
./chessd db user set-password -path chess.db -username alice -password NewPass789
# Update email
./chessd db user set-email -path chess.db -username alice -email newemail@example.com
# Update username
./chessd db user set-username -path chess.db -current alice -new alice2
# Import with existing Argon2 hash
./chessd db user set-hash -path chess.db -username alice -hash '$argon2id$v=19$m=65536,t=3,p=2$...'
# Delete user
./chessd db user delete -path chess.db -username alice
```
### Game Query CLI
```bash
# Query all games
./chessd db query -path chess.db -gameId "*"
# Query games for specific user
./chessd db query -path chess.db -playerId "550e8400-e29b-41d4-a716-446655440000"
# Query specific game
./chessd db query -path chess.db -gameId "a1b2c3d4-e5f6-7890-1234-567890abcdef"
# Delete database (destructive)
./chessd db delete -path chess.db
```
Query parameters accept `*` for all records (default if omitted) or specific IDs for filtering.
## Authentication Configuration
### JWT Secret Management
- **Production**: Cryptographically secure 32-byte secret generated on startup
- **Development** (`-dev`): Fixed secret for testing consistency
- **Sessions**: Valid for 7 days, renewed on each login
### Password Requirements
- Minimum 8 characters
- At least one letter and one number
- Argon2id hashing with secure defaults
### User Account Features
- Case-insensitive username and email matching
- Optional email addresses
- Last login tracking
- Unique constraint enforcement with transaction isolation
## Project Structure
```
chess/
├── cmd/chessd/
│ ├── main.go # Entry point
│ ├── main.go # Entry point with auth initialization
│ ├── pid.go # PID file management
│ └── cli/ # Database CLI
│ └── cli/ # Database and user CLI
├── internal/
│ ├── board/ # FEN/ASCII operations
│ ├── core/ # Shared types
│ ├── core/ # Shared types and API models
│ ├── engine/ # Stockfish UCI wrapper
│ ├── game/ # Game state
│ ├── http/ # Fiber handlers
│ ├── processor/ # Command processing
├── service/ # State management
│ ├── game/ # Game state with player associations
│ ├── http/ # Fiber handlers and auth endpoints
│ ├── handler.go # Game endpoints
│ ├── auth.go # Authentication endpoints
│ │ └── middleware.go # JWT validation
│ ├── processor/ # Command processing with user context
│ ├── service/ # State and user management
│ │ ├── service.go # Core service
│ │ ├── game.go # Game operations
│ │ └── user.go # User and auth operations
│ └── storage/ # SQLite persistence
│ ├── storage.go # Async writer for games
│ ├── game.go # Game persistence
│ ├── user.go # User persistence (synchronous)
│ └── schema.go # Database schema
└── test/ # Test scripts
```
## Testing
See [test documentation](../test/README.md) for details.
See [test documentation](../test/README.md) for comprehensive test suites covering API, authentication, and database operations.
### Quick Test Commands
```bash
# API functionality tests
./test/test-api.sh
# User authentication and database tests
./test/test-db.sh
# Run test server with sample users
./test/test-db-server.sh
```
## Configuration
@ -92,15 +169,30 @@ See [test documentation](../test/README.md) for details.
- Min search time: 100ms (internal/processor/processor.go)
- Write queue: 1000 operations (internal/storage/storage.go)
- DB connections: 25 max, 5 idle (internal/storage/storage.go)
- JWT expiration: 7 days (internal/service/user.go)
### Authentication Configuration
- Password minimum: 8 characters with letter and number
- Username format: 1-40 characters, alphanumeric and underscore
- Email validation: Standard RFC 5322 format
- Hash algorithm: Argon2id (memory-hard, side-channel resistant)
### Storage Configuration
- WAL mode enabled in development for concurrency
- Foreign key constraints enforced
- Async write pattern with 2-second drain on shutdown
- Async write pattern for games with 2-second drain on shutdown
- Synchronous writes for user operations (data consistency)
- Degradation to memory-only on write failures
- Case-insensitive collation for usernames and emails
### Rate Limiting Configuration
- General endpoints: 10 req/s (20 in dev mode)
- User registration: 5 req/min
- User login: 10 req/min
- Rate limit key: IP address from X-Forwarded-For or connection
### PID Management
- Singleton enforcement requires same PID file path - all instances must use the same -pid value
- Singleton enforcement requires same PID file path
- Stale PID detection via signal 0 checking
- Exclusive file locking with LOCK_EX|LOCK_NB
- Automatic cleanup on graceful shutdown
@ -111,10 +203,35 @@ See [test documentation](../test/README.md) for details.
- Search time: 100-10000ms
- UCI moves: 4-5 characters ([a-h][1-8][a-h][1-8][qrbn]?)
- Undo count: 1-300
- Username: 1-40 characters, [a-zA-Z0-9_]
- Password: 8-128 characters, requires letter and number
## Security Considerations
### Authentication Security
- Passwords hashed with Argon2id before storage
- JWT tokens signed with HS256
- Constant-time password comparison
- Case-insensitive matching prevents user enumeration
- Rate limiting on auth endpoints prevents brute force
### Input Validation
- All user inputs validated and sanitized
- SQL injection prevented via parameterized queries
- UCI command injection blocked via character validation
- FEN strings validated against strict regex pattern
### Session Management
- JWT tokens expire after 7 days
- No token refresh mechanism (re-login required)
- Tokens include minimal claims (user ID, username, email)
- Secret rotates on server restart (except dev mode)
## Limitations
- No persistence (memory only)
- Hardcoded Stockfish path
- Fixed worker pool size
- No game history beyond current session
- JWT tokens don't support refresh (must re-login after expiry)
- User deletion doesn't cascade to games (games remain with player IDs)
- No password recovery mechanism
- No email verification for registration
- Fixed worker pool size for engine calculations
- No real-time game updates (polling required)