# 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 - [x] Create `docker-compose.yml` with all services: - [x] PostgreSQL (Authentik database) - [x] Redis (Authentik cache) - [x] Authentik server - [x] Authentik worker - [x] Kaboot backend service - [x] Create `.env.example` with required variables: - [x] `PG_PASS` - PostgreSQL password - [x] `AUTHENTIK_SECRET_KEY` - Authentik secret - [x] `PG_USER`, `PG_DB` - Optional customization - [x] Create setup script to generate secrets (`scripts/setup.sh`) - [x] Add `authentik/` directory structure for volumes: - [x] `authentik/media/` - [x] `authentik/certs/` - [x] `authentik/custom-templates/` - [x] Update `.gitignore` for new files: - [x] `.env` - [x] `authentik/media/*` - [x] `authentik/certs/*` - [x] `server/data/` ### 1.2 Authentik Configuration Documentation - [x] Document initial setup steps in `docs/AUTHENTIK_SETUP.md`: - [x] Navigate to `http://localhost:9000/if/flow/initial-setup/` - [x] Create admin account - [x] Create OAuth2/OIDC Application + Provider: - [x] Application name: `Kaboot` - [x] Application slug: `kaboot` - [x] Provider type: `OAuth2/OIDC` - [x] Client type: `Public` (SPA with PKCE) - [x] Client ID: `kaboot-spa` - [x] Redirect URIs: `http://localhost:5173/callback`, `http://localhost:5173/silent-renew.html` - [x] Scopes: `openid`, `profile`, `email`, `offline_access` - [x] Note down OIDC endpoints (issuer, JWKS URI, etc.) --- ## Phase 2: Backend API Development ### 2.1 Project Setup - [x] Create `server/` directory structure: ``` server/ ├── Dockerfile ├── package.json ├── tsconfig.json └── src/ ├── index.ts ├── db/ ├── middleware/ ├── routes/ └── services/ ``` - [x] Initialize `package.json` with dependencies: - [x] `express` - Web framework - [x] `better-sqlite3` - SQLite driver - [x] `jsonwebtoken` - JWT verification - [x] `jwks-rsa` - JWKS client for Authentik - [x] `uuid` - ID generation - [x] `cors` - CORS middleware - [x] Dev deps: `typescript`, `@types/*`, `tsx` - [x] Create `tsconfig.json` for Node.js - [x] Create `Dockerfile` for backend container ### 2.2 Database Layer - [x] Create `server/src/db/schema.sql`: - [x] `users` table (synced from OIDC claims) - [x] `quizzes` table (with `user_id` foreign key) - [x] `questions` table (with `quiz_id` foreign key) - [x] `answer_options` table (with `question_id` foreign key) - [x] Indexes for foreign keys - [x] Create `server/src/db/connection.ts`: - [x] Initialize better-sqlite3 connection - [x] Run schema on startup - [x] Export db instance ### 2.3 Authentication Middleware - [x] Create `server/src/middleware/auth.ts`: - [x] JWKS client setup pointing to Authentik - [x] `requireAuth` middleware function: - [x] Extract Bearer token from Authorization header - [x] Verify JWT signature against JWKS - [x] Validate issuer matches Authentik - [x] Attach decoded user to request - [x] Define `AuthenticatedRequest` interface ### 2.4 API Routes - [x] Create `server/src/routes/quizzes.ts`: - [x] `GET /api/quizzes` - List user's quizzes (with question count) - [x] `GET /api/quizzes/:id` - Get full quiz with questions and options - [x] `POST /api/quizzes` - Save new quiz (upsert user from token) - [x] `PUT /api/quizzes/:id` - Update existing quiz - [x] `DELETE /api/quizzes/:id` - Delete quiz (verify ownership) - [x] Create `server/src/routes/users.ts`: - [x] `GET /api/users/me` - Get current user profile - [x] Create `server/src/index.ts`: - [x] Express app setup - [x] CORS configuration (allow frontend origin) - [x] JSON body parser - [x] Mount routes - [x] Error handling middleware - [x] Start server on port 3001 ### 2.5 Backend Testing - [x] Test API with automated test suite: - [x] Verify 401 without token - [x] Verify endpoints work with valid Authentik token - [x] Verify quiz CRUD operations - [x] Verify user sync from token claims --- ## Phase 3: Frontend Authentication ### 3.1 Dependencies - [x] Add to `package.json`: - [x] `react-oidc-context` - React OIDC hooks - [x] `oidc-client-ts` - Underlying OIDC client - [ ] Run `npm install` ### 3.2 OIDC Configuration - [x] Create `src/config/oidc.ts`: - [x] Define `oidcConfig` object: - [x] `authority` - Authentik issuer URL - [x] `client_id` - `kaboot-spa` - [x] `redirect_uri` - `${origin}/callback` - [x] `post_logout_redirect_uri` - `${origin}` - [x] `response_type` - `code` (PKCE) - [x] `scope` - `openid profile email offline_access` - [x] `automaticSilentRenew` - `true` - [x] `userStore` - `WebStorageStateStore` with localStorage - [x] `onSigninCallback` - Clean URL after redirect ### 3.3 Auth Provider Setup - [x] Modify `index.tsx`: - [x] Import `AuthProvider` from `react-oidc-context` - [x] Import `oidcConfig` - [x] Wrap `` with `` ### 3.4 Auth UI Components - [x] Create `components/AuthButton.tsx`: - [x] Use `useAuth()` hook - [x] Show loading state while auth initializing - [x] Show "Sign In" button when unauthenticated - [x] Show username + "Sign Out" button when authenticated - [x] Modify `components/Landing.tsx`: - [x] Add `` to top-right corner - [x] Style consistently with existing design ### 3.5 Authenticated Fetch Hook - [x] Create `hooks/useAuthenticatedFetch.ts`: - [x] Use `useAuth()` to get access token - [x] Create `authFetch` wrapper that: - [x] Adds `Authorization: Bearer ` header - [x] Adds `Content-Type: application/json` - [x] Handles 401 by triggering silent renew - [x] Export `{ authFetch, isAuthenticated }` --- ## Phase 4: Quiz Library Feature ### 4.1 Quiz Library Hook - [x] Create `hooks/useQuizLibrary.ts`: - [x] State: `quizzes`, `loading`, `error` - [x] `fetchQuizzes()` - GET /api/quizzes - [x] `loadQuiz(id)` - GET /api/quizzes/:id, return Quiz - [x] `saveQuiz(quiz, source, aiTopic?)` - POST /api/quizzes - [x] `deleteQuiz(id)` - DELETE /api/quizzes/:id - [x] Handle loading and error states ### 4.2 Quiz Library UI - [x] Create `components/QuizLibrary.tsx`: - [x] Modal overlay design (consistent with app style) - [x] Header: "My Library" with close button - [x] Quiz list: - [x] Show title, question count, source badge (AI/Manual), date - [x] Click to load quiz - [x] Delete button with confirmation - [x] Empty state: "No saved quizzes yet" - [x] Loading state: Spinner ### 4.3 Landing Page Integration - [x] Modify `components/Landing.tsx`: - [x] Add "My Quizzes" button (only visible when authenticated) - [x] Add state for quiz library modal visibility - [x] Render `` modal when open - [x] Handle quiz load: call `onLoadQuiz` prop with loaded quiz ### 4.4 Types Update - [x] Modify `types.ts`: - [x] Add `SavedQuiz` interface (extends `Quiz` with id, source, dates) - [x] Add `QuizListItem` interface (for list view) - [x] Add `QuizSource` type: `'manual' | 'ai_generated'` ### 4.5 Game Hook Integration - [x] Modify `hooks/useGame.ts`: - [x] Add `loadSavedQuiz(quiz)` function - [x] Export for App.tsx to consume --- ## Phase 5: Save Integration ### 5.1 Save After AI Generation - [x] Modify `hooks/useGame.ts`: - [x] Add `pendingQuizToSave` state - [x] After successful AI generation, set `pendingQuizToSave` - [x] Add `dismissSavePrompt()` function - [x] Export these for UI to consume - [x] Create `components/SaveQuizPrompt.tsx`: - [x] Modal asking "Save this quiz to your library?" - [x] Show quiz title - [x] "Save" and "Skip" buttons - [x] Loading state while saving - [x] Wire up in `App.tsx`: - [x] Show SaveQuizPrompt in LOBBY state when authenticated and pendingQuizToSave exists - [x] Handle save via useQuizLibrary hook ### 5.2 Save in Quiz Creator - [x] Modify `components/QuizCreator.tsx`: - [x] Add "Save to my library" checkbox (only shown when authenticated) - [x] Pass `saveToLibrary` flag to `onFinalize` - [x] Modify `hooks/useGame.ts`: - [x] Update `finalizeManualQuiz` to accept save preference - [x] If save requested, set `pendingQuizToSave` ### 5.3 Load Quiz Flow (Already done in Phase 4) - [x] `loadSavedQuiz(quiz: Quiz)` function in useGame.ts - [x] Wire up from Landing → QuizLibrary → useGame --- ## Phase 6: Polish & Error Handling ### 6.1 Loading States - [x] Add loading indicators: - [x] Quiz library list loading - [x] Quiz loading when selected - [x] Save operation in progress - [x] Disable buttons during async operations ### 6.2 Error Handling - [x] Display user-friendly error messages: - [x] Failed to load quiz library - [x] Failed to save quiz - [x] Failed to delete quiz - [x] Network/auth errors - [x] Add retry mechanisms where appropriate ### 6.3 Toast Notifications (Optional) - [x] Add `react-hot-toast` or similar - [x] Show success toasts: - [x] "Quiz saved successfully" - [x] "Quiz deleted" - [x] Show error toasts for failures ### 6.4 Edge Cases - [x] Handle auth token expiry gracefully - [x] Handle offline state - [x] Handle concurrent save attempts - [x] 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 | **COMPLETE** | Toast notifications, loading states, error handling, edge cases | | Phase 7 | Not Started | |