Add Authentik blueprints for automated OAuth2/OIDC setup

Automate the manual Authentik configuration process using native YAML blueprints
that are applied on container startup.

Changes:
- Add kaboot-setup.yaml blueprint for local development
- Add kaboot-setup-production.yaml.example for production with configurable domains
- Update docker-compose.yml and docker-compose.prod.yml to mount blueprints
- Add AUTHENTIK_BOOTSTRAP_PASSWORD/TOKEN env vars for automated admin setup
- Update setup.sh to generate bootstrap credentials and display admin password
- Update Caddyfile.example with proper proxy headers for Authentik
- Add Caddyfile to .gitignore (user-specific config)
- Update docs with Quick Start sections for automated setup

The blueprints create:
- OAuth2/OIDC provider (public client, client_id: kaboot-spa)
- Kaboot application with redirect URIs
- kaboot-users group with application binding
- Enrollment flow with sign-up capability
- Password complexity policy
- Test user and service account (passwords set manually)
This commit is contained in:
Joey Yakimowich-Payne 2026-01-14 16:20:10 -07:00
commit 1506210a2e
No known key found for this signature in database
GPG key ID: DDF6AF5B21B407D4
11 changed files with 765 additions and 131 deletions

View file

@ -22,6 +22,13 @@ KABOOT_BACKEND_PORT=3001
# ==============================================================================
AUTHENTIK_ERROR_REPORTING=false
# ==============================================================================
# OPTIONAL - Authentik Bootstrap (for automated setup)
# Generate with: openssl rand -base64 36 | tr -d '\n'
# ==============================================================================
AUTHENTIK_BOOTSTRAP_PASSWORD=
AUTHENTIK_BOOTSTRAP_TOKEN=
# ==============================================================================
# OPTIONAL - OIDC (Override if using custom domain)
# ==============================================================================

3
.gitignore vendored
View file

