inital
This commit is contained in:
715
API_DOCUMENTATION.md
Normal file
715
API_DOCUMENTATION.md
Normal file
@@ -0,0 +1,715 @@
|
||||
# API Documentation
|
||||
|
||||
Complete REST API documentation for the Feuerwehr Dashboard backend.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Base URL](#base-url)
|
||||
- [Authentication](#authentication)
|
||||
- [Rate Limiting](#rate-limiting)
|
||||
- [Response Format](#response-format)
|
||||
- [Error Codes](#error-codes)
|
||||
- [Health Check](#health-check)
|
||||
- [Authentication Endpoints](#authentication-endpoints)
|
||||
- [User Endpoints](#user-endpoints)
|
||||
- [Request Examples](#request-examples)
|
||||
|
||||
## Base URL
|
||||
|
||||
### Development
|
||||
```
|
||||
http://localhost:3000
|
||||
```
|
||||
|
||||
### Production
|
||||
```
|
||||
https://api.yourdomain.com
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
The API uses JWT (JSON Web Token) bearer authentication for protected endpoints.
|
||||
|
||||
### Authentication Header Format
|
||||
|
||||
```http
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
```
|
||||
|
||||
### How to Get Tokens
|
||||
|
||||
1. User authenticates via Authentik OAuth flow
|
||||
2. Frontend receives authorization code
|
||||
3. Frontend sends code to `/api/auth/callback`
|
||||
4. Backend returns `accessToken` and `refreshToken`
|
||||
5. Frontend includes `accessToken` in subsequent requests
|
||||
|
||||
### Token Expiration
|
||||
|
||||
- **Access Token**: 1 hour (3600 seconds)
|
||||
- **Refresh Token**: 24 hours (86400 seconds)
|
||||
|
||||
Use `/api/auth/refresh` endpoint to get a new access token before it expires.
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
All `/api/*` endpoints are rate-limited to prevent abuse.
|
||||
|
||||
### Limits
|
||||
|
||||
- **Window**: 15 minutes
|
||||
- **Max Requests**: 100 requests per IP
|
||||
|
||||
### Rate Limit Headers
|
||||
|
||||
```http
|
||||
RateLimit-Limit: 100
|
||||
RateLimit-Remaining: 95
|
||||
RateLimit-Reset: 1645564800
|
||||
```
|
||||
|
||||
### Rate Limit Exceeded Response
|
||||
|
||||
```http
|
||||
HTTP/1.1 429 Too Many Requests
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Too many requests from this IP, please try again later."
|
||||
}
|
||||
```
|
||||
|
||||
## Response Format
|
||||
|
||||
All API responses follow a consistent format:
|
||||
|
||||
### Success Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Operation completed successfully",
|
||||
"data": {
|
||||
// Response data here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Error description",
|
||||
"error": "Optional error details"
|
||||
}
|
||||
```
|
||||
|
||||
## Error Codes
|
||||
|
||||
### HTTP Status Codes
|
||||
|
||||
| Code | Meaning | Description |
|
||||
|------|---------|-------------|
|
||||
| 200 | OK | Request successful |
|
||||
| 201 | Created | Resource created successfully |
|
||||
| 400 | Bad Request | Invalid request parameters |
|
||||
| 401 | Unauthorized | Authentication required or failed |
|
||||
| 403 | Forbidden | Authenticated but not authorized |
|
||||
| 404 | Not Found | Resource not found |
|
||||
| 429 | Too Many Requests | Rate limit exceeded |
|
||||
| 500 | Internal Server Error | Server error occurred |
|
||||
|
||||
### Common Error Messages
|
||||
|
||||
```json
|
||||
// Missing authentication
|
||||
{
|
||||
"success": false,
|
||||
"message": "No token provided"
|
||||
}
|
||||
|
||||
// Invalid token
|
||||
{
|
||||
"success": false,
|
||||
"message": "Invalid or expired token"
|
||||
}
|
||||
|
||||
// Inactive user
|
||||
{
|
||||
"success": false,
|
||||
"message": "User account is inactive"
|
||||
}
|
||||
```
|
||||
|
||||
## Health Check
|
||||
|
||||
Check if the API is running and healthy.
|
||||
|
||||
### GET /health
|
||||
|
||||
**Authentication**: Not required
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
GET /health HTTP/1.1
|
||||
Host: api.yourdomain.com
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"timestamp": "2026-02-23T10:30:00.000Z",
|
||||
"uptime": 3600.5,
|
||||
"environment": "production"
|
||||
}
|
||||
```
|
||||
|
||||
**Response Fields**:
|
||||
- `status` (string): Always "ok" if server is running
|
||||
- `timestamp` (string): Current server time in ISO 8601 format
|
||||
- `uptime` (number): Server uptime in seconds
|
||||
- `environment` (string): Current environment (development/production)
|
||||
|
||||
## Authentication Endpoints
|
||||
|
||||
### POST /api/auth/callback
|
||||
|
||||
Handle OAuth callback and exchange authorization code for tokens.
|
||||
|
||||
**Authentication**: Not required
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"code": "authorization_code_from_authentik"
|
||||
}
|
||||
```
|
||||
|
||||
**Request Example**:
|
||||
```http
|
||||
POST /api/auth/callback HTTP/1.1
|
||||
Host: api.yourdomain.com
|
||||
Content-Type: application/json
|
||||
|
||||
```
|
||||
|
||||
**Success Response**:
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Authentication successful",
|
||||
"data": {
|
||||
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"user": {
|
||||
"id": 1,
|
||||
"email": "john.doe@feuerwehr.de",
|
||||
"name": "John Doe",
|
||||
"preferredUsername": "john.doe",
|
||||
"givenName": "John",
|
||||
"familyName": "Doe",
|
||||
"profilePictureUrl": "https://auth.example.com/media/avatars/john.jpg",
|
||||
"isActive": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 400 Bad Request
|
||||
```
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Authorization code is required"
|
||||
}
|
||||
```
|
||||
|
||||
```http
|
||||
HTTP/1.1 403 Forbidden
|
||||
```
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "User account is inactive"
|
||||
}
|
||||
```
|
||||
|
||||
```http
|
||||
HTTP/1.1 500 Internal Server Error
|
||||
```
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Authentication failed"
|
||||
}
|
||||
```
|
||||
|
||||
**Response Fields**:
|
||||
- `accessToken` (string): JWT access token for API authentication
|
||||
- `refreshToken` (string): JWT refresh token for getting new access tokens
|
||||
- `user` (object): User profile information
|
||||
- `id` (number): User database ID
|
||||
- `email` (string): User email address
|
||||
- `name` (string): Full name
|
||||
- `preferredUsername` (string): Preferred username
|
||||
- `givenName` (string): First name
|
||||
- `familyName` (string): Last name
|
||||
- `profilePictureUrl` (string): URL to profile picture
|
||||
- `isActive` (boolean): Whether user account is active
|
||||
|
||||
---
|
||||
|
||||
### POST /api/auth/refresh
|
||||
|
||||
Refresh an expired access token using a refresh token.
|
||||
|
||||
**Authentication**: Not required (uses refresh token)
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"refreshToken": "your_refresh_token"
|
||||
}
|
||||
```
|
||||
|
||||
**Request Example**:
|
||||
```http
|
||||
POST /api/auth/refresh HTTP/1.1
|
||||
Host: api.yourdomain.com
|
||||
Content-Type: application/json
|
||||
|
||||
```
|
||||
|
||||
**Success Response**:
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Token refreshed successfully",
|
||||
"data": {
|
||||
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 400 Bad Request
|
||||
```
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Refresh token is required"
|
||||
}
|
||||
```
|
||||
|
||||
```http
|
||||
HTTP/1.1 401 Unauthorized
|
||||
```
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Invalid refresh token"
|
||||
}
|
||||
```
|
||||
|
||||
```http
|
||||
HTTP/1.1 403 Forbidden
|
||||
```
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "User account is inactive"
|
||||
}
|
||||
```
|
||||
|
||||
**Response Fields**:
|
||||
- `accessToken` (string): New JWT access token
|
||||
|
||||
---
|
||||
|
||||
### POST /api/auth/logout
|
||||
|
||||
Logout the current user.
|
||||
|
||||
**Authentication**: Optional (for logging purposes)
|
||||
|
||||
**Request Headers**:
|
||||
```http
|
||||
Authorization: Bearer <access-token>
|
||||
```
|
||||
|
||||
**Request Example**:
|
||||
```http
|
||||
POST /api/auth/logout HTTP/1.1
|
||||
Host: api.yourdomain.com
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
**Success Response**:
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Logout successful"
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: Since this API uses stateless JWT authentication, logout is primarily handled client-side by discarding the tokens. This endpoint exists for audit logging purposes.
|
||||
|
||||
---
|
||||
|
||||
## User Endpoints
|
||||
|
||||
### GET /api/user/me
|
||||
|
||||
Get the currently authenticated user's profile.
|
||||
|
||||
**Authentication**: Required
|
||||
|
||||
**Request Headers**:
|
||||
```http
|
||||
Authorization: Bearer <access-token>
|
||||
```
|
||||
|
||||
**Request Example**:
|
||||
```http
|
||||
GET /api/user/me HTTP/1.1
|
||||
Host: api.yourdomain.com
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
**Success Response**:
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"email": "john.doe@feuerwehr.de",
|
||||
"name": "John Doe",
|
||||
"preferredUsername": "john.doe",
|
||||
"givenName": "John",
|
||||
"familyName": "Doe",
|
||||
"profilePictureUrl": "https://auth.example.com/media/avatars/john.jpg",
|
||||
"isActive": true,
|
||||
"lastLoginAt": "2026-02-23T10:30:00.000Z",
|
||||
"createdAt": "2026-01-01T12:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 401 Unauthorized
|
||||
```
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Not authenticated"
|
||||
}
|
||||
```
|
||||
|
||||
```http
|
||||
HTTP/1.1 404 Not Found
|
||||
```
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "User not found"
|
||||
}
|
||||
```
|
||||
|
||||
**Response Fields**:
|
||||
- `id` (number): User database ID
|
||||
- `email` (string): User email address
|
||||
- `name` (string): Full name
|
||||
- `preferredUsername` (string): Preferred username
|
||||
- `givenName` (string): First name
|
||||
- `familyName` (string): Last name
|
||||
- `profilePictureUrl` (string): URL to profile picture
|
||||
- `isActive` (boolean): Whether user account is active
|
||||
- `lastLoginAt` (string): Last login timestamp (ISO 8601)
|
||||
- `createdAt` (string): Account creation timestamp (ISO 8601)
|
||||
|
||||
---
|
||||
|
||||
## Request Examples
|
||||
|
||||
### Full Authentication Flow Example
|
||||
|
||||
#### Step 1: User Clicks Login
|
||||
|
||||
Frontend redirects to Authentik:
|
||||
```javascript
|
||||
const authentikAuthUrl = `https://auth.yourdomain.com/application/o/authorize/`;
|
||||
const params = new URLSearchParams({
|
||||
client_id: 'your_client_id',
|
||||
redirect_uri: 'https://dashboard.yourdomain.com/auth/callback',
|
||||
response_type: 'code',
|
||||
scope: 'openid profile email'
|
||||
});
|
||||
|
||||
window.location.href = `${authentikAuthUrl}?${params}`;
|
||||
```
|
||||
|
||||
#### Step 2: Authentik Redirects Back
|
||||
|
||||
After authentication, Authentik redirects to:
|
||||
```
|
||||
https://dashboard.yourdomain.com/auth/callback?code=abc123def456
|
||||
```
|
||||
|
||||
#### Step 3: Exchange Code for Tokens
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.yourdomain.com/api/auth/callback \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"code": "abc123def456"
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Authentication successful",
|
||||
"data": {
|
||||
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImVtYWlsIjoiam9obkBleGFtcGxlLmNvbSIsImlhdCI6MTcwODY5MTQwMCwiZXhwIjoxNzA4Njk1MDAwfQ.signature",
|
||||
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImVtYWlsIjoiam9obkBleGFtcGxlLmNvbSIsImlhdCI6MTcwODY5MTQwMCwiZXhwIjoxNzA4Nzc3ODAwfQ.signature",
|
||||
"user": {
|
||||
"id": 1,
|
||||
"email": "john.doe@feuerwehr.de",
|
||||
"name": "John Doe",
|
||||
"preferredUsername": "john.doe",
|
||||
"givenName": "John",
|
||||
"familyName": "Doe",
|
||||
"profilePictureUrl": null,
|
||||
"isActive": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 4: Access Protected Resources
|
||||
|
||||
```bash
|
||||
curl -X GET https://api.yourdomain.com/api/user/me \
|
||||
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
```
|
||||
|
||||
#### Step 5: Refresh Token When Expired
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.yourdomain.com/api/auth/refresh \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
}'
|
||||
```
|
||||
|
||||
### JavaScript/TypeScript Examples
|
||||
|
||||
#### Using Axios
|
||||
|
||||
```typescript
|
||||
import axios from 'axios';
|
||||
|
||||
const API_URL = 'https://api.yourdomain.com';
|
||||
|
||||
// Create axios instance with auth
|
||||
const api = axios.create({
|
||||
baseURL: API_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Add auth token to requests
|
||||
api.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('accessToken');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
// Handle authentication
|
||||
export const handleCallback = async (code: string) => {
|
||||
const response = await api.post('/api/auth/callback', { code });
|
||||
const { accessToken, refreshToken, user } = response.data.data;
|
||||
|
||||
// Store tokens
|
||||
localStorage.setItem('accessToken', accessToken);
|
||||
localStorage.setItem('refreshToken', refreshToken);
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
// Refresh token
|
||||
export const refreshAccessToken = async () => {
|
||||
const refreshToken = localStorage.getItem('refreshToken');
|
||||
const response = await api.post('/api/auth/refresh', { refreshToken });
|
||||
const { accessToken } = response.data.data;
|
||||
|
||||
localStorage.setItem('accessToken', accessToken);
|
||||
return accessToken;
|
||||
};
|
||||
|
||||
// Get current user
|
||||
export const getCurrentUser = async () => {
|
||||
const response = await api.get('/api/user/me');
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
// Logout
|
||||
export const logout = async () => {
|
||||
await api.post('/api/auth/logout');
|
||||
localStorage.removeItem('accessToken');
|
||||
localStorage.removeItem('refreshToken');
|
||||
};
|
||||
```
|
||||
|
||||
### cURL Examples
|
||||
|
||||
#### Health Check
|
||||
```bash
|
||||
curl -X GET https://api.yourdomain.com/health
|
||||
```
|
||||
|
||||
#### Login Callback
|
||||
```bash
|
||||
curl -X POST https://api.yourdomain.com/api/auth/callback \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"code":"your_auth_code"}'
|
||||
```
|
||||
|
||||
#### Get Current User
|
||||
```bash
|
||||
curl -X GET https://api.yourdomain.com/api/user/me \
|
||||
-H "Authorization: Bearer your_access_token"
|
||||
```
|
||||
|
||||
#### Refresh Token
|
||||
```bash
|
||||
curl -X POST https://api.yourdomain.com/api/auth/refresh \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"refreshToken":"your_refresh_token"}'
|
||||
```
|
||||
|
||||
#### Logout
|
||||
```bash
|
||||
curl -X POST https://api.yourdomain.com/api/auth/logout \
|
||||
-H "Authorization: Bearer your_access_token"
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### HTTPS Required in Production
|
||||
|
||||
Always use HTTPS for API requests in production to protect tokens and sensitive data.
|
||||
|
||||
### Token Storage
|
||||
|
||||
- **Access Token**: Store in memory or sessionStorage (more secure)
|
||||
- **Refresh Token**: Can be stored in httpOnly cookie or localStorage
|
||||
- **Never**: Store tokens in unencrypted cookies or URL parameters
|
||||
|
||||
### CORS Configuration
|
||||
|
||||
The API is configured to only accept requests from allowed origins:
|
||||
|
||||
```javascript
|
||||
// Backend CORS configuration
|
||||
cors({
|
||||
origin: process.env.CORS_ORIGIN, // e.g., https://dashboard.yourdomain.com
|
||||
credentials: true
|
||||
})
|
||||
```
|
||||
|
||||
Ensure `CORS_ORIGIN` environment variable matches your frontend URL exactly.
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
Respect rate limits to avoid being temporarily blocked. Implement exponential backoff for failed requests.
|
||||
|
||||
## Additional Notes
|
||||
|
||||
### Timestamps
|
||||
|
||||
All timestamps are in ISO 8601 format with UTC timezone:
|
||||
```
|
||||
2026-02-23T10:30:00.000Z
|
||||
```
|
||||
|
||||
### JSON Format
|
||||
|
||||
All request bodies and responses use JSON format with `Content-Type: application/json`.
|
||||
|
||||
### Versioning
|
||||
|
||||
API versioning is currently not implemented. All endpoints are under `/api/` prefix.
|
||||
|
||||
Future versions may use:
|
||||
- `/api/v1/` for version 1
|
||||
- `/api/v2/` for version 2
|
||||
|
||||
## Support
|
||||
|
||||
For API support:
|
||||
|
||||
1. Check this documentation
|
||||
2. Review [DEVELOPMENT.md](DEVELOPMENT.md) for debugging tips
|
||||
3. Check backend logs for detailed error messages
|
||||
4. Consult [ARCHITECTURE.md](ARCHITECTURE.md) for system design
|
||||
5. Create an issue with detailed request/response information
|
||||
|
||||
## Changelog
|
||||
|
||||
### Version 1.0.0 (2026-02-23)
|
||||
|
||||
Initial API release:
|
||||
- OAuth 2.0 authentication with Authentik
|
||||
- JWT-based session management
|
||||
- User profile endpoints
|
||||
- Health check endpoint
|
||||
- Rate limiting
|
||||
- Security headers
|
||||
- OAuth 2.0 authentication with Authentik
|
||||
- JWT-based session management
|
||||
- User profile endpoints
|
||||
- Health check endpoint
|
||||
- Rate limiting
|
||||
- Security headers
|
||||
Reference in New Issue
Block a user