- API Key Middleware für alle /api/ Endpoints (X-API-Key, Bearer, Query) - API Key Management Routes (generate, revoke, status) - MCP Tools erweitert: 12 Tools (war 3) inkl. bulk ops, dashboard, search - Audit Logging für alle API/MCP Aktionen - docs/API-REFERENCE.md - vollständige API Dokumentation - docs/ARCHITECTURE.md - Architektur-Übersicht - Vorbereitung für NotiBot-Integration
21 KiB
TaskFlow Refactoring Review
Datum: 2026-02-03
Reviewer: AI Code Review (Senior Level)
Scope: Full-Stack Analyse – Server, Shared, Client
Executive Summary
TaskFlow ist eine funktional umfangreiche Task-Management-App mit Gamification, ADHD-Features, AI-Integration und Social-Features. Der Code funktioniert, hat aber erhebliche architektonische und sicherheitstechnische Probleme, die vor einem Production-Release adressiert werden müssen.
Kernprobleme:
routes.tsist ein 2345-Zeilen-Monolith – alle 96 Endpoints in einer Datei, keine klare Trennung- Kritische Security-Issues – Debug-Endpoints ohne Auth, 2FA-Codes im Response, hardcoded Session Secret
- Doppelte Route-Registrierungen –
DELETE /api/tasks/:idist zweimal definiert (Zeile 889 + 1277) - Doppelter Label-Delete –
storage.deleteLabel()wird zweimal aufgerufen (Zeile 970 + 974) - Kein Rate Limiting, kein CORS, kein Helmet – grundlegende Security-Middleware fehlt komplett
- Massive
any-Nutzung – 20+any-Types allein in routes.ts - N+1 Query-Probleme in
getTasksForUser()und Label-Share-Views
Gesamtbewertung: ⚠️ Funktional, aber nicht produktionsreif. Geschätzter Refactoring-Aufwand: 3-4 Wochen für eine Person.
Kritische Issues
🔴 SEC-1: Debug-Endpoints ohne Authentifizierung (KRITISCH)
Datei: server/routes.ts, Zeilen 93-113
app.post("/api/debug/fix-settings", async (req, res) => {
await storage.setSystemSettings("registration_enabled", "true");
// ... resets SMTP, enables registration
});
app.post("/api/debug/force-enable-registration", async (req, res) => {
await storage.setSystemSettings("registration_enabled", "true");
});
Problem: Jeder kann ohne Authentifizierung die Registrierung aktivieren und SMTP-Einstellungen überschreiben. Ein Angreifer kann sich dadurch selbst registrieren und das System kompromittieren.
Fix: Sofort entfernen oder hinter isAdmin-Middleware + Environment-Check (NODE_ENV !== 'production') schützen.
🔴 SEC-2: 2FA-Code im API-Response exponiert (KRITISCH)
Datei: server/auth.ts, Zeilen 182 und 262
return res.status(200).json({
message: "2fa_required",
userId: user.id,
email: user.email,
debugCode: code // ← 2FA-Code im Klartext!
});
Problem: Der 2FA-Code wird direkt im Login-Response zurückgegeben. Das macht 2FA komplett wirkungslos – jeder Angreifer, der das Login-Request sieht, hat den Code.
Fix: debugCode entfernen. Nur in NODE_ENV === 'development' als Console-Log.
🔴 SEC-3: Hardcoded Session Secret (KRITISCH)
Datei: server/auth.ts, Zeile 27
secret: process.env.SESSION_SECRET || "s3cr3t_m3ss4g3",
Problem: Wenn SESSION_SECRET nicht gesetzt ist, wird ein bekanntes Secret verwendet. Session-Cookies können damit gefälscht werden.
Fix: Bei fehlendem SESSION_SECRET in Production den Server nicht starten lassen:
if (!process.env.SESSION_SECRET && process.env.NODE_ENV === 'production') {
throw new Error("SESSION_SECRET must be set in production!");
}
🔴 SEC-4: 2FA Fail-Open – Immer bypass (KRITISCH)
Datei: server/auth.ts, Zeile 170
if (true) { // ← IMMER true!
return res.status(200).json({
message: "2fa_required",
...
});
}
Die Bedingung if (true) macht den Code-Pfad, der prüft ob die E-Mail tatsächlich gesendet wurde, irrelevant. Die Variable sent wird nie ausgewertet. Der Kommentar sagt "Fail-open for testing" – das muss in Production gefixt werden.
🔴 SEC-5: Fehlende Auth-Checks auf mehreren Endpoints
| Endpoint | Problem |
|---|---|
GET /api/labels/:id |
Kein isAuthenticated() Check |
POST /api/labels |
Kein expliziter Auth-Check (nur req.user wird genutzt) |
PATCH /api/labels/:id |
Kein Auth-Check |
DELETE /api/labels/:id |
Kein Auth-Check, kein Ownership-Check |
GET /api/notes |
Kein Auth-Check |
POST /api/notes |
Kein Auth-Check |
GET /api/goals |
Kein Auth-Check |
POST /api/goals |
Kein Auth-Check |
GET /api/rewards |
Kein Auth-Check |
GET /api/leaderboard |
Kein Auth-Check (gewollt?) |
🔴 SEC-6: Fehlende Security-Middleware
Nicht vorhanden:
- Rate Limiting – Brute-Force auf Login/2FA möglich
- Helmet.js – Keine Security-Header (CSP, X-Frame-Options, etc.)
- CORS – Keine Origin-Einschränkung
- CSRF Protection – Keine CSRF-Tokens
- Input Sanitization – Kein XSS-Schutz auf user-generated Content
🟡 SEC-7: Gemini API-Key in URL exponiert
Datei: server/ai.ts
const url = `...?key=${apiKey}`;
Google Gemini API-Key wird als Query-Parameter gesendet und landet damit in Server-Logs und Browser-History.
Architektur-Empfehlungen
ARCH-1: routes.ts aufteilen (Höchste Priorität)
Aktuell: 1 Datei, 2345 Zeilen, 96 Endpoints, 0 Separation of Concerns.
Vorschlag: Router-Module
server/
├── routes/
│ ├── index.ts # Router-Registry
│ ├── auth.routes.ts # Login, Register, 2FA, Password Reset
│ ├── admin.routes.ts # User-Mgmt, Settings, Audit Logs
│ ├── task.routes.ts # CRUD Tasks, Sharing, Subtasks
│ ├── label.routes.ts # CRUD Labels, Sharing
│ ├── ai.routes.ts # Chat, Analyze, Schedule, Breakdown
│ ├── social.routes.ts # Leaderboard, Search, Share All
│ ├── gamification.routes.ts # Rewards, XP, Goals, Challenges
│ ├── adhd.routes.ts # Break Logs, Energy, Focus, Body Doubling
│ ├── push.routes.ts # Push Notifications
│ └── mcp.routes.ts # MCP Server
├── middleware/
│ ├── auth.middleware.ts
│ ├── rate-limit.middleware.ts
│ └── validation.middleware.ts
Geschätzter Aufwand: ~2 Tage, reines Code-Moving ohne Logikänderung.
ARCH-2: Storage-Interface ist zu breit
IStorage hat 80+ Methoden – das ist kein Interface mehr, das ist ein God Object.
Vorschlag: Repository Pattern
interface IUserRepository { ... }
interface ITaskRepository { ... }
interface ILabelRepository { ... }
interface IGamificationRepository { ... }
// etc.
Jedes Repository wird separat implementiert (Mem + DB). Die Service-Layer-Klassen (GamificationService, AiService) erhalten nur die Repositories, die sie brauchen.
ARCH-3: MemStorage als separate Implementierung beibehalten, aber isoliert
MemStorage hat viele Stubs (throw new Error("Not implemented")). Das ist fragil. Entweder:
- Option A: MemStorage komplett pflegen (Test-Support)
- Option B: MemStorage entfernen und nur mit DB + Testcontainer arbeiten
ARCH-4: Service Layer konsistent nutzen
Aktuell: Services existieren (GamificationService, AiService, RecurrenceService, EmailService), aber die Logik ist trotzdem großteils in routes.ts verstreut.
Beispiel: calculateXP() (Zeile 1393) ist eine lose Funktion in routes.ts, obwohl GamificationService.getXPForSource() existiert. calculateXP wird nie aufgerufen → Dead Code.
ARCH-5: DB Migration ist fragil
Datei: server/db.ts, Zeilen 56-85
runMigrations() erstellt nur 3 Tabellen (users, labels, tasks) mit minimalem Schema. Das Schema in shared/schema.ts hat 20+ Tabellen. Die Funktion ist veraltet und gefährlich, weil sie existierende Tabellen nicht aktualisiert.
Fix: Drizzle Kit Migrations (drizzle-kit generate + drizzle-kit migrate) verwenden statt manueller SQL.
Code-Qualität
DUP-1: Doppelte DELETE Route für Tasks
Datei: server/routes.ts
Zeile 889: app.delete("/api/tasks/:id", ...) ← mit Auth + Ownership-Check
Zeile 1277: app.delete("/api/tasks/:id", ...) ← OHNE Auth-Check!
Express registriert beide, aber nur die erste wird ausgeführt (Zeile 889). Die zweite (1277) ist Dead Code, aber ihre Existenz zeigt Copy-Paste-Probleme. Die zweite hat keinen Auth-Check – wäre sie die erste, hätte jeder Tasks löschen können.
DUP-2: Doppelter deleteLabel()-Aufruf
Datei: server/routes.ts, Zeilen 970-974
const success = await storage.deleteLabel(req.params.id);
if (!success) {
return res.status(404).json({ error: "Label not found" });
}
await storage.deleteLabel(req.params.id); // ← Zweiter Delete-Aufruf!
Das Label wird zweimal gelöscht. Der zweite Call ist ein No-Op (already deleted), aber es ist ein Bug.
DUP-3: Doppelte createReward()-Deklaration im Interface
Datei: server/storage.ts, Zeilen 79-80
createReward(reward: InsertReward): Promise<Reward>;
createReward(reward: InsertReward): Promise<Reward>; // Duplikat
DUP-4: AI Context-Building ist copy-pasted
Der User-Context-Block (User Context: - User ID: ...) wird in mindestens 3 Stellen fast identisch gebaut:
POST /api/ai/chat(Zeile ~680)PUT /api/ai/chat/:messageId(Zeile ~800)POST /api/ai/analyze-completion(Zeile ~740)
Fix: buildUserContext(user: User, tasks: Task[]): string Utility-Funktion extrahieren.
DUP-5: Auth-Check Pattern wiederholt sich 50+ mal
if (!req.isAuthenticated()) return res.sendStatus(401);
const userId = (req.user as User).id;
Fix: Express-Middleware:
function requireAuth(req, res, next) {
if (!req.isAuthenticated()) return res.sendStatus(401);
next();
}
DUP-6: AuditLog-Erstellung ist boilerplate-intensiv
Fast jeder Endpoint hat:
await storage.createAuditLog({
userId: (req.user as User).id,
action: "...",
entityType: "...",
entityId: ...,
details: ...,
source: "USER"
});
Fix: Audit-Middleware oder Decorator-Pattern.
TYPE-1: any-Types (20+ Vorkommen)
| Datei | Zeile | Problem |
|---|---|---|
routes.ts |
21 | isAdmin(req: any, res: any, next: any) |
routes.ts |
366 | const settings: any = {} |
routes.ts |
1393 | calculateXP(task: any) |
routes.ts |
1547 | const updates: any = {} |
routes.ts |
1835 | const exportData: any = {} |
routes.ts |
2036 | (user.adhdSettings as any) |
storage.ts |
80+ | ADHD methods: any params und returns |
recurrence.ts |
39 | const newTask: any = { ... } |
gamification.ts |
Div. | awardXP overloads mit inkonsistenter Signatur |
Fix: Proper TypeScript Interfaces für alle. Express Request/Response Types mit req.user über Declaration Merging definieren.
TYPE-2: Inkonsistente GamificationService.awardXP Signatur
In gamification.ts:
async awardXP(userId: string, source: string, amount?: number, details?: any)
In routes.ts Zeile 1925 (log-break):
await gamificationService.awardXP(userId, 10, 'break_taken', undefined, { breakType });
Hier wird 10 als source und 'break_taken' als amount übergeben – die Parameter sind vertauscht! Das führt zu falschen XP-Berechnungen.
Performance
PERF-1: N+1 Queries in getTasksForUser() (DbStorage)
Datei: server/storage.ts, Zeilen ~940-1000
async getTasksForUser(userId: string): Promise<Task[]> {
// Query 1: My tasks (with LEFT JOIN!)
const resultRaw = await this.db.select(...)...
// Query 2: Shared tasks links
const sharedLinks = await this.db.select()...
// Query 3: Shared tasks by IDs
sharedTasks = await this.db.select()...
// Query 4: Global access grants
const accessGrants = await this.db.select()...
// Query 5: Tasks from global access
globalTasks = await this.db.select()...
// Query 6: Shared labels
const sharedLabels = await this.db.select()...
// Query 7: Tasks from shared labels
tasksFromSharedLabels = await this.db.select()...
}
7 separate Queries für einen einzigen API-Call (GET /api/tasks). Das wird bei jedem Seitenaufruf ausgeführt.
Fix: Eine einzige SQL-Query mit UNIONs:
SELECT t.* FROM tasks t WHERE t.user_id = $1
UNION
SELECT t.* FROM tasks t JOIN shared_tasks st ON t.id = st.task_id WHERE st.shared_with_user_id = $1
UNION
-- etc.
PERF-2: searchTasks() holt ALLE Tasks und filtert im JS
Datei: server/storage.ts (DbStorage):
async searchTasks(query: string, userId: string): Promise<Task[]> {
const allTasks = await this.getTasksForUser(userId); // 7 Queries!
if (!query) return allTasks;
return allTasks.filter(t => t.title.toLowerCase().includes(lowerQuery)...);
}
Fix: SQL ILIKE direkt in der Query nutzen. Für Volltext: PostgreSQL Full-Text Search oder pg_trgm.
PERF-3: Admin Settings laden jede Einstellung einzeln
Datei: server/routes.ts, Zeile 359-380
for (const key of keys) {
const val = await storage.getSystemSettings(key); // 1 Query pro Key!
...
}
12 separate DB-Queries für ein Admin-Panel-Load.
Fix: getSystemSettingsBulk(keys: string[]): Promise<Record<string, string>> mit WHERE key IN (...).
PERF-4: AI Chat holt ALLE Tasks bei jedem Message
Jede Chat-Message triggert:
const tasks = await storage.getTasksForUser(user.id); // 7 Queries
const activeTasks = tasks.filter(t => t.status !== 'done');
Fix: Caching (Redis/in-memory mit TTL) oder Summary-Table.
PERF-5: Fehlende DB-Indizes
Schema definiert keine expliziten Indizes. Mindestens benötigt:
tasks.user_id(jeder Task-Fetch)tasks.label_id(Label-basierte Queries)tasks.status(Filter nach Status)tasks.parent_task_id(Subtask-Lookups)xp_events.user_id+created_at(Analytics)shared_tasks.shared_with_user_id(Sharing-Lookups)audit_logs.created_at(Log-Sortierung)
PERF-6: Label-Share-View hat O(n²) Problem
Datei: routes.ts, Zeile ~1000
const sharesWithDetails = await Promise.all(users.map(async u => {
const shareInfos = await storage.getLabelShares(req.params.id); // ← SAME query in loop!
const specificShare = shareInfos.find(s => s.sharedWithUserId === u.id);
...
}));
getLabelShares() wird einmal pro User aufgerufen, obwohl es immer dasselbe Ergebnis liefert.
Fix: getLabelShares() einmal vor der Loop aufrufen.
Dead Code & Cleanup
DEAD-1: Unused calculateXP() function
Datei: routes.ts, Zeile 1393-1399
const calculateXP = (task: any) => {
let baseXP = 10;
if (task.priority === 'high') baseXP += 20;
...
};
Wird nirgendwo aufgerufen. GamificationService.getXPForSource() hat diese Logik.
DEAD-2: Notes-Endpoints sind Placeholder
Datei: routes.ts, Zeilen 1285-1300
app.get("/api/notes", async (req, res) => {
res.json([]); // Placeholder until storage implementation
});
app.post("/api/notes", async (req, res) => {
res.status(201).json({ ...result.data, id: "placeholder" });
});
Notes-Schema existiert in schema.ts, aber die Storage-Implementation fehlt. Entweder implementieren oder entfernen.
DEAD-3: Duplizierte DELETE /api/tasks/:id Route
Zeile 1277 ist eine Dead-Route (Express nimmt die erste Registrierung).
DEAD-4: Goals-Endpoint nutzt updateTask statt updateGoal
Datei: routes.ts, Zeile 1759
app.patch("/api/goals/:id", async (req, res) => {
const updated = await storage.updateTask(req.params.id, req.body); // ← WRONG!
updateTask statt updateGoal – das ist ein Bug, kein Dead Code. Goals werden als Tasks aktualisiert!
DEAD-5: DEFAULT_SYSTEM_PROMPT ist doppelt definiert
- In
server/ai.ts(Zeile 11) - In
server/index.ts(Zeile 87) – nochmal als inline String, aber abweichend
DEAD-6: Leaderboard Route ist doppelt registriert
Datei: client/src/App.tsx
<Route path="/leaderboard"><LeaderboardPage /></Route>
<Route path="/leaderboard"><LeaderboardPage /></Route> // Duplikat
DEAD-7: Auskommentierter AiChat Import
Datei: client/src/App.tsx
// import { AiChat } from "@/components/AiChat";
Weitere Issues
MISC-1: Fire-and-Forget AI Processing
Datei: routes.ts, Zeile ~680
res.json(userMessage); // Response sofort gesendet
(async () => {
// AI Processing in Background
const responseContent = await aiService.chat(history, user, context);
await storage.addMessage({ ... });
})(); // ← Kein Error-Handling für Client!
Der Client bekommt nie die AI-Antwort direkt. Er muss pollen. Das ist fragil. Besser: Server-Sent Events (SSE) oder WebSocket für AI-Responses.
MISC-2: PATCH /api/goals/:id hat keinen Auth-Check
Zeile 1759 – kein isAuthenticated() Check, kein Ownership-Check. Jeder kann jeden Goal ändern.
(Hinweis: Wird aktuell fälschlicherweise über updateTask geroutet – siehe DEAD-4.)
MISC-3: XP Double-Counting Risk
Datei: server/storage.ts (DbStorage), logXpEvent():
async logXpEvent(event: InsertXpEvent): Promise<XpEvent> {
const result = await this.db.insert(schema.xpEvents).values(event).returning();
if (event.userId) {
await this.updateUserXP(event.userId, event.amount); // ← Adds XP again!
}
return result[0];
}
GamificationService.awardXP() ruft zuerst storage.updateUserXP(userId, xpAmount) auf, dann storage.logXpEvent(). In logXpEvent() wird updateUserXP() nochmal aufgerufen → XP wird doppelt vergeben!
MISC-4: Fehlende Pagination
Keine einzige Liste hat Pagination:
GET /api/tasks– alle Tasks auf einmalGET /api/admin/users– alle UserGET /api/admin/audit-logs– max 100 (hardcoded)GET /api/ai/conversations– alle ConversationsGET /api/user/history– alle XP Events
Priorisierte Roadmap
Phase 1: Security Fixes (1-2 Tage) 🔴
| # | Task | Impact |
|---|---|---|
| 1 | Debug-Endpoints entfernen/schützen (SEC-1) | Kritisch |
| 2 | debugCode aus API-Responses entfernen (SEC-2) |
Kritisch |
| 3 | Session Secret Validierung (SEC-3) | Kritisch |
| 4 | if (true) in 2FA-Flow fixen (SEC-4) |
Kritisch |
| 5 | Fehlende Auth-Checks hinzufügen (SEC-5) | Kritisch |
| 6 | Rate Limiting installieren (express-rate-limit) |
Hoch |
| 7 | Helmet.js einbinden | Hoch |
| 8 | CORS konfigurieren | Hoch |
Phase 2: Bug Fixes (1 Tag) 🟡
| # | Task | Impact |
|---|---|---|
| 1 | Doppelte DELETE /api/tasks/:id Route entfernen (DUP-1) |
Bug |
| 2 | Doppelten deleteLabel() Call fixen (DUP-2) |
Bug |
| 3 | PATCH /api/goals/:id → updateGoal statt updateTask (DEAD-4) |
Bug |
| 4 | XP Double-Counting fixen (MISC-3) | Bug |
| 5 | gamificationService.awardXP() Parameter-Reihenfolge fixen (TYPE-2) |
Bug |
Phase 3: Architecture – routes.ts aufteilen (2-3 Tage)
| # | Task |
|---|---|
| 1 | Router-Module erstellen (10 Dateien) |
| 2 | Auth-Middleware extrahieren (requireAuth, requireAdmin) |
| 3 | Validation-Middleware mit Zod |
| 4 | Audit-Log-Helper/Middleware |
| 5 | User-Context-Builder extrahieren |
Phase 4: Performance (2-3 Tage)
| # | Task |
|---|---|
| 1 | getTasksForUser() zu einer UNION-Query refactoren |
| 2 | searchTasks() mit SQL ILIKE |
| 3 | Bulk getSystemSettings() |
| 4 | DB-Indizes hinzufügen |
| 5 | Pagination für alle Listen |
Phase 5: TypeScript & Code Quality (2-3 Tage)
| # | Task |
|---|---|
| 1 | any Types durch proper Interfaces ersetzen |
| 2 | Express Request Type-Augmentation für req.user |
| 3 | Dead Code entfernen (Notes-Placeholder, calculateXP, doppelte Routes) |
| 4 | Storage Interface aufteilen (Repository Pattern) |
| 5 | Consistent Error-Handling mit Custom Error Classes |
Phase 6: Nice-to-Have (ongoing)
| # | Task |
|---|---|
| 1 | SSE/WebSocket für AI-Chat statt Fire-and-Forget |
| 2 | Drizzle Kit Migrations statt manueller SQL |
| 3 | MemStorage entweder voll implementieren oder entfernen |
| 4 | OpenAPI/Swagger Spec generieren |
| 5 | Integration Tests |
| 6 | Client-seitige Validierung mit shared Zod Schemas |
Zusammenfassung nach Datei
| Datei | LOC | Zustand | Hauptprobleme |
|---|---|---|---|
server/routes.ts |
2345 | 🔴 Kritisch | Monolith, Duplikate, fehlende Auth, Dead Code |
server/storage.ts |
1634 | 🟡 Problematisch | God-Interface (80+ Methods), any-Types, N+1 |
shared/schema.ts |
551 | 🟢 OK | Gut strukturiert, fehlende Indizes |
server/auth.ts |
270 | 🔴 Kritisch | debugCode, hardcoded secret, if(true) |
server/ai.ts |
550 | 🟡 Problematisch | Sehr lang, aber gut strukturiert. Gemini-Key in URL |
server/gamification.ts |
160 | 🟢 OK | Inkonsistente awardXP-Signatur |
server/email.ts |
130 | 🟢 OK | Solide, i18n-Support |
server/push.ts |
220 | 🟢 OK | Gute Fehlerbehandlung |
server/services/recurrence.ts |
100 | 🟢 OK | any-Cast, sonst sauber |
server/db.ts |
100 | 🟡 Problematisch | Veraltete Migration-Logik |
server/index.ts |
140 | 🟡 Problematisch | Doppelter System-Prompt, aber sonst OK |
server/mcp.ts |
200 | 🟢 OK | Saubere MCP-Implementation |
client/src/App.tsx |
340 | 🟡 Problematisch | Zu viel Logik, doppelte Route, aber funktional |
Dieser Report wurde automatisch generiert. Alle Zeilenangaben beziehen sich auf den Stand vom 2026-02-03.