@ -29,6 +29,9 @@ dist-ssr
.env.*.local
.env.test
# Local Caddy config (use Caddyfile.example as template)
Caddyfile
# Authentik volumes (keep structure, ignore data)
authentik/media/*
!authentik/media/.gitkeep

View file

@ -16,5 +16,11 @@ kaboot.example.com {
}
auth.example.com {
reverse_proxy authentik-server:9000
reverse_proxy authentik-server:9000 {
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-Host {host}
transport http {
keepalive 30s
}
}
}

View file

@ -0,0 +1,216 @@
# Kaboot Authentik Blueprint - PRODUCTION
# Copy this file to kaboot-setup-production.yaml and update the domain.
#
# IMPORTANT: Remove or rename kaboot-setup.yaml when using this file
# to avoid conflicting configurations.
#
# Prerequisites:
# - Set AUTHENTIK_BOOTSTRAP_PASSWORD and AUTHENTIK_BOOTSTRAP_TOKEN in .env
# - Mount this directory to /blueprints/custom in docker-compose.yml
# - Update KABOOT_DOMAIN and AUTH_DOMAIN below with your actual domains
#
# yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json
---
version: 1
metadata:
name: Kaboot Application Setup (Production)
labels:
blueprints.goauthentik.io/description: "Complete Kaboot OAuth2/OIDC setup for production"
context:
kaboot_domain: kaboot.example.com
auth_domain: auth.example.com
entries:
# ═══════════════════════════════════════════════════════════════════════════════
# GROUPS
# ═══════════════════════════════════════════════════════════════════════════════
- id: kaboot-users-group
model: authentik_core.group
identifiers:
name: kaboot-users
attrs:
name: kaboot-users
# ═══════════════════════════════════════════════════════════════════════════════
# OAUTH2/OIDC PROVIDER
# ═══════════════════════════════════════════════════════════════════════════════
- id: kaboot-oauth2-provider
model: authentik_providers_oauth2.oauth2provider
identifiers:
name: Kaboot OAuth2
attrs:
name: Kaboot OAuth2
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
client_type: public
client_id: kaboot-spa
redirect_uris:
- url: !Format ["https://%s/callback", !Context kaboot_domain]
matching_mode: strict
- url: !Format ["https://%s/silent-renew.html", !Context kaboot_domain]
matching_mode: strict
- url: !Format ["https://%s", !Context kaboot_domain]
matching_mode: strict
access_code_validity: minutes=1
access_token_validity: minutes=30
refresh_token_validity: days=30
sub_mode: hashed_user_id
include_claims_in_id_token: true
issuer_mode: per_provider
property_mappings:
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, offline_access]]
# ═══════════════════════════════════════════════════════════════════════════════
# APPLICATION
# ═══════════════════════════════════════════════════════════════════════════════
- id: kaboot-application
model: authentik_core.application
identifiers:
slug: kaboot
attrs:
name: Kaboot
slug: kaboot
provider: !KeyOf kaboot-oauth2-provider
policy_engine_mode: any
meta_launch_url: !Format ["https://%s", !Context kaboot_domain]
- id: kaboot-group-policy-binding
model: authentik_policies.policybinding
identifiers:
target: !KeyOf kaboot-application
group: !KeyOf kaboot-users-group
attrs:
order: 0
enabled: true
negate: false
timeout: 30
# ═══════════════════════════════════════════════════════════════════════════════
# PASSWORD POLICY
# ═══════════════════════════════════════════════════════════════════════════════
- id: password-complexity-policy
model: authentik_policies_password.passwordpolicy
identifiers:
name: password-complexity
attrs:
name: password-complexity
password_field: password
length_min: 8
amount_uppercase: 1
amount_lowercase: 1
amount_digits: 1
error_message: "Password must be at least 8 characters with 1 uppercase, 1 lowercase, and 1 digit."
# ═══════════════════════════════════════════════════════════════════════════════
# ENROLLMENT STAGES
# ═══════════════════════════════════════════════════════════════════════════════
- id: enrollment-prompt-stage
model: authentik_stages_prompt.promptstage
identifiers:
name: enrollment-prompt
attrs:
name: enrollment-prompt
fields:
- !Find [authentik_stages_prompt.prompt, [name, default-source-enrollment-field-username]]
- !Find [authentik_stages_prompt.prompt, [name, default-user-settings-field-email]]
- !Find [authentik_stages_prompt.prompt, [name, default-password-change-field-password]]
- !Find [authentik_stages_prompt.prompt, [name, default-password-change-field-password-repeat]]
validation_policies:
- !KeyOf password-complexity-policy
- id: enrollment-user-write-stage
model: authentik_stages_user_write.userwritestage
identifiers:
name: enrollment-user-write
attrs:
name: enrollment-user-write
user_creation_mode: always_create
create_users_as_inactive: false
create_users_group: !KeyOf kaboot-users-group
- id: enrollment-user-login-stage
model: authentik_stages_user_login.userloginstage
identifiers:
name: enrollment-user-login
attrs:
name: enrollment-user-login
session_duration: hours=24
remember_me_offset: days=30
network_binding: no_binding
geoip_binding: no_binding
terminate_other_sessions: false
# ═══════════════════════════════════════════════════════════════════════════════
# ENROLLMENT FLOW
# ═══════════════════════════════════════════════════════════════════════════════
- id: enrollment-flow
model: authentik_flows.flow
identifiers:
slug: enrollment-flow
attrs:
name: Enrollment Flow
title: Sign Up
slug: enrollment-flow
designation: enrollment
authentication: none
- id: enrollment-flow-prompt-binding
model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf enrollment-flow
stage: !KeyOf enrollment-prompt-stage
attrs:
order: 10
evaluate_on_plan: true
re_evaluate_policies: false
invalid_response_action: retry
- id: enrollment-flow-user-write-binding
model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf enrollment-flow
stage: !KeyOf enrollment-user-write-stage
attrs:
order: 20
evaluate_on_plan: true
re_evaluate_policies: false
invalid_response_action: retry
- id: enrollment-flow-user-login-binding
model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf enrollment-flow
stage: !KeyOf enrollment-user-login-stage
attrs:
order: 30
evaluate_on_plan: true
re_evaluate_policies: false
invalid_response_action: retry
# ═══════════════════════════════════════════════════════════════════════════════
# LINK ENROLLMENT FLOW TO DEFAULT LOGIN
# ═══════════════════════════════════════════════════════════════════════════════
- id: update-identification-stage
model: authentik_stages_identification.identificationstage
identifiers:
name: default-authentication-identification
attrs:
enrollment_flow: !KeyOf enrollment-flow
user_fields:
- email
- username
case_insensitive_matching: true
show_matched_user: true
show_source_labels: false
pretend_user_exists: true

View file

@ -0,0 +1,257 @@
# Kaboot Authentik Blueprint
# This blueprint automatically configures all Authentik resources needed for Kaboot.
# Place in authentik/blueprints/ and it will be auto-imported on container startup.
#
# Prerequisites:
# - Set AUTHENTIK_BOOTSTRAP_PASSWORD and AUTHENTIK_BOOTSTRAP_TOKEN in .env
# - Mount this directory to /blueprints/custom in docker-compose.yml
#
#
# What this creates:
# - Kaboot OAuth2/OIDC Provider (public client)
# - Kaboot Application
# - kaboot-users Group
# - Enrollment flow with prompt, user write, and login stages
# - Password complexity policy
# - Test user (kaboottest) - for manual browser testing
# - Service account (kaboot-test-service) - for API testing
#
# yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json
---
version: 1
metadata:
name: Kaboot Application Setup
labels:
blueprints.goauthentik.io/description: "Complete Kaboot OAuth2/OIDC setup with enrollment flow"
entries:
# ═══════════════════════════════════════════════════════════════════════════════
# GROUPS
# ═══════════════════════════════════════════════════════════════════════════════
- id: kaboot-users-group
model: authentik_core.group
identifiers:
name: kaboot-users
attrs:
name: kaboot-users
# ═══════════════════════════════════════════════════════════════════════════════
# OAUTH2/OIDC PROVIDER
# ═══════════════════════════════════════════════════════════════════════════════
- id: kaboot-oauth2-provider
model: authentik_providers_oauth2.oauth2provider
identifiers:
name: Kaboot OAuth2
attrs:
name: Kaboot OAuth2
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
client_type: public
client_id: kaboot-spa
redirect_uris:
- url: http://localhost:5173/callback
matching_mode: strict
- url: http://localhost:5173/silent-renew.html
matching_mode: strict
- url: http://localhost:5173
matching_mode: strict
access_code_validity: minutes=1
access_token_validity: minutes=30
refresh_token_validity: days=30
sub_mode: hashed_user_id
include_claims_in_id_token: true
issuer_mode: per_provider
property_mappings:
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, offline_access]]
# ═══════════════════════════════════════════════════════════════════════════════
# APPLICATION
# ═══════════════════════════════════════════════════════════════════════════════
- id: kaboot-application
model: authentik_core.application
identifiers:
slug: kaboot
attrs:
name: Kaboot
slug: kaboot
provider: !KeyOf kaboot-oauth2-provider
policy_engine_mode: any
meta_launch_url: http://localhost:5173
- id: kaboot-group-policy-binding
model: authentik_policies.policybinding
identifiers:
target: !KeyOf kaboot-application
group: !KeyOf kaboot-users-group
attrs:
order: 0
enabled: true
negate: false
timeout: 30
# ═══════════════════════════════════════════════════════════════════════════════
# PASSWORD POLICY
# ═══════════════════════════════════════════════════════════════════════════════
- id: password-complexity-policy
model: authentik_policies_password.passwordpolicy
identifiers:
name: password-complexity
attrs:
name: password-complexity
password_field: password
length_min: 8
amount_uppercase: 1
amount_lowercase: 1
amount_digits: 1
error_message: "Password must be at least 8 characters with 1 uppercase, 1 lowercase, and 1 digit."
# ═══════════════════════════════════════════════════════════════════════════════
# ENROLLMENT STAGES
# ═══════════════════════════════════════════════════════════════════════════════
- id: enrollment-prompt-stage
model: authentik_stages_prompt.promptstage
identifiers:
name: enrollment-prompt
attrs:
name: enrollment-prompt
fields:
- !Find [authentik_stages_prompt.prompt, [name, default-source-enrollment-field-username]]
- !Find [authentik_stages_prompt.prompt, [name, default-user-settings-field-email]]
- !Find [authentik_stages_prompt.prompt, [name, default-password-change-field-password]]
- !Find [authentik_stages_prompt.prompt, [name, default-password-change-field-password-repeat]]
validation_policies:
- !KeyOf password-complexity-policy
- id: enrollment-user-write-stage
model: authentik_stages_user_write.userwritestage
identifiers:
name: enrollment-user-write
attrs:
name: enrollment-user-write
user_creation_mode: always_create
create_users_as_inactive: false
create_users_group: !KeyOf kaboot-users-group
- id: enrollment-user-login-stage
model: authentik_stages_user_login.userloginstage
identifiers:
name: enrollment-user-login
attrs:
name: enrollment-user-login
session_duration: hours=24
remember_me_offset: days=30
network_binding: no_binding
geoip_binding: no_binding
terminate_other_sessions: false
# ═══════════════════════════════════════════════════════════════════════════════
# ENROLLMENT FLOW
# ═══════════════════════════════════════════════════════════════════════════════
- id: enrollment-flow
model: authentik_flows.flow
identifiers:
slug: enrollment-flow
attrs:
name: Enrollment Flow
title: Sign Up
slug: enrollment-flow
designation: enrollment
authentication: none
- id: enrollment-flow-prompt-binding
model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf enrollment-flow
stage: !KeyOf enrollment-prompt-stage
attrs:
order: 10
evaluate_on_plan: true
re_evaluate_policies: false
invalid_response_action: retry
- id: enrollment-flow-user-write-binding
model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf enrollment-flow
stage: !KeyOf enrollment-user-write-stage
attrs:
order: 20
evaluate_on_plan: true
re_evaluate_policies: false
invalid_response_action: retry
- id: enrollment-flow-user-login-binding
model: authentik_flows.flowstagebinding
identifiers:
target: !KeyOf enrollment-flow
stage: !KeyOf enrollment-user-login-stage
attrs:
order: 30
evaluate_on_plan: true
re_evaluate_policies: false
invalid_response_action: retry
# ═══════════════════════════════════════════════════════════════════════════════
# LINK ENROLLMENT FLOW TO DEFAULT LOGIN
# ═══════════════════════════════════════════════════════════════════════════════
- id: update-identification-stage
model: authentik_stages_identification.identificationstage
identifiers:
name: default-authentication-identification
attrs:
enrollment_flow: !KeyOf enrollment-flow
user_fields:
- email
- username
case_insensitive_matching: true
show_matched_user: true
show_source_labels: false
pretend_user_exists: true
# ═══════════════════════════════════════════════════════════════════════════════
# TEST USER (for manual browser testing)
# ═══════════════════════════════════════════════════════════════════════════════
- id: kaboot-test-user
model: authentik_core.user
identifiers:
username: kaboottest
attrs:
username: kaboottest
name: Kaboot Test
email: kaboottest@test.com
path: users
is_active: true
groups:
- !KeyOf kaboot-users-group
# Note: Password must be set manually via UI or API after blueprint import
# Run: docker compose exec authentik-server ak setpassword kaboottest
# ═══════════════════════════════════════════════════════════════════════════════
# SERVICE ACCOUNT (for API/automated testing)
# ═══════════════════════════════════════════════════════════════════════════════
- id: kaboot-test-service-account
model: authentik_core.user
identifiers:
username: kaboot-test-service
attrs:
username: kaboot-test-service
name: Kaboot Test Service
path: users
type: service_account
is_active: true
groups:
- !KeyOf kaboot-users-group
# Note: App password must be created via UI or API after blueprint import
# See docs/AUTHENTIK_SETUP.md for instructions

View file

@ -1,15 +1,16 @@
# 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.
# Use with docker-compose.prod.yml using the -f flag.
#
# Usage:
# docker compose -f docker-compose.yml -f docker-compose.caddy.yml up -d
# docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml up -d
#
# Prerequisites:
# 1. Create a Caddyfile in the project root (see docs/PRODUCTION.md)
# 1. Copy Caddyfile.example to Caddyfile and update domains
# 2. Build the frontend: npm run build
# 3. Update your domain DNS to point to your server
# 4. See docs/PRODUCTION.md for full instructions
services:
caddy:

111
docker-compose.prod.yml Normal file
View file

@ -0,0 +1,111 @@
services:
postgresql:
image: docker.io/library/postgres:16-alpine
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
start_period: 20s
interval: 30s
timeout: 5s
retries: 5
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
restart: unless-stopped
command: --save 60 1 --loglevel warning
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s
interval: 30s
timeout: 3s
retries: 5
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}
AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD:-}
AUTHENTIK_BOOTSTRAP_TOKEN: ${AUTHENTIK_BOOTSTRAP_TOKEN:-}
volumes:
- ./authentik/media:/media
- ./authentik/custom-templates:/templates
- ./authentik/blueprints:/blueprints/custom
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}
OIDC_INTERNAL_JWKS_URI: http://authentik-server:9000/application/o/kaboot/jwks/
CORS_ORIGIN: ${CORS_ORIGIN}
LOG_REQUESTS: ${LOG_REQUESTS:-true}
volumes:
- kaboot-data:/data
networks:
- kaboot-network
volumes:
postgresql-data:
redis-data:
kaboot-data:
networks:
kaboot-network:
driver: bridge

View file

@ -51,9 +51,12 @@ services:
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?authentik secret key required}
AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false}
AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD:-}
AUTHENTIK_BOOTSTRAP_TOKEN: ${AUTHENTIK_BOOTSTRAP_TOKEN:-}
volumes:
- ./authentik/media:/media
- ./authentik/custom-templates:/templates
- ./authentik/blueprints:/blueprints/custom
ports:
- "${AUTHENTIK_PORT_HTTP:-9000}:9000"
- "${AUTHENTIK_PORT_HTTPS:-9443}:9443"

View file

@ -2,6 +2,45 @@
This guide walks through configuring Authentik as the OAuth2/OIDC identity provider for Kaboot.
## Quick Start (Automated Setup)
The recommended approach uses Authentik Blueprints for automatic configuration:
```bash
# 1. Run setup script (generates all secrets including admin password)
./scripts/setup.sh
# 2. Start the stack
docker compose up -d
# 3. Wait for Authentik to initialize (~30 seconds)
docker compose logs -f authentik-server | grep -i blueprint
# 4. Set password for test user
docker compose exec authentik-server ak set_password kaboottest
# Enter: kaboottest (or your preferred password)
# 5. (Optional) Create app password for service account via UI
# See "Step 7: Create a Service Account" below
```
The blueprint automatically creates:
- Kaboot OAuth2/OIDC Provider (public client, client_id: `kaboot-spa`)
- Kaboot Application with proper redirect URIs
- `kaboot-users` Group
- Enrollment flow with sign-up capability
- Password complexity policy
- Test user (`kaboottest`)
- Service account (`kaboot-test-service`)
Your admin credentials are printed by `setup.sh` - save them!
---
## Manual Setup (Alternative)
If you prefer manual configuration or need to customize the setup, follow the steps below.
## Prerequisites
- Docker and Docker Compose installed

View file

@ -1,21 +1,59 @@
# Production Deployment Guide
This guide provides instructions for deploying Kaboot to a production environment. It covers security, persistence, and configuration for a robust setup.
This guide provides instructions for deploying Kaboot to a production environment with Caddy reverse proxy and automatic HTTPS.
## Quick Start (Caddy + Automated Authentik Setup)
```bash
# 1. Generate secrets
./scripts/setup.sh
# 2. Configure production blueprint
cp authentik/blueprints/kaboot-setup-production.yaml.example \
authentik/blueprints/kaboot-setup-production.yaml
# Edit the blueprint - update domains in the 'context' section:
# kaboot_domain: your-app.com
# auth_domain: auth.your-app.com
# Remove dev blueprint to avoid conflicts
rm authentik/blueprints/kaboot-setup.yaml
# 3. Configure Caddyfile
cp Caddyfile.example Caddyfile
# Edit Caddyfile - replace example.com with your domains
# 4. Update .env with production values
# - OIDC_ISSUER=https://auth.your-app.com/application/o/kaboot/
# - OIDC_JWKS_URI=https://auth.your-app.com/application/o/kaboot/jwks/
# - CORS_ORIGIN=https://your-app.com
# 5. Build frontend with production URLs
VITE_API_URL=https://your-app.com/api \
VITE_OIDC_AUTHORITY=https://auth.your-app.com/application/o/kaboot/ \
npm run build
# 6. Start the stack
docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml up -d
# 7. Verify
docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml ps
```
## 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).
- Two registered domain names (e.g., `kaboot.example.com` and `auth.example.com`).
- DNS A records pointing both domains to your server.
- 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.
- **Caddy**: Reverse proxy with automatic HTTPS via Let's Encrypt.
- **Authentik**: Identity Provider for OAuth2/OIDC authentication.
- **Kaboot Backend**: Express server for quiz logic and SQLite storage.
- **Kaboot Frontend**: Static assets served via Nginx or a dedicated service.
- **Kaboot Frontend**: Static assets served via Caddy.
- **PostgreSQL**: Database for Authentik.
- **Redis**: Cache and task queue for Authentik.
@ -26,18 +64,15 @@ Create a production `.env` file. Do not commit this file to version control.
### Backend & Authentik Configuration
```env
# Database Passwords (Generate strong secrets)
# Database Passwords (Generated by setup.sh)
PG_PASS=your_strong_postgres_password
AUTHENTIK_SECRET_KEY=your_strong_authentik_secret
# Infrastructure
AUTHENTIK_PORT_HTTP=9000
KABOOT_BACKEND_PORT=3001
# Bootstrap credentials (Generated by setup.sh)
AUTHENTIK_BOOTSTRAP_PASSWORD=your_admin_password
AUTHENTIK_BOOTSTRAP_TOKEN=your_api_token
# AI Configuration
GEMINI_API_KEY=your_gemini_api_key
# OIDC Production Settings
# OIDC Production Settings (update with your domains)
OIDC_ISSUER=https://auth.example.com/application/o/kaboot/
OIDC_JWKS_URI=https://auth.example.com/application/o/kaboot/jwks/
@ -52,112 +87,14 @@ 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
## Docker Compose Files
Create a `docker-compose.prod.yml` for your production environment:
The project includes pre-configured compose files:
```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
- **`docker-compose.prod.yml`** - Production services (Authentik, Backend, PostgreSQL, Redis)
- **`docker-compose.caddy.yml`** - Caddy reverse proxy overlay
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
```
These files are ready to use - no need to create your own.
## HTTPS and Reverse Proxy
@ -195,7 +132,13 @@ kaboot.example.com {
}
auth.example.com {
reverse_proxy authentik-server:9000
reverse_proxy authentik-server:9000 {
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-Host {host}
transport http {
keepalive 30s
}
}
}
```
@ -212,7 +155,7 @@ This creates the `dist/` directory with production assets.
Use both compose files together:
```bash
docker compose -f docker-compose.yml -f docker-compose.caddy.yml up -d
docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml up -d
```
This will:
@ -225,7 +168,7 @@ This will:
Check that all services are running:
```bash
docker compose -f docker-compose.yml -f docker-compose.caddy.yml ps
docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml ps
```
View Caddy logs:
@ -234,10 +177,16 @@ View Caddy logs:
docker logs kaboot-caddy
```
Check Authentik blueprint was applied:
```bash
docker compose -f docker-compose.prod.yml logs authentik-server | grep -i blueprint
```
**Stopping the Stack**
```bash
docker compose -f docker-compose.yml -f docker-compose.caddy.yml down
docker compose -f docker-compose.prod.yml -f docker-compose.caddy.yml down
```
### Option 2: Nginx
@ -287,12 +236,37 @@ server {
## Authentik Configuration for Production
### Option A: Automated Setup with Blueprint (Recommended)
Use the production blueprint for automated configuration:
```bash
# 1. Copy and configure the production blueprint
cp authentik/blueprints/kaboot-setup-production.yaml.example \
authentik/blueprints/kaboot-setup-production.yaml
# 2. Edit the blueprint and update the domains
# Find the 'context' section and change:
# - kaboot_domain: kaboot.example.com -> your-app.com
# - auth_domain: auth.example.com -> auth.your-app.com
# 3. Remove the development blueprint (prevents conflicts)
rm authentik/blueprints/kaboot-setup.yaml
# 4. Start the stack - blueprint applies automatically
docker compose -f docker-compose.prod.yml up -d
```
### Option B: Manual Configuration
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.
### 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
@ -317,10 +291,12 @@ docker exec kaboot-postgresql pg_dump -U authentik authentik > authentik_backup_
## Security Checklist
- [ ] Change all default passwords (`PG_PASS`, `AUTHENTIK_SECRET_KEY`).
- [ ] Run `./scripts/setup.sh` to generate strong secrets (don't use defaults).
- [ ] Ensure `NODE_ENV` is set to `production`.
- [ ] Use HTTPS for all connections.
- [ ] Set `CORS_ORIGIN` to your specific frontend domain.
- [ ] Use HTTPS for all connections (Caddy handles this automatically).
- [ ] Set `CORS_ORIGIN` to your specific frontend domain (e.g., `https://kaboot.example.com`).
- [ ] Update `OIDC_ISSUER` and `OIDC_JWKS_URI` to use your auth domain with HTTPS.
- [ ] 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.
- [ ] Configure firewall to only allow ports 80 and 443 from the internet.

View file

@ -26,23 +26,38 @@ echo "Generating secrets..."
PG_PASS=$(openssl rand -base64 36 | tr -d '\n')
AUTHENTIK_SECRET_KEY=$(openssl rand -base64 60 | tr -d '\n')
AUTHENTIK_BOOTSTRAP_PASSWORD=$(openssl rand -base64 24 | tr -d '\n')
AUTHENTIK_BOOTSTRAP_TOKEN=$(openssl rand -base64 36 | 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"
sed -i '' "s|^AUTHENTIK_BOOTSTRAP_PASSWORD=.*|AUTHENTIK_BOOTSTRAP_PASSWORD=${AUTHENTIK_BOOTSTRAP_PASSWORD}|" "$ENV_FILE"
sed -i '' "s|^AUTHENTIK_BOOTSTRAP_TOKEN=.*|AUTHENTIK_BOOTSTRAP_TOKEN=${AUTHENTIK_BOOTSTRAP_TOKEN}|" "$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"
sed -i "s|^AUTHENTIK_BOOTSTRAP_PASSWORD=.*|AUTHENTIK_BOOTSTRAP_PASSWORD=${AUTHENTIK_BOOTSTRAP_PASSWORD}|" "$ENV_FILE"
sed -i "s|^AUTHENTIK_BOOTSTRAP_TOKEN=.*|AUTHENTIK_BOOTSTRAP_TOKEN=${AUTHENTIK_BOOTSTRAP_TOKEN}|" "$ENV_FILE"
fi
echo ""
echo "Created .env file with generated secrets."
echo ""
echo "Authentik admin credentials (save these):"
echo " Username: akadmin"
echo " Password: ${AUTHENTIK_BOOTSTRAP_PASSWORD}"
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 " 3. Wait for Authentik to start (~30 seconds)"
echo " 4. The Kaboot application is auto-configured via blueprint!"
echo ""
echo "Remaining manual steps:"
echo " - Set password for test user: docker compose exec authentik-server ak set_password kaboottest"
echo " - Create app password for service account via Authentik UI"
echo " - See docs/AUTHENTIK_SETUP.md for details"
echo ""