Phase 1 done

This commit is contained in:
Joey Yakimowich-Payne 2026-01-13 14:14:30 -07:00
commit 9a3fc97a34
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
14 changed files with 496 additions and 33 deletions

34
.env.example Normal file
View file

@ -0,0 +1,34 @@
# ==============================================================================
# REQUIRED - Generate with: openssl rand -base64 36 | tr -d '\n'
# ==============================================================================
PG_PASS=
AUTHENTIK_SECRET_KEY=
# ==============================================================================
# OPTIONAL - Authentik Database
# ==============================================================================
PG_USER=authentik
PG_DB=authentik
# ==============================================================================
# OPTIONAL - Ports
# ==============================================================================
AUTHENTIK_PORT_HTTP=9000
AUTHENTIK_PORT_HTTPS=9443
KABOOT_BACKEND_PORT=3001
# ==============================================================================
# OPTIONAL - Authentik Settings
# ==============================================================================
AUTHENTIK_ERROR_REPORTING=false
# ==============================================================================
# OPTIONAL - OIDC (Override if using custom domain)
# ==============================================================================
OIDC_ISSUER=http://localhost:9000/application/o/kaboot/
OIDC_JWKS_URI=http://localhost:9000/application/o/kaboot/jwks/
# ==============================================================================
# OPTIONAL - CORS (Frontend origin for backend API)
# ==============================================================================
CORS_ORIGIN=http://localhost:5173

15
.gitignore vendored
View file

