kaboot/IMPLEMENTATION_PLAN.md

16 KiB

Kaboot: Account-Based Quiz Persistence with Authentik + SQLite

Overview

Add user accounts via Authentik (OIDC) and persist quizzes to SQLite database. Users can save custom and AI-generated quizzes to their account and load them later.

Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                           Docker Compose Stack                               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌───────────┐ │
│  │   Authentik  │    │  PostgreSQL  │    │    Redis     │    │  Kaboot   │ │
│  │   (IdP)      │◀───│  (Authentik  │    │  (Authentik  │    │  Backend  │ │
│  │   :9000      │    │   DB)        │    │   Cache)     │    │  :3001    │ │
│  └──────┬───────┘    └──────────────┘    └──────────────┘    └─────┬─────┘ │
│         │                                                           │       │
│         │ OIDC/OAuth2                                    SQLite    │       │
│         │                                                │         │       │
│  ┌──────▼───────────────────────────────────────────────▼─────────▼─────┐ │
│  │                        Kaboot Frontend (Vite)                         │ │
│  │                              :5173                                    │ │
│  └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘

Phase 1: Infrastructure Setup

1.1 Docker Compose Configuration

  • Create docker-compose.yml with all services:
    • PostgreSQL (Authentik database)
    • Redis (Authentik cache)
    • Authentik server
    • Authentik worker
    • Kaboot backend service
  • Create .env.example with required variables:
    • PG_PASS - PostgreSQL password
    • AUTHENTIK_SECRET_KEY - Authentik secret
    • PG_USER, PG_DB - Optional customization
  • Create setup script to generate secrets (scripts/setup.sh)
  • Add authentik/ directory structure for volumes:
    • authentik/media/
    • authentik/certs/
    • authentik/custom-templates/
  • Update .gitignore for new files:
    • .env
    • authentik/media/*
    • authentik/certs/*
    • server/data/

1.2 Authentik Configuration Documentation

  • Document initial setup steps in docs/AUTHENTIK_SETUP.md:
    • Navigate to http://localhost:9000/if/flow/initial-setup/
    • Create admin account
    • Create OAuth2/OIDC Application + Provider:
      • Application name: Kaboot
      • Application slug: kaboot
      • Provider type: OAuth2/OIDC
      • Client type: Public (SPA with PKCE)
      • Client ID: kaboot-spa
      • Redirect URIs: http://localhost:5173/callback, http://localhost:5173/silent-renew.html
      • Scopes: openid, profile, email, offline_access
    • Note down OIDC endpoints (issuer, JWKS URI, etc.)

Phase 2: Backend API Development

2.1 Project Setup

  • Create server/ directory structure:
    server/
    ├── Dockerfile
    ├── package.json
    ├── tsconfig.json
    └── src/
        ├── index.ts
        ├── db/
        ├── middleware/
        ├── routes/
        └── services/
    
  • Initialize package.json with dependencies:
    • express - Web framework
    • better-sqlite3 - SQLite driver
    • jsonwebtoken - JWT verification
    • jwks-rsa - JWKS client for Authentik
    • uuid - ID generation
    • cors - CORS middleware
    • Dev deps: typescript, @types/*, tsx
  • Create tsconfig.json for Node.js
  • Create Dockerfile for backend container

2.2 Database Layer

  • Create server/src/db/schema.sql:
    • users table (synced from OIDC claims)
    • quizzes table (with user_id foreign key)
    • questions table (with quiz_id foreign key)
    • answer_options table (with question_id foreign key)
    • Indexes for foreign keys
  • Create server/src/db/connection.ts:
    • Initialize better-sqlite3 connection
    • Run schema on startup
    • Export db instance

2.3 Authentication Middleware

  • Create server/src/middleware/auth.ts:
    • JWKS client setup pointing to Authentik
    • requireAuth middleware function:
      • Extract Bearer token from Authorization header
      • Verify JWT signature against JWKS
      • Validate issuer matches Authentik
      • Attach decoded user to request
    • Define AuthenticatedRequest interface

2.4 API Routes

  • Create server/src/routes/quizzes.ts:
    • GET /api/quizzes - List user's quizzes (with question count)
    • GET /api/quizzes/:id - Get full quiz with questions and options
    • POST /api/quizzes - Save new quiz (upsert user from token)
    • PUT /api/quizzes/:id - Update existing quiz
    • DELETE /api/quizzes/:id - Delete quiz (verify ownership)
  • Create server/src/routes/users.ts:
    • GET /api/users/me - Get current user profile
  • Create server/src/index.ts:
    • Express app setup
    • CORS configuration (allow frontend origin)
    • JSON body parser
    • Mount routes
    • Error handling middleware
    • Start server on port 3001

2.5 Backend Testing

  • Test API with automated test suite:
    • Verify 401 without token
    • Verify endpoints work with valid Authentik token
    • Verify quiz CRUD operations
    • Verify user sync from token claims

Phase 3: Frontend Authentication

3.1 Dependencies

  • Add to package.json:
    • react-oidc-context - React OIDC hooks
    • oidc-client-ts - Underlying OIDC client
  • Run npm install

3.2 OIDC Configuration

  • Create src/config/oidc.ts:
    • Define oidcConfig object:
      • authority - Authentik issuer URL
      • client_id - kaboot-spa
      • redirect_uri - ${origin}/callback
      • post_logout_redirect_uri - ${origin}
      • response_type - code (PKCE)
      • scope - openid profile email offline_access
      • automaticSilentRenew - true
      • userStore - WebStorageStateStore with localStorage
      • onSigninCallback - Clean URL after redirect

3.3 Auth Provider Setup

  • Modify index.tsx:
    • Import AuthProvider from react-oidc-context
    • Import oidcConfig
    • Wrap <App /> with <AuthProvider {...oidcConfig}>

3.4 Auth UI Components

  • Create components/AuthButton.tsx:
    • Use useAuth() hook
    • Show loading state while auth initializing
    • Show "Sign In" button when unauthenticated
    • Show username + "Sign Out" button when authenticated
  • Modify components/Landing.tsx:
    • Add <AuthButton /> to top-right corner
    • Style consistently with existing design

3.5 Authenticated Fetch Hook

  • Create hooks/useAuthenticatedFetch.ts:
    • Use useAuth() to get access token
    • Create authFetch wrapper that:
      • Adds Authorization: Bearer <token> header
      • Adds Content-Type: application/json
      • Handles 401 by triggering silent renew
    • Export { authFetch, isAuthenticated }

Phase 4: Quiz Library Feature

4.1 Quiz Library Hook

  • Create hooks/useQuizLibrary.ts:
    • State: quizzes, loading, error
    • fetchQuizzes() - GET /api/quizzes
    • loadQuiz(id) - GET /api/quizzes/:id, return Quiz
    • saveQuiz(quiz, source, aiTopic?) - POST /api/quizzes
    • deleteQuiz(id) - DELETE /api/quizzes/:id
    • Handle loading and error states

4.2 Quiz Library UI

  • Create components/QuizLibrary.tsx:
    • Modal overlay design (consistent with app style)
    • Header: "My Library" with close button
    • Quiz list:
      • Show title, question count, source badge (AI/Manual), date
      • Click to load quiz
      • Delete button with confirmation
    • Empty state: "No saved quizzes yet"
    • Loading state: Spinner

4.3 Landing Page Integration

  • Modify components/Landing.tsx:
    • Add "My Quizzes" button (only visible when authenticated)
    • Add state for quiz library modal visibility
    • Render <QuizLibrary /> modal when open
    • Handle quiz load: call onLoadQuiz prop with loaded quiz

4.4 Types Update

  • Modify types.ts:
    • Add SavedQuiz interface (extends Quiz with id, source, dates)
    • Add QuizListItem interface (for list view)
    • Add QuizSource type: 'manual' | 'ai_generated'

4.5 Game Hook Integration

  • Modify hooks/useGame.ts:
    • Add loadSavedQuiz(quiz) function
    • Export for App.tsx to consume

Phase 5: Save Integration

5.1 Save After AI Generation

  • Modify hooks/useGame.ts:
    • Add pendingQuizToSave state
    • After successful AI generation, set pendingQuizToSave
    • Add dismissSavePrompt() function
    • Export these for UI to consume
  • Create components/SaveQuizPrompt.tsx:
    • Modal asking "Save this quiz to your library?"
    • Show quiz title
    • "Save" and "Skip" buttons
    • Loading state while saving
  • Wire up in App.tsx:
    • Show SaveQuizPrompt in LOBBY state when authenticated and pendingQuizToSave exists
    • Handle save via useQuizLibrary hook

5.2 Save in Quiz Creator

  • Modify components/QuizCreator.tsx:
    • Add "Save to my library" checkbox (only shown when authenticated)
    • Pass saveToLibrary flag to onFinalize
  • Modify hooks/useGame.ts:
    • Update finalizeManualQuiz to accept save preference
    • If save requested, set pendingQuizToSave

5.3 Load Quiz Flow (Already done in Phase 4)

  • loadSavedQuiz(quiz: Quiz) function in useGame.ts
  • Wire up from Landing → QuizLibrary → useGame

Phase 6: Polish & Error Handling

6.1 Loading States

  • Add loading indicators:
    • Quiz library list loading
    • Quiz loading when selected
    • Save operation in progress
  • Disable buttons during async operations

6.2 Error Handling

  • Display user-friendly error messages:
    • Failed to load quiz library
    • Failed to save quiz
    • Failed to delete quiz
    • Network/auth errors
  • Add retry mechanisms where appropriate

6.3 Toast Notifications (Optional)

  • Add react-hot-toast or similar
  • Show success toasts:
    • "Quiz saved successfully"
    • "Quiz deleted"
  • Show error toasts for failures

6.4 Edge Cases

  • Handle auth token expiry gracefully
  • Handle offline state
  • Handle concurrent save attempts
  • Validate quiz data before save

Phase 7: Documentation & Deployment

7.1 Documentation

  • Update main README.md:
    • Add Docker Compose setup instructions
    • Document environment variables
    • Add Authentik configuration steps
  • Create docs/AUTHENTIK_SETUP.md (detailed IdP setup)
  • Create docs/API.md (backend API documentation)

7.2 Production Considerations

  • Document production deployment:
    • HTTPS setup (reverse proxy)
    • Update redirect URIs for production domain
    • Database backup strategy
    • Authentik email configuration
  • Add health check endpoints
  • Add logging configuration

File Structure Reference

kaboot/
├── docker-compose.yml           # NEW
├── .env.example                 # NEW
├── .env                         # NEW (gitignored)
├── scripts/
│   └── setup.sh                 # NEW
├── docs/
│   ├── AUTHENTIK_SETUP.md       # NEW
│   └── API.md                   # NEW
├── server/                      # NEW
│   ├── Dockerfile
│   ├── package.json
│   ├── tsconfig.json
│   └── src/
│       ├── index.ts
│       ├── db/
│       │   ├── connection.ts
│       │   └── schema.sql
│       ├── middleware/
│       │   └── auth.ts
│       ├── routes/
│       │   ├── quizzes.ts
│       │   └── users.ts
│       └── services/
│           └── quiz.service.ts
├── authentik/                   # NEW (gitignored contents)
│   ├── media/
│   ├── certs/
│   └── custom-templates/
├── src/
│   ├── main.tsx                 # MODIFY
│   ├── App.tsx                  # MODIFY
│   ├── types.ts                 # MODIFY
│   ├── config/
│   │   └── oidc.ts              # NEW
│   ├── hooks/
│   │   ├── useGame.ts           # MODIFY
│   │   ├── useAuthenticatedFetch.ts  # NEW
│   │   └── useQuizLibrary.ts    # NEW
│   └── components/
│       ├── Landing.tsx          # MODIFY
│       ├── QuizCreator.tsx      # MODIFY
│       ├── AuthButton.tsx       # NEW
│       ├── QuizLibrary.tsx      # NEW
│       └── SaveQuizPrompt.tsx   # NEW
└── package.json                 # MODIFY

Technical Decisions

Decision Choice Rationale
Auth Provider Authentik Self-hosted, full-featured OIDC, Docker-friendly
OIDC Library react-oidc-context Lightweight, hooks-based, well-maintained
Backend Framework Express Simple, widely known, sufficient for this use case
Database SQLite (better-sqlite3) Zero config, file-based, perfect for single-server
Token Storage localStorage Required for SPA silent renew; acceptable for this app

Estimated Effort

Phase Description Estimate
1 Infrastructure Setup 1 day
2 Backend API Development 1-2 days
3 Frontend Authentication 1 day
4 Quiz Library Feature 1 day
5 Save Integration 0.5 day
6 Polish & Error Handling 0.5 day
7 Documentation & Deployment 0.5 day
Total 5-6 days

Progress Tracking

Last Updated: 2026-01-13

Phase Status Notes
Phase 1 COMPLETE Docker Compose, .env, setup script, Authentik docs
Phase 2 COMPLETE Backend API with Express, SQLite, JWT auth, Quiz CRUD
Phase 3 COMPLETE OIDC config, AuthProvider, AuthButton, useAuthenticatedFetch
Phase 4 COMPLETE useQuizLibrary hook, QuizLibrary modal, Landing integration
Phase 5 COMPLETE SaveQuizPrompt modal, QuizCreator save checkbox, save integration
Phase 6 Not Started
Phase 7 Not Started