16 KiB
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.ymlwith all services:- PostgreSQL (Authentik database)
- Redis (Authentik cache)
- Authentik server
- Authentik worker
- Kaboot backend service
- Create
.env.examplewith required variables:PG_PASS- PostgreSQL passwordAUTHENTIK_SECRET_KEY- Authentik secretPG_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
.gitignorefor new files:.envauthentik/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
- Application name:
- Note down OIDC endpoints (issuer, JWKS URI, etc.)
- Navigate to
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.jsonwith dependencies:express- Web frameworkbetter-sqlite3- SQLite driverjsonwebtoken- JWT verificationjwks-rsa- JWKS client for Authentikuuid- ID generationcors- CORS middleware- Dev deps:
typescript,@types/*,tsx
- Create
tsconfig.jsonfor Node.js - Create
Dockerfilefor backend container
2.2 Database Layer
- Create
server/src/db/schema.sql:userstable (synced from OIDC claims)quizzestable (withuser_idforeign key)questionstable (withquiz_idforeign key)answer_optionstable (withquestion_idforeign 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
requireAuthmiddleware function:- Extract Bearer token from Authorization header
- Verify JWT signature against JWKS
- Validate issuer matches Authentik
- Attach decoded user to request
- Define
AuthenticatedRequestinterface
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 optionsPOST /api/quizzes- Save new quiz (upsert user from token)PUT /api/quizzes/:id- Update existing quizDELETE /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 hooksoidc-client-ts- Underlying OIDC client
- Run
npm install
3.2 OIDC Configuration
- Create
src/config/oidc.ts:- Define
oidcConfigobject:authority- Authentik issuer URLclient_id-kaboot-sparedirect_uri-${origin}/callbackpost_logout_redirect_uri-${origin}response_type-code(PKCE)scope-openid profile email offline_accessautomaticSilentRenew-trueuserStore-WebStorageStateStorewith localStorageonSigninCallback- Clean URL after redirect
- Define
3.3 Auth Provider Setup
- Modify
index.tsx:- Import
AuthProviderfromreact-oidc-context - Import
oidcConfig - Wrap
<App />with<AuthProvider {...oidcConfig}>
- Import
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
- Use
- Modify
components/Landing.tsx:- Add
<AuthButton />to top-right corner - Style consistently with existing design
- Add
3.5 Authenticated Fetch Hook
- Create
hooks/useAuthenticatedFetch.ts:- Use
useAuth()to get access token - Create
authFetchwrapper that:- Adds
Authorization: Bearer <token>header - Adds
Content-Type: application/json - Handles 401 by triggering silent renew
- Adds
- Export
{ authFetch, isAuthenticated }
- Use
Phase 4: Quiz Library Feature
4.1 Quiz Library Hook
- Create
src/hooks/useQuizLibrary.ts:- State:
quizzes,loading,error fetchQuizzes()- GET /api/quizzesloadQuiz(id)- GET /api/quizzes/:id, return QuizsaveQuiz(quiz, source, aiTopic?)- POST /api/quizzesdeleteQuiz(id)- DELETE /api/quizzes/:id- Handle loading and error states
- State:
4.2 Quiz Library UI
- Create
src/components/QuizLibrary.tsx:- Modal overlay design (consistent with app style)
- Header: "My Quizzes" with close button
- Search/filter input (optional, future enhancement)
- Quiz list:
- Show title, question count, source badge (AI/Manual), date
- Click to select
- Delete button with confirmation
- Footer: "Load Selected" and "Cancel" buttons
- Empty state: "No saved quizzes yet"
- Loading state: Skeleton/spinner
4.3 Landing Page Integration
- Modify
src/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
onLoadQuizprop with loaded quiz
4.4 Types Update
- Modify
src/types.ts:- Add
SavedQuizinterface (extendsQuizwith id, source, dates) - Add
QuizListIteminterface (for list view) - Add
QuizSourcetype:'manual' | 'ai_generated'
- Add
Phase 5: Save Integration
5.1 Save After AI Generation
- Modify
src/hooks/useGame.ts:- Add
pendingQuizToSavestate - After successful AI generation, set
pendingQuizToSave - Add
savePendingQuiz()anddismissSavePrompt()functions - Export these for UI to consume
- Add
- Create
src/components/SaveQuizPrompt.tsx:- Modal asking "Save this quiz to your library?"
- Show quiz title
- "Save" and "Skip" buttons
- Only show when authenticated
5.2 Save in Quiz Creator
- Modify
src/components/QuizCreator.tsx:- Add checkbox or toggle: "Save to my library"
- Pass
shouldSaveflag toonFinalize
- Modify
src/hooks/useGame.ts:- Update
finalizeManualQuizto accept save preference - If save requested + authenticated, call
saveQuiz
- Update
5.3 Load Quiz Flow
- Modify
src/hooks/useGame.ts:- Add
loadSavedQuiz(quiz: Quiz)function - Initialize game state with loaded quiz
- Transition to LOBBY state
- Add
- 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-toastor 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 | Not Started | |
| Phase 5 | Not Started | |
| Phase 6 | Not Started | |
| Phase 7 | Not Started |