This commit is contained in:
Matthias Hochmeister
2026-02-27 14:02:03 +01:00
parent 04d4f89834
commit 1c6c59c199
10 changed files with 76 additions and 66 deletions

View File

@@ -87,9 +87,9 @@ JWT_SECRET=your_jwt_secret_here
# The frontend URL that is allowed to make requests to the backend
# IMPORTANT: Must match your frontend URL exactly!
# Development: http://localhost:5173 (Vite dev server)
# Production: https://dashboard.yourdomain.com
# Production: https://start.feuerwehr-rems.at
# Multiple origins: Use comma-separated values (if supported by your setup)
CORS_ORIGIN=http://localhost:80
CORS_ORIGIN=https://start.feuerwehr-rems.at
# ============================================================================
# FRONTEND CONFIGURATION
@@ -103,16 +103,16 @@ FRONTEND_PORT=80
# API URL for frontend
# The URL where the frontend will send API requests
# Development: http://localhost:3000
# Production: https://api.yourdomain.com
# Production: https://start.feuerwehr-rems.at (proxied via nginx /api/)
# IMPORTANT: Must be accessible from the user's browser!
VITE_API_URL=http://localhost:3000
VITE_API_URL=https://start.feuerwehr-rems.at
# Authentik URL for frontend
# The base URL of your Authentik instance (without application path)
# Development: http://localhost:9000
# Production: https://auth.yourdomain.com
# Production: https://auth.firesuite.feuerwehr-rems.at
# IMPORTANT: Used for OAuth redirect URL construction
VITE_AUTHENTIK_URL=https://auth.yourdomain.com
VITE_AUTHENTIK_URL=https://auth.firesuite.feuerwehr-rems.at
# ============================================================================
# AUTHENTIK OAUTH CONFIGURATION
@@ -133,18 +133,18 @@ AUTHENTIK_CLIENT_SECRET=your_client_secret_here
# OAuth Issuer URL
# From Authentik: Applications → Providers → Your Provider → OpenID Configuration
# Format: https://auth.yourdomain.com/application/o/your-app-slug/
# Format: https://auth.firesuite.feuerwehr-rems.at/application/o/your-app-slug/
# IMPORTANT: Must end with a trailing slash (/)
# Development: http://localhost:9000/application/o/feuerwehr-dashboard/
# Production: https://auth.yourdomain.com/application/o/feuerwehr-dashboard/
AUTHENTIK_ISSUER=https://auth.yourdomain.com/application/o/feuerwehr-dashboard/
# Production: https://auth.firesuite.feuerwehr-rems.at/application/o/feuerwehr-dashboard/
AUTHENTIK_ISSUER=https://auth.firesuite.feuerwehr-rems.at/application/o/feuerwehr-dashboard/
# OAuth Redirect URI
# The URL where Authentik will redirect after successful authentication
# Must match EXACTLY what you configured in Authentik
# Development: http://localhost:5173/auth/callback
# Production: https://dashboard.yourdomain.com/auth/callback
AUTHENTIK_REDIRECT_URI=https://dashboard.yourdomain.com/auth/callback
# Production: https://start.feuerwehr-rems.at/auth/callback
AUTHENTIK_REDIRECT_URI=https://start.feuerwehr-rems.at/auth/callback
# OAuth Scopes (optional, has defaults)
# Default: openid profile email
@@ -227,13 +227,13 @@ AUTHENTIK_REDIRECT_URI=https://dashboard.yourdomain.com/auth/callback
# BACKEND_PORT=3000
# NODE_ENV=production
# JWT_SECRET=<generated-with-openssl-rand-base64-32>
# CORS_ORIGIN=https://dashboard.yourdomain.com
# CORS_ORIGIN=https://start.feuerwehr-rems.at
# FRONTEND_PORT=80
# VITE_API_URL=https://api.yourdomain.com
# VITE_API_URL=https://start.feuerwehr-rems.at
# AUTHENTIK_CLIENT_ID=<from-authentik>
# AUTHENTIK_CLIENT_SECRET=<from-authentik>
# AUTHENTIK_ISSUER=https://auth.yourdomain.com/application/o/feuerwehr-dashboard/
# AUTHENTIK_REDIRECT_URI=https://dashboard.yourdomain.com/auth/callback
# AUTHENTIK_ISSUER=https://auth.firesuite.feuerwehr-rems.at/application/o/feuerwehr-dashboard/
# AUTHENTIK_REDIRECT_URI=https://start.feuerwehr-rems.at/auth/callback
# LOG_LEVEL=info
#
# ============================================================================

