Files
task-manager/docs/REFACTORING-REVIEW.md
T
NotiBot 4f6aff32ab
continuous-integration/drone/push Build is passing
feat: API Key Auth + erweiterte MCP Tools + Dokumentation
- 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
2026-02-03 10:23:18 +01:00

21 KiB
Raw Blame History

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:

  1. routes.ts ist ein 2345-Zeilen-Monolith alle 96 Endpoints in einer Datei, keine klare Trennung
  2. Kritische Security-Issues Debug-Endpoints ohne Auth, 2FA-Codes im Response, hardcoded Session Secret
  3. Doppelte Route-Registrierungen DELETE /api/tasks/:id ist zweimal definiert (Zeile 889 + 1277)
  4. Doppelter Label-Delete storage.deleteLabel() wird zweimal aufgerufen (Zeile 970 + 974)
  5. Kein Rate Limiting, kein CORS, kein Helmet grundlegende Security-Middleware fehlt komplett
  6. Massive any-Nutzung 20+ any-Types allein in routes.ts
  7. 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 einmal
  • GET /api/admin/users alle User
  • GET /api/admin/audit-logs max 100 (hardcoded)
  • GET /api/ai/conversations alle Conversations
  • GET /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/:idupdateGoal 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.