From 5cdafc7a4dec8ad077d9f3510c98a29759d765cb Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Tue, 13 Jan 2026 13:57:06 -0700 Subject: [PATCH] Implementation plan --- IMPLEMENTATION_PLAN.md | 409 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 409 insertions(+) create mode 100644 IMPLEMENTATION_PLAN.md diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000..db75bb2 --- /dev/null +++ b/IMPLEMENTATION_PLAN.md @@ -0,0 +1,409 @@ +# 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 manually with curl/Postman: + - [ ] 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 `src/main.tsx`: + - [ ] Import `AuthProvider` from `react-oidc-context` + - [ ] Import `oidcConfig` + - [ ] Wrap `` with `` + +### 3.4 Auth UI Components +- [ ] Create `src/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 `src/components/Landing.tsx`: + - [ ] Add `` to top-right corner + - [ ] Style consistently with existing design + +### 3.5 Authenticated Fetch Hook +- [ ] Create `src/hooks/useAuthenticatedFetch.ts`: + - [ ] Use `useAuth()` to get access token + - [ ] Create `authFetch` wrapper that: + - [ ] Adds `Authorization: Bearer ` 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 `src/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 `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 `` modal when open + - [ ] Handle quiz load: call `onLoadQuiz` prop with loaded quiz + +### 4.4 Types Update +- [ ] Modify `src/types.ts`: + - [ ] Add `SavedQuiz` interface (extends `Quiz` with id, source, dates) + - [ ] Add `QuizListItem` interface (for list view) + - [ ] Add `QuizSource` type: `'manual' | 'ai_generated'` + +--- + +## Phase 5: Save Integration + +### 5.1 Save After AI Generation +- [ ] Modify `src/hooks/useGame.ts`: + - [ ] Add `pendingQuizToSave` state + - [ ] After successful AI generation, set `pendingQuizToSave` + - [ ] Add `savePendingQuiz()` and `dismissSavePrompt()` functions + - [ ] Export these for UI to consume +- [ ] 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 `shouldSave` flag to `onFinalize` +- [ ] Modify `src/hooks/useGame.ts`: + - [ ] Update `finalizeManualQuiz` to accept save preference + - [ ] If save requested + authenticated, call `saveQuiz` + +### 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 +- [ ] 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 | Not Started | | +| Phase 2 | Not Started | | +| Phase 3 | Not Started | | +| Phase 4 | Not Started | | +| Phase 5 | Not Started | | +| Phase 6 | Not Started | | +| Phase 7 | Not Started | |