716 lines
14 KiB
Markdown
716 lines
14 KiB
Markdown
# 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
|