security improvements
This commit is contained in:
822
documentation/API.md
Normal file
822
documentation/API.md
Normal file
@@ -0,0 +1,822 @@
|
||||
# API Documentation for ACC Server Manager
|
||||
|
||||
## Overview
|
||||
|
||||
The ACC Server Manager provides a comprehensive REST API for managing Assetto Corsa Competizione dedicated servers. This API enables full control over server instances, configurations, user management, and monitoring through HTTP endpoints.
|
||||
|
||||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:3000/api/v1
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
All API endpoints (except public ones) require authentication via JWT tokens.
|
||||
|
||||
### Authentication Header
|
||||
```http
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
```
|
||||
|
||||
### Token Expiration
|
||||
- Default token lifetime: 24 hours
|
||||
- Tokens should be refreshed before expiration
|
||||
- Failed authentication returns HTTP 401
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
The API implements multiple layers of rate limiting:
|
||||
|
||||
- **Global**: 100 requests per minute per IP
|
||||
- **Authentication**: 5 attempts per 15 minutes per IP
|
||||
- **API Endpoints**: 60 requests per minute per IP
|
||||
|
||||
Rate limit exceeded responses return HTTP 429 with retry information.
|
||||
|
||||
## Response Format
|
||||
|
||||
All API responses follow a consistent JSON format:
|
||||
|
||||
### Success Response
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
// Response data
|
||||
},
|
||||
"message": "Operation completed successfully"
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "ERROR_CODE",
|
||||
"message": "Human readable error message",
|
||||
"details": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## HTTP Status Codes
|
||||
|
||||
| Status Code | Description |
|
||||
|-------------|-------------|
|
||||
| 200 | OK - Request successful |
|
||||
| 201 | Created - Resource created successfully |
|
||||
| 400 | Bad Request - Invalid request data |
|
||||
| 401 | Unauthorized - Authentication required |
|
||||
| 403 | Forbidden - Insufficient permissions |
|
||||
| 404 | Not Found - Resource not found |
|
||||
| 409 | Conflict - Resource already exists |
|
||||
| 422 | Unprocessable Entity - Validation failed |
|
||||
| 429 | Too Many Requests - Rate limit exceeded |
|
||||
| 500 | Internal Server Error - Server error |
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Authentication
|
||||
|
||||
#### Login
|
||||
```http
|
||||
POST /api/v1/auth/login
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"username": "string",
|
||||
"password": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"token": "jwt-token-string",
|
||||
"user": {
|
||||
"id": "uuid",
|
||||
"username": "string",
|
||||
"role": {
|
||||
"id": "uuid",
|
||||
"name": "string",
|
||||
"permissions": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Register User
|
||||
```http
|
||||
POST /api/v1/auth/register
|
||||
```
|
||||
*Requires: `user.create` permission*
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"username": "string",
|
||||
"password": "string",
|
||||
"roleId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Current User
|
||||
```http
|
||||
GET /api/v1/auth/me
|
||||
```
|
||||
*Requires: Authentication*
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "uuid",
|
||||
"username": "string",
|
||||
"role": {
|
||||
"id": "uuid",
|
||||
"name": "string",
|
||||
"permissions": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Server Management
|
||||
|
||||
#### List Servers
|
||||
```http
|
||||
GET /api/v1/servers
|
||||
```
|
||||
*Requires: `server.read` permission*
|
||||
|
||||
**Query Parameters:**
|
||||
- `page` (integer): Page number (default: 1)
|
||||
- `limit` (integer): Items per page (default: 10)
|
||||
- `search` (string): Search term
|
||||
- `status` (string): Filter by status (running, stopped, error)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"servers": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "string",
|
||||
"ip": "string",
|
||||
"port": 9600,
|
||||
"path": "string",
|
||||
"serviceName": "string",
|
||||
"status": "string",
|
||||
"dateCreated": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 10,
|
||||
"total": 50,
|
||||
"pages": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Create Server
|
||||
```http
|
||||
POST /api/v1/servers
|
||||
```
|
||||
*Requires: `server.create` permission*
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"name": "string",
|
||||
"ip": "string",
|
||||
"port": 9600,
|
||||
"path": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "string",
|
||||
"ip": "string",
|
||||
"port": 9600,
|
||||
"path": "string",
|
||||
"serviceName": "string",
|
||||
"status": "created",
|
||||
"dateCreated": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Server Details
|
||||
```http
|
||||
GET /api/v1/servers/{id}
|
||||
```
|
||||
*Requires: `server.read` permission*
|
||||
|
||||
**Path Parameters:**
|
||||
- `id` (integer): Server ID
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "string",
|
||||
"ip": "string",
|
||||
"port": 9600,
|
||||
"path": "string",
|
||||
"serviceName": "string",
|
||||
"status": "string",
|
||||
"dateCreated": "2024-01-01T00:00:00Z",
|
||||
"configs": [],
|
||||
"statistics": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Update Server
|
||||
```http
|
||||
PUT /api/v1/servers/{id}
|
||||
```
|
||||
*Requires: `server.update` permission*
|
||||
|
||||
**Path Parameters:**
|
||||
- `id` (integer): Server ID
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"name": "string",
|
||||
"ip": "string",
|
||||
"port": 9600,
|
||||
"path": "string"
|
||||
}
|
||||
```
|
||||
|
||||
#### Delete Server
|
||||
```http
|
||||
DELETE /api/v1/servers/{id}
|
||||
```
|
||||
*Requires: `server.delete` permission*
|
||||
|
||||
**Path Parameters:**
|
||||
- `id` (integer): Server ID
|
||||
|
||||
#### Start Server
|
||||
```http
|
||||
POST /api/v1/servers/{id}/start
|
||||
```
|
||||
*Requires: `server.control` permission*
|
||||
|
||||
#### Stop Server
|
||||
```http
|
||||
POST /api/v1/servers/{id}/stop
|
||||
```
|
||||
*Requires: `server.control` permission*
|
||||
|
||||
#### Restart Server
|
||||
```http
|
||||
POST /api/v1/servers/{id}/restart
|
||||
```
|
||||
*Requires: `server.control` permission*
|
||||
|
||||
### Configuration Management
|
||||
|
||||
#### Get Configuration File
|
||||
```http
|
||||
GET /api/v1/servers/{id}/config/{file}
|
||||
```
|
||||
*Requires: `config.read` permission*
|
||||
|
||||
**Path Parameters:**
|
||||
- `id` (integer): Server ID
|
||||
- `file` (string): Configuration file name (configuration, event, eventRules, settings)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"file": "configuration",
|
||||
"content": {},
|
||||
"lastModified": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Update Configuration File
|
||||
```http
|
||||
PUT /api/v1/servers/{id}/config/{file}
|
||||
```
|
||||
*Requires: `config.update` permission*
|
||||
|
||||
**Path Parameters:**
|
||||
- `id` (integer): Server ID
|
||||
- `file` (string): Configuration file name
|
||||
|
||||
**Query Parameters:**
|
||||
- `restart` (boolean): Restart server after update (default: false)
|
||||
- `override` (boolean): Override validation warnings (default: false)
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"tcpPort": 9600,
|
||||
"udpPort": 9600,
|
||||
"maxConnections": 30,
|
||||
"registerToLobby": 1,
|
||||
"serverName": "My ACC Server",
|
||||
"password": "",
|
||||
"adminPassword": "admin123",
|
||||
"trackMedalsRequirement": 0,
|
||||
"safetyRatingRequirement": -1,
|
||||
"racecraftRatingRequirement": -1,
|
||||
"configVersion": 1
|
||||
}
|
||||
```
|
||||
|
||||
#### Validate Configuration
|
||||
```http
|
||||
POST /api/v1/servers/{id}/config/{file}/validate
|
||||
```
|
||||
*Requires: `config.read` permission*
|
||||
|
||||
**Request Body:** Configuration object to validate
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"valid": true,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Steam Integration
|
||||
|
||||
#### Get Steam Credentials
|
||||
```http
|
||||
GET /api/v1/steam/credentials
|
||||
```
|
||||
*Requires: `steam.read` permission*
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"username": "steam_username",
|
||||
"dateCreated": "2024-01-01T00:00:00Z",
|
||||
"lastUpdated": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Update Steam Credentials
|
||||
```http
|
||||
PUT /api/v1/steam/credentials
|
||||
```
|
||||
*Requires: `steam.update` permission*
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"username": "steam_username",
|
||||
"password": "steam_password"
|
||||
}
|
||||
```
|
||||
|
||||
#### Install/Update Server
|
||||
```http
|
||||
POST /api/v1/steam/install
|
||||
```
|
||||
*Requires: `steam.install` permission*
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"serverId": 1,
|
||||
"validate": true,
|
||||
"beta": false
|
||||
}
|
||||
```
|
||||
|
||||
### User Management
|
||||
|
||||
#### List Users
|
||||
```http
|
||||
GET /api/v1/users
|
||||
```
|
||||
*Requires: `user.read` permission*
|
||||
|
||||
**Query Parameters:**
|
||||
- `page` (integer): Page number
|
||||
- `limit` (integer): Items per page
|
||||
- `search` (string): Search term
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"users": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"username": "string",
|
||||
"role": {
|
||||
"id": "uuid",
|
||||
"name": "string"
|
||||
},
|
||||
"createdAt": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
],
|
||||
"pagination": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Create User
|
||||
```http
|
||||
POST /api/v1/users
|
||||
```
|
||||
*Requires: `user.create` permission*
|
||||
|
||||
#### Update User
|
||||
```http
|
||||
PUT /api/v1/users/{id}
|
||||
```
|
||||
*Requires: `user.update` permission*
|
||||
|
||||
#### Delete User
|
||||
```http
|
||||
DELETE /api/v1/users/{id}
|
||||
```
|
||||
*Requires: `user.delete` permission*
|
||||
|
||||
### Role and Permission Management
|
||||
|
||||
#### List Roles
|
||||
```http
|
||||
GET /api/v1/roles
|
||||
```
|
||||
*Requires: `role.read` permission*
|
||||
|
||||
#### Create Role
|
||||
```http
|
||||
POST /api/v1/roles
|
||||
```
|
||||
*Requires: `role.create` permission*
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"permissions": ["permission1", "permission2"]
|
||||
}
|
||||
```
|
||||
|
||||
#### List Permissions
|
||||
```http
|
||||
GET /api/v1/permissions
|
||||
```
|
||||
*Requires: `permission.read` permission*
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"name": "server.create",
|
||||
"description": "Create new servers",
|
||||
"category": "server"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Monitoring and Analytics
|
||||
|
||||
#### Get Server Statistics
|
||||
```http
|
||||
GET /api/v1/servers/{id}/stats
|
||||
```
|
||||
*Requires: `stats.read` permission*
|
||||
|
||||
**Query Parameters:**
|
||||
- `from` (string): Start date (ISO 8601)
|
||||
- `to` (string): End date (ISO 8601)
|
||||
- `granularity` (string): hour, day, week, month
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"totalPlaytime": 3600,
|
||||
"playerCount": [],
|
||||
"sessionTypes": [],
|
||||
"dailyActivity": [],
|
||||
"recentSessions": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Get System Health
|
||||
```http
|
||||
GET /api/v1/system/health
|
||||
```
|
||||
*Public endpoint*
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"status": "healthy",
|
||||
"version": "1.0.0",
|
||||
"uptime": 3600,
|
||||
"database": "connected",
|
||||
"services": {
|
||||
"steam": "available",
|
||||
"nssm": "available"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Lookup Data
|
||||
|
||||
#### Get Tracks
|
||||
```http
|
||||
GET /api/v1/lookup/tracks
|
||||
```
|
||||
*Public endpoint*
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"name": "monza",
|
||||
"uniquePitBoxes": 29,
|
||||
"privateServerSlots": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Car Models
|
||||
```http
|
||||
GET /api/v1/lookup/cars
|
||||
```
|
||||
*Public endpoint*
|
||||
|
||||
#### Get Driver Categories
|
||||
```http
|
||||
GET /api/v1/lookup/driver-categories
|
||||
```
|
||||
*Public endpoint*
|
||||
|
||||
#### Get Cup Categories
|
||||
```http
|
||||
GET /api/v1/lookup/cup-categories
|
||||
```
|
||||
*Public endpoint*
|
||||
|
||||
#### Get Session Types
|
||||
```http
|
||||
GET /api/v1/lookup/session-types
|
||||
```
|
||||
*Public endpoint*
|
||||
|
||||
## Webhooks
|
||||
|
||||
The API supports webhook notifications for server events:
|
||||
|
||||
### Server Status Changes
|
||||
```json
|
||||
{
|
||||
"event": "server.status.changed",
|
||||
"serverId": 1,
|
||||
"serverName": "My Server",
|
||||
"oldStatus": "stopped",
|
||||
"newStatus": "running",
|
||||
"timestamp": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Updates
|
||||
```json
|
||||
{
|
||||
"event": "server.config.updated",
|
||||
"serverId": 1,
|
||||
"serverName": "My Server",
|
||||
"configFile": "configuration",
|
||||
"userId": "uuid",
|
||||
"timestamp": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Codes
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| `AUTH_REQUIRED` | Authentication required |
|
||||
| `AUTH_INVALID` | Invalid credentials |
|
||||
| `AUTH_EXPIRED` | Token expired |
|
||||
| `PERMISSION_DENIED` | Insufficient permissions |
|
||||
| `VALIDATION_FAILED` | Request validation failed |
|
||||
| `RESOURCE_NOT_FOUND` | Requested resource not found |
|
||||
| `RESOURCE_EXISTS` | Resource already exists |
|
||||
| `RATE_LIMIT_EXCEEDED` | Rate limit exceeded |
|
||||
| `SERVER_ERROR` | Internal server error |
|
||||
| `SERVICE_UNAVAILABLE` | External service unavailable |
|
||||
|
||||
## SDK Examples
|
||||
|
||||
### JavaScript/Node.js
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
class ACCServerManagerAPI {
|
||||
constructor(baseUrl, token) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.token = token;
|
||||
this.client = axios.create({
|
||||
baseURL: baseUrl,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getServers() {
|
||||
const response = await this.client.get('/servers');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async createServer(serverData) {
|
||||
const response = await this.client.post('/servers', serverData);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async updateConfig(serverId, file, config, restart = false) {
|
||||
const response = await this.client.put(
|
||||
`/servers/${serverId}/config/${file}?restart=${restart}`,
|
||||
config
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const api = new ACCServerManagerAPI('http://localhost:3000/api/v1', 'your-jwt-token');
|
||||
const servers = await api.getServers();
|
||||
```
|
||||
|
||||
### Python
|
||||
```python
|
||||
import requests
|
||||
|
||||
class ACCServerManagerAPI:
|
||||
def __init__(self, base_url, token):
|
||||
self.base_url = base_url
|
||||
self.headers = {
|
||||
'Authorization': f'Bearer {token}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
def get_servers(self):
|
||||
response = requests.get(f'{self.base_url}/servers', headers=self.headers)
|
||||
return response.json()
|
||||
|
||||
def create_server(self, server_data):
|
||||
response = requests.post(
|
||||
f'{self.base_url}/servers',
|
||||
json=server_data,
|
||||
headers=self.headers
|
||||
)
|
||||
return response.json()
|
||||
|
||||
# Usage
|
||||
api = ACCServerManagerAPI('http://localhost:3000/api/v1', 'your-jwt-token')
|
||||
servers = api.get_servers()
|
||||
```
|
||||
|
||||
### Go
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ACCServerManagerAPI struct {
|
||||
BaseURL string
|
||||
Token string
|
||||
Client *http.Client
|
||||
}
|
||||
|
||||
func NewACCServerManagerAPI(baseURL, token string) *ACCServerManagerAPI {
|
||||
return &ACCServerManagerAPI{
|
||||
BaseURL: baseURL,
|
||||
Token: token,
|
||||
Client: &http.Client{},
|
||||
}
|
||||
}
|
||||
|
||||
func (api *ACCServerManagerAPI) request(method, endpoint string, body interface{}) (*http.Response, error) {
|
||||
var reqBody bytes.Buffer
|
||||
if body != nil {
|
||||
json.NewEncoder(&reqBody).Encode(body)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, api.BaseURL+endpoint, &reqBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+api.Token)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
return api.Client.Do(req)
|
||||
}
|
||||
|
||||
func (api *ACCServerManagerAPI) GetServers() (interface{}, error) {
|
||||
resp, err := api.request("GET", "/servers", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result interface{}
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
return result, nil
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Authentication
|
||||
1. Store JWT tokens securely (httpOnly cookies for web apps)
|
||||
2. Implement token refresh mechanism
|
||||
3. Handle authentication errors gracefully
|
||||
4. Use HTTPS in production
|
||||
|
||||
### Rate Limiting
|
||||
1. Implement exponential backoff for rate-limited requests
|
||||
2. Cache responses when appropriate
|
||||
3. Use batch operations when available
|
||||
4. Monitor rate limit headers
|
||||
|
||||
### Error Handling
|
||||
1. Always check response status codes
|
||||
2. Handle network errors gracefully
|
||||
3. Implement retry logic for transient errors
|
||||
4. Log errors for debugging
|
||||
|
||||
### Performance
|
||||
1. Use pagination for large datasets
|
||||
2. Implement client-side caching
|
||||
3. Use WebSockets for real-time updates
|
||||
4. Compress request/response bodies
|
||||
|
||||
## Support
|
||||
|
||||
For API support:
|
||||
- **Documentation**: Check this guide and interactive Swagger UI
|
||||
- **Issues**: Report API bugs via GitHub Issues
|
||||
- **Community**: Join community discussions for help
|
||||
- **Professional Support**: Contact maintainers for enterprise support
|
||||
|
||||
---
|
||||
|
||||
**Note**: This API is versioned. Breaking changes will result in a new API version. Always specify the version in your requests.
|
||||
Reference in New Issue
Block a user