View File

@@ -23,7 +23,7 @@ http://localhost:3000
### Production
```
https://api.yourdomain.com
https://start.feuerwehr-rems.at
```
## Authentication
@@ -155,7 +155,7 @@ Check if the API is running and healthy.
**Request**:
```http
GET /health HTTP/1.1
Host: api.yourdomain.com
Host: start.feuerwehr-rems.at
```
**Response**:
@@ -197,7 +197,7 @@ Handle OAuth callback and exchange authorization code for tokens.
**Request Example**:
```http
POST /api/auth/callback HTTP/1.1
Host: api.yourdomain.com
Host: start.feuerwehr-rems.at
Content-Type: application/json
```
@@ -295,7 +295,7 @@ Refresh an expired access token using a refresh token.
Host: start.feuerwehr-rems.at
Content-Type: application/json
```
```
**Success Response**:
```http
@@ -370,7 +370,7 @@ Authorization: Bearer <access-token>
**Success Response**:
```http
HTTP/1.1 200 OK
HTTP/1.1 200 OK
Content-Type: application/json
```
@@ -407,7 +407,7 @@ Authorization: Bearer <access-token>
**Success Response**:
```http
HTTP/1.1 200 OK
HTTP/1.1 200 OK
Content-Type: application/json
```
@@ -479,10 +479,10 @@ HTTP/1.1 404 Not Found
redirect_uri: 'https://start.feuerwehr-rems.at/auth/callback',
response_type: 'code',
scope: 'openid profile email'
});
});
window.location.href = `${authentikAuthUrl}?${params}`;
```
```
#### Step 2: Authentik Redirects Back
@@ -494,13 +494,13 @@ window.location.href = `${authentikAuthUrl}?${params}`;
#### Step 3: Exchange Code for Tokens
```bash
curl -X POST https://api.yourdomain.com/api/auth/callback \
curl -X POST https://start.feuerwehr-rems.at/api/auth/callback \
-H "Content-Type: application/json" \
-d '{
"code": "abc123def456"
}'
```
Response:
```json
{
@@ -532,14 +532,14 @@ Response:
#### Step 5: Refresh Token When Expired
```bash
```bash
curl -X POST https://start.feuerwehr-rems.at/api/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}'
```
### JavaScript/TypeScript Examples
#### Using Axios
@@ -553,7 +553,7 @@ curl -X POST https://api.yourdomain.com/api/auth/refresh \
const api = axios.create({
baseURL: API_URL,
headers: {
'Content-Type': 'application/json',
'Content-Type': 'application/json',
},
});
@@ -612,32 +612,32 @@ export const logout = async () => {
#### Login Callback
```bash
curl -X POST https://start.feuerwehr-rems.at/api/auth/callback \
-H "Content-Type: application/json" \
-H "Content-Type: application/json" \
-d '{"code":"your_auth_code"}'
```
#### Get Current User
```bash
```bash
curl -X GET https://start.feuerwehr-rems.at/api/user/me \
-H "Authorization: Bearer your_access_token"
```
#### Refresh Token
```bash
curl -X POST https://api.yourdomain.com/api/auth/refresh \
curl -X POST https://start.feuerwehr-rems.at/api/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refreshToken":"your_refresh_token"}'
```
#### Logout
```bash
```bash
curl -X POST https://start.feuerwehr-rems.at/api/auth/logout \
-H "Authorization: Bearer your_access_token"
```
## Security Considerations
### HTTPS Required in Production
### HTTPS Required in Production
Always use HTTPS for API requests in production to protect tokens and sensitive data.
@@ -660,7 +660,7 @@ The API is configured to only accept requests from allowed origins:
```
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.

View File

@@ -21,8 +21,8 @@ Before you begin, you need:
- An Authentik instance (self-hosted or cloud)
- Admin access to Authentik
- Your Feuerwehr Dashboard URL (e.g., `https://dashboard.yourdomain.com`)
- Your backend API URL (e.g., `https://api.yourdomain.com`)
- Your Feuerwehr Dashboard URL (e.g., `https://start.feuerwehr-rems.at`)
- Your backend API URL (e.g., `https://start.feuerwehr-rems.at`)
## Authentik Installation
@@ -146,7 +146,7 @@ Protocol Settings:
```
http://localhost:5173/auth/callback
http://localhost/auth/callback
https://dashboard.yourdomain.com/auth/callback
https://start.feuerwehr-rems.at/auth/callback
```
Add one URI per line. Include all environments (development, staging, production).
@@ -173,7 +173,7 @@ Configure the application:
Name: Feuerwehr Dashboard
Slug: feuerwehr-dashboard
Provider: Feuerwehr Dashboard Provider (select from dropdown)
Launch URL: https://dashboard.yourdomain.com
Launch URL: https://start.feuerwehr-rems.at
```
**UI Settings** (optional):
@@ -256,10 +256,10 @@ This is the Vite dev server URL.
### Production Environment
```
https://dashboard.yourdomain.com/auth/callback
https://start.feuerwehr-rems.at/auth/callback
```
Replace `yourdomain.com` with your actual domain.
Replace `feuerwehr-rems.at` with your actual domain.
### Docker Local Testing
@@ -317,11 +317,11 @@ const scopes = 'openid profile email';
1. In the provider details, find **OpenID Configuration URL**:
```
https://auth.yourdomain.com/application/o/feuerwehr-dashboard/.well-known/openid-configuration
https://auth.firesuite.feuerwehr-rems.at/application/o/feuerwehr-dashboard/.well-known/openid-configuration
```
2. Important URLs from this configuration:
- **Issuer**: `https://auth.yourdomain.com/application/o/feuerwehr-dashboard/`
- **Issuer**: `https://auth.firesuite.feuerwehr-rems.at/application/o/feuerwehr-dashboard/`
- **Authorization Endpoint**: Auto-discovered
- **Token Endpoint**: Auto-discovered
- **Userinfo Endpoint**: Auto-discovered
@@ -334,8 +334,8 @@ Update your Feuerwehr Dashboard `.env` file:
# Authentik OAuth Configuration
AUTHENTIK_CLIENT_ID=<your-client-id>
AUTHENTIK_CLIENT_SECRET=<your-client-secret>
AUTHENTIK_ISSUER=https://auth.yourdomain.com/application/o/feuerwehr-dashboard/
AUTHENTIK_REDIRECT_URI=https://dashboard.yourdomain.com/auth/callback
AUTHENTIK_ISSUER=https://auth.firesuite.feuerwehr-rems.at/application/o/feuerwehr-dashboard/
AUTHENTIK_REDIRECT_URI=https://start.feuerwehr-rems.at/auth/callback
# For development, use:
# AUTHENTIK_ISSUER=http://localhost:9000/application/o/feuerwehr-dashboard/
@@ -361,7 +361,7 @@ AUTHENTIK_REDIRECT_URI=https://dashboard.yourdomain.com/auth/callback
2. **Open the dashboard** in your browser:
```
Development: http://localhost:5173
Production: https://dashboard.yourdomain.com
Production: https://start.feuerwehr-rems.at
```
3. **Click "Login" button**
@@ -441,7 +441,7 @@ In the dashboard:
**Solution**:
1. Ensure `CORS_ORIGIN` in backend `.env` matches frontend URL
2. For development: `CORS_ORIGIN=http://localhost:5173`
3. For production: `CORS_ORIGIN=https://dashboard.yourdomain.com`
3. For production: `CORS_ORIGIN=https://start.feuerwehr-rems.at`
4. Restart backend after changing CORS settings
### Issue 4: Token Validation Failed
@@ -561,7 +561,7 @@ After configuration, verify:
Client Type: Confidential
Client ID: <auto-generated>
Client Secret: <auto-generated>
Redirect URIs: https://dashboard.yourdomain.com/auth/callback
Redirect URIs: https://start.feuerwehr-rems.at/auth/callback
Scopes: openid, profile, email
Access Token Validity: 3600
Refresh Token Validity: 86400
@@ -571,8 +571,8 @@ Refresh Token Validity: 86400
```bash
AUTHENTIK_CLIENT_ID=<from-authentik>
AUTHENTIK_CLIENT_SECRET=<from-authentik>
AUTHENTIK_ISSUER=https://auth.yourdomain.com/application/o/feuerwehr-dashboard/
AUTHENTIK_REDIRECT_URI=https://dashboard.yourdomain.com/auth/callback
AUTHENTIK_ISSUER=https://auth.firesuite.feuerwehr-rems.at/application/o/feuerwehr-dashboard/
AUTHENTIK_REDIRECT_URI=https://start.feuerwehr-rems.at/auth/callback
```
## Security Best Practices

View File

@@ -154,19 +154,19 @@ NODE_ENV=production
JWT_SECRET=<generated-jwt-secret>
# CORS - Set to your domain!
CORS_ORIGIN=https://dashboard.yourdomain.com
CORS_ORIGIN=https://start.feuerwehr-rems.at
# Frontend
FRONTEND_PORT=80
# API URL - Set to your backend URL
VITE_API_URL=https://api.yourdomain.com
VITE_API_URL=https://start.feuerwehr-rems.at
# Authentik OAuth (from Authentik setup)
AUTHENTIK_CLIENT_ID=<your-client-id>
AUTHENTIK_CLIENT_SECRET=<your-client-secret>
AUTHENTIK_ISSUER=https://auth.yourdomain.com/application/o/feuerwehr/
AUTHENTIK_REDIRECT_URI=https://dashboard.yourdomain.com/auth/callback
AUTHENTIK_ISSUER=https://auth.firesuite.feuerwehr-rems.at/application/o/feuerwehr/
AUTHENTIK_REDIRECT_URI=https://start.feuerwehr-rems.at/auth/callback
```
Secure the .env file:
@@ -253,7 +253,7 @@ Key points for production:
Create `Caddyfile`:
```caddy
dashboard.yourdomain.com {
start.feuerwehr-rems.at {
reverse_proxy localhost:80
encode gzip
@@ -265,7 +265,7 @@ dashboard.yourdomain.com {
}
}
api.yourdomain.com {
start.feuerwehr-rems.at {
reverse_proxy localhost:3000
encode gzip
}
@@ -299,7 +299,7 @@ Create Nginx configuration (`/etc/nginx/sites-available/feuerwehr`):
```nginx
server {
listen 80;
server_name dashboard.yourdomain.com;
server_name start.feuerwehr-rems.at;
location / {
proxy_pass http://localhost:80;
@@ -312,7 +312,7 @@ server {
server {
listen 80;
server_name api.yourdomain.com;
server_name start.feuerwehr-rems.at;
location / {
proxy_pass http://localhost:3000;
@@ -330,7 +330,7 @@ Enable and obtain SSL:
sudo ln -s /etc/nginx/sites-available/feuerwehr /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d dashboard.yourdomain.com -d api.yourdomain.com
sudo certbot --nginx -d start.feuerwehr-rems.at -d start.feuerwehr-rems.at
```
### Option 3: Using Docker with Traefik

View File

@@ -217,8 +217,8 @@ docker run -p 80:80 feuerwehr-frontend:latest
# 1. Set production environment variables
export POSTGRES_PASSWORD="secure_production_password"
export JWT_SECRET="secure_jwt_secret_min_32_chars"
export CORS_ORIGIN="https://yourdomain.com"
export VITE_API_URL="https://api.yourdomain.com"
export CORS_ORIGIN="https://feuerwehr-rems.at"
export VITE_API_URL="https://start.feuerwehr-rems.at"
# 2. Build and start
docker-compose up -d

View File

@@ -59,7 +59,7 @@ const environment: EnvironmentConfig = {
max: parseInt(process.env.RATE_LIMIT_MAX || '100', 10),
},
authentik: {
issuer: process.env.AUTHENTIK_ISSUER || 'https://authentik.yourdomain.com/application/o/your-app/',
issuer: process.env.AUTHENTIK_ISSUER || 'https://auth.firesuite.feuerwehr-rems.at/application/o/feuerwehr-dashboard/',
clientId: process.env.AUTHENTIK_CLIENT_ID || 'your_client_id_here',
clientSecret: process.env.AUTHENTIK_CLIENT_SECRET || 'your_client_secret_here',
redirectUri: process.env.AUTHENTIK_REDIRECT_URI || 'http://localhost:5173/auth/callback',

View File

@@ -36,11 +36,11 @@ services:
DB_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET is required}
JWT_EXPIRES_IN: ${JWT_EXPIRES_IN:-24h}
CORS_ORIGIN: ${CORS_ORIGIN:-http://localhost:80}
CORS_ORIGIN: ${CORS_ORIGIN:-https://start.feuerwehr-rems.at}
AUTHENTIK_ISSUER: ${AUTHENTIK_ISSUER:?AUTHENTIK_ISSUER is required}
AUTHENTIK_CLIENT_ID: ${AUTHENTIK_CLIENT_ID:?AUTHENTIK_CLIENT_ID is required}
AUTHENTIK_CLIENT_SECRET: ${AUTHENTIK_CLIENT_SECRET:?AUTHENTIK_CLIENT_SECRET is required}
AUTHENTIK_REDIRECT_URI: ${AUTHENTIK_REDIRECT_URI:-http://localhost/auth/callback}
AUTHENTIK_REDIRECT_URI: ${AUTHENTIK_REDIRECT_URI:-https://start.feuerwehr-rems.at/auth/callback}
ports:
- "${BACKEND_PORT:-3000}:3000"
depends_on:
@@ -61,7 +61,7 @@ services:
context: ./frontend
dockerfile: Dockerfile
args:
VITE_API_URL: ${VITE_API_URL:-http://localhost:3000}
VITE_API_URL: ${VITE_API_URL:-https://start.feuerwehr-rems.at}
VITE_AUTHENTIK_URL: ${VITE_AUTHENTIK_URL:?VITE_AUTHENTIK_URL is required}
VITE_CLIENT_ID: ${AUTHENTIK_CLIENT_ID:?AUTHENTIK_CLIENT_ID is required}
container_name: feuerwehr_frontend_prod

View File

@@ -1,3 +1,3 @@
VITE_API_URL=http://localhost:3000
VITE_AUTHENTIK_URL=https://authentik.yourdomain.com
VITE_AUTHENTIK_URL=https://auth.firesuite.feuerwehr-rems.at
VITE_CLIENT_ID=your_client_id_here

View File

@@ -65,6 +65,16 @@ http {
add_header Content-Type text/plain;
}
# Proxy API requests to backend
location /api/ {
proxy_pass http://backend:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;

View File

@@ -1,6 +1,6 @@
export const config = {
apiUrl: import.meta.env.VITE_API_URL || 'http://localhost:3000',
authentikUrl: import.meta.env.VITE_AUTHENTIK_URL || 'https://authentik.yourdomain.com',
authentikUrl: import.meta.env.VITE_AUTHENTIK_URL || 'https://auth.firesuite.feuerwehr-rems.at',
clientId: import.meta.env.VITE_CLIENT_ID || 'your_client_id_here',
};