@ -22,3 +22,18 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
# Environment secrets
.env
.env.local
.env.*.local
# Authentik volumes (keep structure, ignore data)
authentik/media/*
!authentik/media/.gitkeep
authentik/certs/*
!authentik/certs/.gitkeep
# Backend data
server/data/
*.db

View file

@ -31,40 +31,40 @@ Add user accounts via Authentik (OIDC) and persist quizzes to SQLite database. U
## Phase 1: Infrastructure Setup ## Phase 1: Infrastructure Setup
### 1.1 Docker Compose Configuration ### 1.1 Docker Compose Configuration
- [ ] Create `docker-compose.yml` with all services: - [x] Create `docker-compose.yml` with all services:
- [ ] PostgreSQL (Authentik database) - [x] PostgreSQL (Authentik database)
- [ ] Redis (Authentik cache) - [x] Redis (Authentik cache)
- [ ] Authentik server - [x] Authentik server
- [ ] Authentik worker - [x] Authentik worker
- [ ] Kaboot backend service - [x] Kaboot backend service
- [ ] Create `.env.example` with required variables: - [x] Create `.env.example` with required variables:
- [ ] `PG_PASS` - PostgreSQL password - [x] `PG_PASS` - PostgreSQL password
- [ ] `AUTHENTIK_SECRET_KEY` - Authentik secret - [x] `AUTHENTIK_SECRET_KEY` - Authentik secret
- [ ] `PG_USER`, `PG_DB` - Optional customization - [x] `PG_USER`, `PG_DB` - Optional customization
- [ ] Create setup script to generate secrets (`scripts/setup.sh`) - [x] Create setup script to generate secrets (`scripts/setup.sh`)
- [ ] Add `authentik/` directory structure for volumes: - [x] Add `authentik/` directory structure for volumes:
- [ ] `authentik/media/` - [x] `authentik/media/`
- [ ] `authentik/certs/` - [x] `authentik/certs/`
- [ ] `authentik/custom-templates/` - [x] `authentik/custom-templates/`
- [ ] Update `.gitignore` for new files: - [x] Update `.gitignore` for new files:
- [ ] `.env` - [x] `.env`
- [ ] `authentik/media/*` - [x] `authentik/media/*`
- [ ] `authentik/certs/*` - [x] `authentik/certs/*`
- [ ] `server/data/` - [x] `server/data/`
### 1.2 Authentik Configuration Documentation ### 1.2 Authentik Configuration Documentation
- [ ] Document initial setup steps in `docs/AUTHENTIK_SETUP.md`: - [x] Document initial setup steps in `docs/AUTHENTIK_SETUP.md`:
- [ ] Navigate to `http://localhost:9000/if/flow/initial-setup/` - [x] Navigate to `http://localhost:9000/if/flow/initial-setup/`
- [ ] Create admin account - [x] Create admin account
- [ ] Create OAuth2/OIDC Application + Provider: - [x] Create OAuth2/OIDC Application + Provider:
- [ ] Application name: `Kaboot` - [x] Application name: `Kaboot`
- [ ] Application slug: `kaboot` - [x] Application slug: `kaboot`
- [ ] Provider type: `OAuth2/OIDC` - [x] Provider type: `OAuth2/OIDC`
- [ ] Client type: `Public` (SPA with PKCE) - [x] Client type: `Public` (SPA with PKCE)
- [ ] Client ID: `kaboot-spa` - [x] Client ID: `kaboot-spa`
- [ ] Redirect URIs: `http://localhost:5173/callback`, `http://localhost:5173/silent-renew.html` - [x] Redirect URIs: `http://localhost:5173/callback`, `http://localhost:5173/silent-renew.html`
- [ ] Scopes: `openid`, `profile`, `email`, `offline_access` - [x] Scopes: `openid`, `profile`, `email`, `offline_access`
- [ ] Note down OIDC endpoints (issuer, JWKS URI, etc.) - [x] Note down OIDC endpoints (issuer, JWKS URI, etc.)
--- ---
@ -400,7 +400,7 @@ kaboot/
| Phase | Status | Notes | | Phase | Status | Notes |
|-------|--------|-------| |-------|--------|-------|
| Phase 1 | Not Started | | | Phase 1 | **COMPLETE** | Docker Compose, .env, setup script, Authentik docs |
| Phase 2 | Not Started | | | Phase 2 | Not Started | |
| Phase 3 | Not Started | | | Phase 3 | Not Started | |
| Phase 4 | Not Started | | | Phase 4 | Not Started | |

0
authentik/certs/.gitkeep Normal file
View file

View file

0
authentik/media/.gitkeep Normal file
View file

129
docker-compose.yml Normal file
View file

@ -0,0 +1,129 @@
services:
# ═══════════════════════════════════════════════════════════════════════════
# AUTHENTIK - Identity Provider
# ═══════════════════════════════════════════════════════════════════════════
postgresql:
image: docker.io/library/postgres:16-alpine
container_name: kaboot-postgresql
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
start_period: 20s
interval: 30s
retries: 5
timeout: 5s
volumes:
- postgresql-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${PG_PASS:?database password required}
POSTGRES_USER: ${PG_USER:-authentik}
POSTGRES_DB: ${PG_DB:-authentik}
networks:
- kaboot-network
redis:
image: docker.io/library/redis:alpine
container_name: kaboot-redis
command: --save 60 1 --loglevel warning
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
volumes:
- redis-data:/data
networks:
- kaboot-network
authentik-server:
image: ghcr.io/goauthentik/server:2025.2
container_name: kaboot-authentik-server
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:?authentik secret key required}
AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false}
volumes:
- ./authentik/media:/media
- ./authentik/custom-templates:/templates
ports:
- "${AUTHENTIK_PORT_HTTP:-9000}:9000"
- "${AUTHENTIK_PORT_HTTPS:-9443}:9443"
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- kaboot-network
authentik-worker:
image: ghcr.io/goauthentik/server:2025.2
container_name: kaboot-authentik-worker
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 - Application Backend
# ═══════════════════════════════════════════════════════════════════════════
kaboot-backend:
build:
context: ./server
dockerfile: Dockerfile
container_name: kaboot-backend
restart: unless-stopped
environment:
NODE_ENV: production
PORT: 3001
DATABASE_PATH: /data/kaboot.db
# OIDC Configuration - points to Authentik
OIDC_ISSUER: ${OIDC_ISSUER:-http://authentik-server:9000/application/o/kaboot/}
OIDC_JWKS_URI: ${OIDC_JWKS_URI:-http://authentik-server:9000/application/o/kaboot/jwks/}
# CORS - allow frontend origin
CORS_ORIGIN: ${CORS_ORIGIN:-http://localhost:5173}
volumes:
- kaboot-data:/data
ports:
- "${KABOOT_BACKEND_PORT:-3001}:3001"
depends_on:
- authentik-server
networks:
- kaboot-network
volumes:
postgresql-data:
redis-data:
kaboot-data:
networks:
kaboot-network:
driver: bridge

159
docs/AUTHENTIK_SETUP.md Normal file
View file

@ -0,0 +1,159 @@
# Authentik Setup Guide for Kaboot
This guide walks through configuring Authentik as the OAuth2/OIDC identity provider for Kaboot.
## Prerequisites
- Docker and Docker Compose installed
- Kaboot stack running (`docker compose up -d`)
- Access to `http://localhost:9000`
## Step 1: Initial Authentik Setup
1. Navigate to `http://localhost:9000/if/flow/initial-setup/`
- **Important**: Include the trailing slash `/`
2. Create the admin account:
- Email: Your email address
- Password: Choose a strong password
3. Log in with the credentials you just created
## Step 2: Create the Kaboot Application
1. In the Authentik admin interface, go to **Applications** > **Applications**
2. Click **Create with provider**
3. **Application Settings**:
| Field | Value |
|-------|-------|
| Name | `Kaboot` |
| Slug | `kaboot` |
| Launch URL | `http://localhost:5173` |
4. Click **Next**
## Step 3: Configure OAuth2/OIDC Provider
1. Select **OAuth2/OIDC** as the Provider Type
2. Click **Next**
3. **Provider Configuration**:
| Field | Value |
|-------|-------|
| Name | `Kaboot OAuth2` |
| Authorization flow | `default-provider-authorization-implicit-consent` |
| Client type | `Public` |
| Client ID | `kaboot-spa` |
4. **Redirect URIs** (one per line):
```
http://localhost:5173/callback
http://localhost:5173/silent-renew.html
http://localhost:5173
```
5. **Advanced Settings**:
| Field | Value |
|-------|-------|
| Subject mode | `Based on the User's hashed ID` |
| Include claims in id_token | `Yes` |
| Issuer mode | `Each provider has a different issuer` |
6. **Scopes** - Ensure these are selected:
- `openid`
- `profile`
- `email`
- `offline_access` (for refresh tokens)
7. Click **Submit**
## Step 4: Verify OIDC Endpoints
After creation, go to **Applications** > **Providers** > **Kaboot OAuth2**
Note these endpoints (you'll need them for frontend configuration):
| Endpoint | URL |
|----------|-----|
| Issuer | `http://localhost:9000/application/o/kaboot/` |
| Authorization | `http://localhost:9000/application/o/authorize/` |
| Token | `http://localhost:9000/application/o/token/` |
| UserInfo | `http://localhost:9000/application/o/userinfo/` |
| JWKS | `http://localhost:9000/application/o/kaboot/jwks/` |
## Step 5: Test the Configuration
1. Open the OpenID Configuration URL in your browser:
```
http://localhost:9000/application/o/kaboot/.well-known/openid-configuration
```
2. You should see a JSON response with all OIDC endpoints
## Step 6: Create a Test User (Optional)
1. Go to **Directory** > **Users**
2. Click **Create**
3. Fill in user details:
- Username: `testuser`
- Name: `Test User`
- Email: `test@example.com`
4. After creation, click on the user and go to the **Credentials** tab
5. Click **Set password** to create a password
## Environment Variables
Ensure your `.env` file has the correct OIDC configuration:
```bash
OIDC_ISSUER=http://localhost:9000/application/o/kaboot/
OIDC_JWKS_URI=http://localhost:9000/application/o/kaboot/jwks/
```
For the frontend OIDC config (`src/config/oidc.ts`):
```typescript
export const oidcConfig = {
authority: 'http://localhost:9000/application/o/kaboot/',
client_id: 'kaboot-spa',
redirect_uri: `${window.location.origin}/callback`,
// ... rest of config
};
```
## Troubleshooting
### "Invalid redirect URI" error
- Ensure all redirect URIs are added exactly as configured in the provider
- Check for trailing slashes - they must match exactly
### "Client not found" error
- Verify the Client ID matches `kaboot-spa`
- Ensure the application is enabled (not archived)
### CORS errors
- Authentik handles CORS automatically for configured redirect URIs
- Ensure your frontend origin (`http://localhost:5173`) is in the redirect URIs
### Token validation fails on backend
- Verify `OIDC_ISSUER` and `OIDC_JWKS_URI` are correct
- The backend must be able to reach Authentik at `http://authentik-server:9000` (Docker network)
## Production Notes
For production deployment:
1. Use HTTPS everywhere
2. Update all URLs from `localhost` to your domain
3. Update redirect URIs in Authentik
4. Update frontend OIDC config with production URLs
5. Update `.env` with production OIDC endpoints
6. Consider enabling Authentik error reporting
7. Configure email settings in Authentik for password recovery

4
features.md Normal file
View file

@ -0,0 +1,4 @@
- [ ] All data stored in sqlite db.
- [ ] AI generated content based on document upload
- [ ] Moderation (kick player, lock game, filter names)
- [ ] Persistent game urls while game is active

48
scripts/setup.sh Executable file
View file

@ -0,0 +1,48 @@
#!/bin/bash
set -e
ENV_FILE=".env"
ENV_EXAMPLE=".env.example"
echo "Kaboot Setup Script"
echo "==================="
echo ""
if [ -f "$ENV_FILE" ]; then
read -p ".env file already exists. Overwrite? (y/N): " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Aborting. Existing .env file preserved."
exit 0
fi
fi
if [ ! -f "$ENV_EXAMPLE" ]; then
echo "Error: .env.example not found. Run this script from the project root."
exit 1
fi
echo "Generating secrets..."
PG_PASS=$(openssl rand -base64 36 | tr -d '\n')
AUTHENTIK_SECRET_KEY=$(openssl rand -base64 60 | tr -d '\n')
cp "$ENV_EXAMPLE" "$ENV_FILE"
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "s|^PG_PASS=.*|PG_PASS=${PG_PASS}|" "$ENV_FILE"
sed -i '' "s|^AUTHENTIK_SECRET_KEY=.*|AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY}|" "$ENV_FILE"
else
sed -i "s|^PG_PASS=.*|PG_PASS=${PG_PASS}|" "$ENV_FILE"
sed -i "s|^AUTHENTIK_SECRET_KEY=.*|AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY}|" "$ENV_FILE"
fi
echo ""
echo "Created .env file with generated secrets."
echo ""
echo "Next steps:"
echo " 1. Review .env and adjust settings if needed"
echo " 2. Run: docker compose up -d"
echo " 3. Open: http://localhost:9000/if/flow/initial-setup/"
echo " 4. Follow docs/AUTHENTIK_SETUP.md to configure the OAuth2 provider"
echo ""

15
server/Dockerfile Normal file
View file

@ -0,0 +1,15 @@
FROM node:22-alpine
WORKDIR /app
RUN apk add --no-cache python3 make g++
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3001
CMD ["npm", "start"]

28
server/package.json Normal file
View file

@ -0,0 +1,28 @@
{
"name": "kaboot-backend",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"better-sqlite3": "^11.7.0",
"cors": "^2.8.5",
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2",
"jwks-rsa": "^3.1.0",
"uuid": "^11.0.5"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.12",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
"@types/jsonwebtoken": "^9.0.7",
"@types/node": "^22.10.7",
"@types/uuid": "^10.0.0",
"tsx": "^4.19.2",
"typescript": "^5.7.3"
}
}

16
server/src/index.ts Normal file
View file

@ -0,0 +1,16 @@
import express from 'express';
import cors from 'cors';
const app = express();
const PORT = process.env.PORT || 3001;
app.use(cors({ origin: process.env.CORS_ORIGIN || 'http://localhost:5173' }));
app.use(express.json());
app.get('/health', (_req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
app.listen(PORT, () => {
console.log(`Kaboot backend running on port ${PORT}`);
});

15
server/tsconfig.json Normal file
View file

@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "src",
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}