Complete stage 7

This commit is contained in:
Joey Yakimowich-Payne 2026-01-13 20:21:47 -07:00
commit 1f88e291a9
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
9 changed files with 784 additions and 26 deletions

View file

@ -32,3 +32,8 @@ OIDC_JWKS_URI=http://localhost:9000/application/o/kaboot/jwks/
# OPTIONAL - CORS (Frontend origin for backend API) # OPTIONAL - CORS (Frontend origin for backend API)
# ============================================================================== # ==============================================================================
CORS_ORIGIN=http://localhost:5173 CORS_ORIGIN=http://localhost:5173
# ==============================================================================
# OPTIONAL - Logging
# ==============================================================================
LOG_REQUESTS=false

20
Caddyfile.example Normal file
View file

@ -0,0 +1,20 @@
# Kaboot Production Caddyfile
# Copy this file to Caddyfile and update the domain names
kaboot.example.com {
root * /srv/frontend
file_server
try_files {path} /index.html
handle /api/* {
reverse_proxy kaboot-backend:3001
}
handle /health {
reverse_proxy kaboot-backend:3001
}
}
auth.example.com {
reverse_proxy authentik-server:9000
}

View file

@ -299,21 +299,21 @@ Add user accounts via Authentik (OIDC) and persist quizzes to SQLite database. U
## Phase 7: Documentation & Deployment ## Phase 7: Documentation & Deployment
### 7.1 Documentation ### 7.1 Documentation
- [ ] Update main `README.md`: - [x] Update main `README.md`:
- [ ] Add Docker Compose setup instructions - [x] Add Docker Compose setup instructions
- [ ] Document environment variables - [x] Document environment variables
- [ ] Add Authentik configuration steps - [x] Add Authentik configuration steps
- [ ] Create `docs/AUTHENTIK_SETUP.md` (detailed IdP setup) - [x] Create `docs/AUTHENTIK_SETUP.md` (detailed IdP setup)
- [ ] Create `docs/API.md` (backend API documentation) - [x] Create `docs/API.md` (backend API documentation)
### 7.2 Production Considerations ### 7.2 Production Considerations
- [ ] Document production deployment: - [x] Document production deployment:
- [ ] HTTPS setup (reverse proxy) - [x] HTTPS setup (reverse proxy)
- [ ] Update redirect URIs for production domain - [x] Update redirect URIs for production domain
- [ ] Database backup strategy - [x] Database backup strategy
- [ ] Authentik email configuration - [x] Authentik email configuration
- [ ] Add health check endpoints - [x] Add health check endpoints
- [ ] Add logging configuration - [x] Add logging configuration
--- ---
@ -399,7 +399,7 @@ kaboot/
## Progress Tracking ## Progress Tracking
**Last Updated**: 2026-01-13 **Last Updated**: 2026-01-14
| Phase | Status | Notes | | Phase | Status | Notes |
|-------|--------|-------| |-------|--------|-------|
@ -409,4 +409,4 @@ kaboot/
| Phase 4 | **COMPLETE** | useQuizLibrary hook, QuizLibrary modal, Landing integration | | Phase 4 | **COMPLETE** | useQuizLibrary hook, QuizLibrary modal, Landing integration |
| Phase 5 | **COMPLETE** | SaveQuizPrompt modal, QuizCreator save checkbox, save integration | | Phase 5 | **COMPLETE** | SaveQuizPrompt modal, QuizCreator save checkbox, save integration |
| Phase 6 | **COMPLETE** | Toast notifications, loading states, error handling, edge cases | | Phase 6 | **COMPLETE** | Toast notifications, loading states, error handling, edge cases |
| Phase 7 | Not Started | | | Phase 7 | **COMPLETE** | README, API docs, PRODUCTION docs, health checks, logging |

124
README.md
View file

@ -2,19 +2,123 @@
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" /> <img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
</div> </div>
# Run and deploy your AI Studio app # Kaboot
This contains everything you need to run your app locally. Kaboot is an AI-powered quiz party game inspired by Kahoot. It leverages the Google Gemini API to instantly generate engaging quizzes on any topic, allowing users to host and join multiplayer games with ease.
View your app in AI Studio: https://ai.studio/apps/drive/1N0ITrr45ZWdQvXMQNxOULCmJBQyaiWH8 ## Features
## Run Locally - **AI Quiz Generation**: Create full quizzes in seconds by simply providing a topic or prompt using Google Gemini.
- **Real-time Multiplayer**: Host games and have players join via game pins using Peer-to-Peer technology.
- **Single-player Arcade Mode**: Play against AI bots to sharpen your skills.
- **Secure Authentication**: Integrated with Authentik for robust OIDC-based user management.
- **Quiz Library**: Save, manage, and reuse your AI-generated quizzes.
- **Dynamic UI**: A premium, responsive interface built with React, Framer Motion, and Lucide.
**Prerequisites:** Node.js ## Architecture
Kaboot is built with a modern decoupled architecture:
1. Install dependencies: - **Frontend**: React (Vite) with PeerJS for real-time communication.
`npm install` - **Backend**: Node.js Express server managing the quiz database and session state.
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key - **Database**: SQLite (Better-SQLite3) for lightweight, reliable data storage.
3. Run the app: - **Identity Provider**: Authentik (running in Docker) providing OIDC authentication.
`npm run dev` - **Infrastructure**: Redis and PostgreSQL (supporting Authentik).
- **AI Engine**: Google Gemini API for intelligent content generation.
## Prerequisites
Before you begin, ensure you have the following installed:
- **Docker & Docker Compose**: For running the backend services and authentication.
- **Node.js (v18+)**: For local frontend development.
- **Google Gemini API Key**: Required for AI quiz generation.
## Quick Start
The fastest way to get Kaboot running is using Docker Compose.
### 1. Initialize Environment
Run the setup script to generate necessary secrets and create your `.env` file:
```bash
chmod +x scripts/setup.sh
./scripts/setup.sh
```
### 2. Configure Gemini API
Open the newly created `.env` file and add your Gemini API key:
```env
GEMINI_API_KEY=your_api_key_here
```
### 3. Start Services
Launch the entire stack using Docker Compose:
```bash
docker compose up -d
```
This will start:
- **Authentik** (Port 9000)
- **PostgreSQL** & **Redis** (Internal)
- **Kaboot Backend** (Port 3001)
### 4. Setup Authentication
Follow the [Authentik Setup Guide](docs/AUTHENTIK_SETUP.md) to configure the OIDC provider.
## Development Setup
If you want to run the frontend in development mode with hot-reloading:
1. **Install Dependencies**:
```bash
npm install
```
2. **Run Development Server**:
```bash
npm run dev
```
The frontend will be available at `http://localhost:5173`.
## Configuration
Kaboot uses environment variables for configuration. Refer to `.env.example` for a complete list.
| Variable | Description | Default |
|----------|-------------|---------|
| `KABOOT_BACKEND_PORT` | Port for the Express backend | `3001` |
| `AUTHENTIK_PORT_HTTP` | Port for Authentik web interface | `9000` |
| `GEMINI_API_KEY` | Your Google Gemini API key | (Required) |
| `CORS_ORIGIN` | Allowed origin for API requests | `http://localhost:5173` |
| `PG_PASS` | PostgreSQL password for Authentik | (Generated) |
| `AUTHENTIK_SECRET_KEY` | Secret key for Authentik | (Generated) |
## Testing
### Backend Tests
To run the backend test suite:
```bash
cd server
npm install
npm test
```
## Documentation
- [Authentik Configuration](docs/AUTHENTIK_SETUP.md)
- [API Reference](docs/API.md)
## Troubleshooting
- **Authentik Initial Setup**: If you can't access the setup page, ensure `authentik-server` container is healthy using `docker compose ps`.
- **CORS Errors**: Verify that `CORS_ORIGIN` in your `.env` matches your frontend URL.
- **Gemini API Issues**: Ensure your API key is valid and has sufficient quota. Check the browser console for specific error messages from the GenAI SDK.
- **Database Locked**: If you encounter SQLite locking issues, ensure only one instance of the backend is writing to the database volume.
---
View your app in AI Studio: [https://ai.studio/apps/drive/1N0ITrr45ZWdQvXMQNxOULCmJBQyaiWH8](https://ai.studio/apps/drive/1N0ITrr45ZWdQvXMQNxOULCmJBQyaiWH8)

3
ai-todo.md Normal file
View file

@ -0,0 +1,3 @@
# Kaboot Documentation Tasks
- [x] Create /Users/joey/Downloads/kaboot/docs/PRODUCTION.md with production deployment guide

35
docker-compose.caddy.yml Normal file
View file

@ -0,0 +1,35 @@
# Caddy Reverse Proxy for Kaboot Production
#
# This compose file adds Caddy as a reverse proxy with automatic HTTPS.
# Use with the main docker-compose.yml using the -f flag.
#
# Usage:
# docker compose -f docker-compose.yml -f docker-compose.caddy.yml up -d
#
# Prerequisites:
# 1. Create a Caddyfile in the project root (see docs/PRODUCTION.md)
# 2. Build the frontend: npm run build
# 3. Update your domain DNS to point to your server
services:
caddy:
image: caddy:2-alpine
container_name: kaboot-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./dist:/srv/frontend:ro
- caddy-data:/data
- caddy-config:/config
depends_on:
- kaboot-backend
- authentik-server
networks:
- kaboot-network
volumes:
caddy-data:
caddy-config:

239
docs/API.md Normal file
View file

@ -0,0 +1,239 @@
# Kaboot Backend API Documentation
The Kaboot backend provides a RESTful API for managing quizzes and user profiles.
## Base URL
The backend server runs at:
`http://localhost:3001`
## Authentication
All routes under `/api/*` require authentication using an OIDC Bearer token in the `Authorization` header.
```http
Authorization: Bearer <your_access_token>
```
Tokens are issued by the Authentik Identity Provider.
---
## Endpoints
### Health Check
#### GET /health
Check the operational status of the backend server.
- **Authentication**: None
- **Response**: `200 OK`
- **Example Response**:
```json
{
"status": "ok",
"timestamp": "2026-01-13T10:00:00.000Z"
}
```
- **Curl Example**:
```bash
curl http://localhost:3001/health
```
---
### User Profile
#### GET /api/users/me
Retrieve the profile of the currently authenticated user.
- **Authentication**: Required
- **Response**: `200 OK`
- **Example Response**:
```json
{
"id": "user_123",
"username": "jdoe",
"email": "jdoe@example.com",
"displayName": "John Doe",
"createdAt": "2026-01-13T10:00:00.000Z",
"lastLogin": "2026-01-13T10:00:00.000Z",
"isNew": false
}
```
- **Curl Example**:
```bash
curl -H "Authorization: Bearer <token>" http://localhost:3001/api/users/me
```
---
### Quizzes
#### GET /api/quizzes
List all quizzes created by the authenticated user.
- **Authentication**: Required
- **Response**: `200 OK`
- **Example Response**:
```json
[
{
"id": "quiz_8b3f...",
"title": "World Capitals",
"source": "manual",
"aiTopic": null,
"createdAt": "2026-01-13T10:00:00.000Z",
"updatedAt": "2026-01-13T10:00:00.000Z",
"questionCount": 5
}
]
```
- **Curl Example**:
```bash
curl -H "Authorization: Bearer <token>" http://localhost:3001/api/quizzes
```
#### GET /api/quizzes/:id
Retrieve full details for a specific quiz, including all questions and answer options.
- **Authentication**: Required
- **Response**: `200 OK` or `404 Not Found`
- **Example Response**:
```json
{
"id": "quiz_8b3f...",
"title": "World Capitals",
"source": "manual",
"aiTopic": null,
"createdAt": "2026-01-13T10:00:00.000Z",
"updatedAt": "2026-01-13T10:00:00.000Z",
"questions": [
{
"id": "question_456",
"text": "What is the capital of Japan?",
"timeLimit": 20,
"orderIndex": 0,
"options": [
{
"id": "option_789",
"text": "Tokyo",
"isCorrect": true,
"shape": "triangle",
"color": "red",
"reason": "Tokyo is the political and economic center of Japan.",
"orderIndex": 0
}
]
}
]
}
```
- **Curl Example**:
```bash
curl -H "Authorization: Bearer <token>" http://localhost:3001/api/quizzes/quiz_8b3f...
```
#### POST /api/quizzes
Create a new quiz.
- **Authentication**: Required
- **Validation Rules**:
- `title`: Required, non-empty string.
- `source`: Required, must be `'manual'` or `'ai_generated'`.
- `questions`: Required, array with at least 1 question.
- **Question validation**:
- `text`: Required, non-empty string.
- `options`: Required, array with at least 2 options.
- Each option:
- `text`: Required, non-empty string.
- `isCorrect`: Required, boolean.
- `shape`: Required, one of `'triangle'`, `'diamond'`, `'circle'`, `'square'`.
- `color`: Required, one of `'red'`, `'blue'`, `'yellow'`, `'green'`.
- `reason`: Optional, string explaining the answer.
- At least one option must be marked as correct (`isCorrect: true`).
- **Request Body**:
```json
{
"title": "Space Exploration",
"source": "manual",
"questions": [
{
"text": "Which planet is known as the Red Planet?",
"timeLimit": 20,
"options": [
{ "text": "Mars", "isCorrect": true, "shape": "triangle", "color": "red" },
{ "text": "Venus", "isCorrect": false, "shape": "diamond", "color": "blue" }
]
}
]
}
```
- **Response**: `201 Created`
- **Example Response**:
```json
{
"id": "new_quiz_uuid"
}
```
- **Curl Example**:
```bash
curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d '{"title":"Space Quiz","source":"manual","questions":[{"text":"Which planet is known as the Red Planet?","options":[{"text":"Mars","isCorrect":true,"shape":"triangle","color":"red"},{"text":"Venus","isCorrect":false,"shape":"diamond","color":"blue"}]}]}' \
http://localhost:3001/api/quizzes
```
#### PUT /api/quizzes/:id
Update an existing quiz. This operation replaces the existing questions and options.
- **Authentication**: Required
- **Validation Rules**: Same as `POST /api/quizzes`.
- **Response**: `200 OK` or `404 Not Found`
- **Curl Example**:
```bash
curl -X PUT -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d '{"title":"Updated Space Quiz","questions":[{"text":"Which planet is red?","options":[{"text":"Mars","isCorrect":true,"shape":"triangle","color":"red"},{"text":"Venus","isCorrect":false,"shape":"diamond","color":"blue"}]}]}' \
http://localhost:3001/api/quizzes/quiz_uuid
```
#### DELETE /api/quizzes/:id
Permanently delete a quiz.
- **Authentication**: Required
- **Response**: `204 No Content` or `404 Not Found`
- **Curl Example**:
```bash
curl -X DELETE -H "Authorization: Bearer <token>" http://localhost:3001/api/quizzes/quiz_uuid
```
---
## Error Responses
The API returns standard HTTP status codes along with a JSON error object.
- **400 Bad Request**
Returned when the request body is invalid or validation fails.
```json
{ "error": "Title is required and cannot be empty" }
```
- **401 Unauthorized**
Returned when the Bearer token is missing, expired, or invalid.
```json
{ "error": "Missing or invalid authorization header" }
```
or
```json
{ "error": "Invalid token", "details": "..." }
```
- **404 Not Found**
Returned when the requested quiz does not exist or does not belong to the user.
```json
{ "error": "Quiz not found" }
```
- **500 Internal Server Error**
Returned when an unexpected error occurs on the server.
```json
{ "error": "Internal server error" }
```

326
docs/PRODUCTION.md Normal file
View file

@ -0,0 +1,326 @@
# Production Deployment Guide
This guide provides instructions for deploying Kaboot to a production environment. It covers security, persistence, and configuration for a robust setup.
## Prerequisites
- A Linux server with Docker and Docker Compose installed.
- A registered domain name (e.g., `kaboot.example.com`).
- SSL certificates (e.g., from Let's Encrypt).
- A Google Gemini API key.
## Architecture Overview
In production, the stack consists of:
- **Nginx**: Reverse proxy handling HTTPS and routing.
- **Authentik**: Identity Provider for authentication.
- **Kaboot Backend**: Express server for quiz logic and SQLite storage.
- **Kaboot Frontend**: Static assets served via Nginx or a dedicated service.
- **PostgreSQL**: Database for Authentik.
- **Redis**: Cache and task queue for Authentik.
## Environment Variables
Create a production `.env` file. Do not commit this file to version control.
### Backend & Authentik Configuration
```env
# Database Passwords (Generate strong secrets)
PG_PASS=your_strong_postgres_password
AUTHENTIK_SECRET_KEY=your_strong_authentik_secret
# Infrastructure
AUTHENTIK_PORT_HTTP=9000
KABOOT_BACKEND_PORT=3001
# AI Configuration
GEMINI_API_KEY=your_gemini_api_key
# OIDC Production Settings
OIDC_ISSUER=https://auth.example.com/application/o/kaboot/
OIDC_JWKS_URI=https://auth.example.com/application/o/kaboot/jwks/
# Security
CORS_ORIGIN=https://kaboot.example.com
LOG_REQUESTS=true
```
### Frontend Configuration
The frontend requires environment variables at build time:
- `VITE_API_URL`: `https://kaboot.example.com/api`
- `VITE_OIDC_AUTHORITY`: `https://auth.example.com/application/o/kaboot/`
## Docker Compose Production Example
Create a `docker-compose.prod.yml` for your production environment:
```yaml
services:
postgresql:
image: docker.io/library/postgres:16-alpine
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 30s
timeout: 5s
retries: 5
volumes:
- postgresql-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${PG_PASS}
POSTGRES_USER: ${PG_USER:-authentik}
POSTGRES_DB: ${PG_DB:-authentik}
networks:
- kaboot-network
redis:
image: docker.io/library/redis:alpine
restart: unless-stopped
command: --save 60 1 --loglevel warning
volumes:
- redis-data:/data
networks:
- kaboot-network
authentik-server:
image: ghcr.io/goauthentik/server:2025.2
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
volumes:
- ./authentik/media:/media
- ./authentik/custom-templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- kaboot-network
authentik-worker:
image: ghcr.io/goauthentik/server:2025.2
restart: unless-stopped
command: worker
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
user: root
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./authentik/media:/media
- ./authentik/certs:/certs
- ./authentik/custom-templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- kaboot-network
kaboot-backend:
build:
context: ./server
dockerfile: Dockerfile
restart: unless-stopped
environment:
NODE_ENV: production
PORT: 3001
DATABASE_PATH: /data/kaboot.db
OIDC_ISSUER: ${OIDC_ISSUER}
OIDC_JWKS_URI: ${OIDC_JWKS_URI}
CORS_ORIGIN: ${CORS_ORIGIN}
LOG_REQUESTS: ${LOG_REQUESTS}
volumes:
- kaboot-data:/data
networks:
- kaboot-network
volumes:
postgresql-data:
redis-data:
kaboot-data:
networks:
kaboot-network:
driver: bridge
```
## HTTPS and Reverse Proxy
Choose one of the following reverse proxy options. Caddy is recommended for its simplicity and automatic HTTPS.
### Option 1: Caddy (Recommended)
Caddy automatically obtains and renews SSL certificates from Let's Encrypt.
A separate `docker-compose.caddy.yml` is provided to add Caddy to your stack.
**Step 1: Create the Caddyfile**
Copy and customize the example Caddyfile:
```bash
cp Caddyfile.example Caddyfile
```
Edit `Caddyfile` and replace `kaboot.example.com` and `auth.example.com` with your actual domains:
```caddyfile
kaboot.example.com {
root * /srv/frontend
file_server
try_files {path} /index.html
handle /api/* {
reverse_proxy kaboot-backend:3001
}
handle /health {
reverse_proxy kaboot-backend:3001
}
}
auth.example.com {
reverse_proxy authentik-server:9000
}
```
**Step 2: Build the Frontend**
```bash
npm run build
```
This creates the `dist/` directory with production assets.
**Step 3: Start with Caddy**
Use both compose files together:
```bash
docker compose -f docker-compose.yml -f docker-compose.caddy.yml up -d
```
This will:
- Start all Kaboot services (backend, Authentik, PostgreSQL, Redis)
- Start Caddy as a reverse proxy on ports 80 and 443
- Automatically obtain SSL certificates from Let's Encrypt
**Step 4: Verify**
Check that all services are running:
```bash
docker compose -f docker-compose.yml -f docker-compose.caddy.yml ps
```
View Caddy logs:
```bash
docker logs kaboot-caddy
```
**Stopping the Stack**
```bash
docker compose -f docker-compose.yml -f docker-compose.caddy.yml down
```
### Option 2: Nginx
Use Nginx as a reverse proxy with manual SSL certificate management.
```nginx
server {
listen 443 ssl;
server_name kaboot.example.com;
ssl_certificate /etc/letsencrypt/live/kaboot.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/kaboot.example.com/privkey.pem;
location / {
root /var/www/kaboot/frontend;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://localhost:3001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /health {
proxy_pass http://localhost:3001/health;
}
}
server {
listen 443 ssl;
server_name auth.example.com;
ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem;
location / {
proxy_pass http://localhost:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
## Authentik Configuration for Production
1. **Update Redirect URIs**: In the Authentik Admin interface, go to **Applications** > **Providers** > **Kaboot OAuth2**. Update the **Redirect URIs** to use your production domain:
- `https://kaboot.example.com/callback`
- `https://kaboot.example.com/silent-renew.html`
- `https://kaboot.example.com`
2. **Email Configuration**: To enable password recovery, configure SMTP settings in Authentik. In the Admin interface, go to **System** > **Settings** and update the Email section.
- Host: your SMTP server
- Port: 587 or 465
- Username/Password: your credentials
- Use TLS/SSL: Enabled
## Database Backup Strategy
Kaboot uses SQLite, making backups straightforward.
### SQLite (Kaboot Data)
The database file is located in the `kaboot-data` volume at `/data/kaboot.db`. To back it up:
```bash
docker exec kaboot-backend sqlite3 /data/kaboot.db ".backup '/data/backup_$(date +%F).db'"
```
Then, copy the backup file from the volume to a secure location.
### PostgreSQL (Authentik Data)
For Authentik's metadata:
```bash
docker exec kaboot-postgresql pg_dump -U authentik authentik > authentik_backup_$(date +%F).sql
```
## Security Checklist
- [ ] Change all default passwords (`PG_PASS`, `AUTHENTIK_SECRET_KEY`).
- [ ] Ensure `NODE_ENV` is set to `production`.
- [ ] Use HTTPS for all connections.
- [ ] Set `CORS_ORIGIN` to your specific frontend domain.
- [ ] Regularly back up the `kaboot.db` and PostgreSQL data.
- [ ] Monitor logs by setting `LOG_REQUESTS=true`.
- [ ] Keep Docker images updated to the latest stable versions.

View file

@ -12,6 +12,19 @@ app.use(cors({
credentials: true, credentials: true,
})); }));
const LOG_REQUESTS = process.env.LOG_REQUESTS === 'true';
app.use((req: Request, res: Response, next: NextFunction) => {
if (LOG_REQUESTS && req.path !== '/health') {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.path} ${res.statusCode} ${duration}ms`);
});
}
next();
});
app.use((req: Request, res: Response, next: NextFunction) => { app.use((req: Request, res: Response, next: NextFunction) => {
express.json({ limit: '10mb' })(req, res, (err) => { express.json({ limit: '10mb' })(req, res, (err) => {
if (err instanceof SyntaxError && 'body' in err) { if (err instanceof SyntaxError && 'body' in err) {
@ -27,7 +40,20 @@ app.use((req: Request, res: Response, next: NextFunction) => {
}); });
app.get('/health', (_req: Request, res: Response) => { app.get('/health', (_req: Request, res: Response) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() }); try {
db.prepare('SELECT 1').get();
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
database: 'connected'
});
} catch {
res.status(503).json({
status: 'error',
timestamp: new Date().toISOString(),
database: 'disconnected'
});
}
}); });
app.use('/api/quizzes', quizzesRouter); app.use('/api/quizzes', quizzesRouter);