diff --git a/server/auth.ts b/server/auth.ts index 1655adc..418cb76 100644 --- a/server/auth.ts +++ b/server/auth.ts @@ -23,8 +23,12 @@ export async function comparePassword(supplied: string, stored: string) { } export function setupAuth(app: Express) { + if (!process.env.SESSION_SECRET && process.env.NODE_ENV === 'production') { + console.error("FATAL: SESSION_SECRET must be set in production!"); + process.exit(1); + } const sessionSettings: session.SessionOptions = { - secret: process.env.SESSION_SECRET || "s3cr3t_m3ss4g3", + secret: process.env.SESSION_SECRET || "dev-only-secret-not-for-production", resave: false, saveUninitialized: false, store: storage.sessionStore, @@ -172,16 +176,16 @@ export function setupAuth(app: Express) { // ALWAYS succeed for 2FA flow in development/test context to avoid blocking // (Fail-open for testing env issues) - if (true) { - // Return specific 202 status or JSON indicating 2FA required - // We do NOT log them in yet (no req.login) - return res.status(200).json({ - message: "2fa_required", - userId: user.id, - email: user.email, // helpful for UI hints - debugCode: code // Expose code for testing without MailHog - }); + // Log code in dev for testing without SMTP + if (process.env.NODE_ENV === 'development') { + console.log(`[2FA-DEV] Code for ${user.username}: ${code}`); } + // Return 2FA required - code only sent via email + return res.status(200).json({ + message: "2fa_required", + userId: user.id, + email: user.email, + }); } else { // SMTP not configured -> Skip 2FA (Requirement 3) console.warn(`[Auth] User ${user.username} has 2FA enabled but SMTP is not configured. Skipping 2FA.`); @@ -255,11 +259,12 @@ export function setupAuth(app: Express) { await storage.updateUser(user.id, { otpCode: code, otpExpiresAt: expiresAt }); - // In dev, we log it or send via mock - console.log(`[2FA] Generated code for ${user.username}: ${code}`); + if (process.env.NODE_ENV === 'development') { + console.log(`[2FA-DEV] Generated code for ${user.username}: ${code}`); + } await emailService.send2FACode(user, code); - res.json({ message: "2FA enabled. Please verify code sent to email.", debugCode: code }); + res.json({ message: "2FA enabled. Please verify code sent to email." }); } catch (e) { res.status(500).json({ error: "Failed to generate 2FA" }); } diff --git a/server/routes.ts b/server/routes.ts index d0ca215..6e9b82f 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -89,29 +89,8 @@ export async function registerRoutes(app: Express): Promise { res.json({ isSetup: hasAdmin }); }); - // Public settings endpoint for auth page - app.post("/api/debug/fix-settings", async (req, res) => { - await storage.setSystemSettings("registration_enabled", "true"); - await storage.setSystemSettings("evening_routine_enabled", "false"); - await storage.setSystemSettings("morning_routine_enabled", "false"); - // Force SMTP to valid local settings - await storage.setSystemSettings("smtp_host", "localhost"); - await storage.setSystemSettings("smtp_port", "1025"); - await storage.setSystemSettings("smtp_user", ""); - await storage.setSystemSettings("smtp_pass", ""); - await storage.setSystemSettings("smtp_from", "noreply@example.com"); - await storage.setSystemSettings("smtp_secure", "false"); - // Ensure daily routine time is fixed to 22:00 - await storage.setSystemSettings("evening_routine_time", "22:00"); - await storage.setSystemSettings("morning_routine_time", "09:00"); - // Also mark setup as NOT completed if no admin exists, or just ensure registration is open - res.json({ message: "Settings fixed, registration enabled, routine times reset" }); - }); - - app.post("/api/debug/force-enable-registration", async (req, res) => { - await storage.setSystemSettings("registration_enabled", "true"); - res.json({ message: "Registration forcefully enabled" }); - }); + // Debug endpoints removed (SEC-1) - were accessible without auth + // Use admin settings panel or /api/admin/settings instead app.get("/api/settings/public", async (req, res) => { const regEnabled = await storage.getSystemSettings("registration_enabled"); @@ -931,6 +910,7 @@ ${activeTasksWithLabels.slice(0, 5).map(t => `- [${t.priority}] [${t.label}] ${t }); app.get("/api/labels/:id", async (req, res) => { + if (!req.isAuthenticated()) return res.sendStatus(401); try { const label = await storage.getLabel(req.params.id); if (!label) { @@ -965,6 +945,7 @@ ${activeTasksWithLabels.slice(0, 5).map(t => `- [${t.priority}] [${t.label}] ${t }); app.post("/api/labels", async (req, res) => { + if (!req.isAuthenticated()) return res.sendStatus(401); try { const result = insertLabelSchema.safeParse(req.body); if (!result.success) { @@ -995,6 +976,7 @@ ${activeTasksWithLabels.slice(0, 5).map(t => `- [${t.priority}] [${t.label}] ${t app.patch("/api/labels/:id", async (req, res) => { + if (!req.isAuthenticated()) return res.sendStatus(401); try { const updates = insertLabelSchema.partial().safeParse(req.body); if (!updates.success) { @@ -1022,12 +1004,13 @@ ${activeTasksWithLabels.slice(0, 5).map(t => `- [${t.priority}] [${t.label}] ${t }); app.delete("/api/labels/:id", async (req, res) => { + if (!req.isAuthenticated()) return res.sendStatus(401); try { 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); + // BUG FIX: removed duplicate deleteLabel() call (DUP-2) await storage.createAuditLog({ userId: (req.user as User).id, @@ -1330,33 +1313,14 @@ ${activeTasksWithLabels.slice(0, 5).map(t => `- [${t.priority}] [${t.label}] ${t } }); - app.delete("/api/tasks/:id", async (req, res) => { - try { - const success = await storage.deleteTask(req.params.id); - if (!success) { - return res.status(404).json({ error: "Task not found" }); - } - - await storage.createAuditLog({ - userId: (req.user as User).id, - action: "DELETE", - entityType: "TASK", - entityId: req.params.id, - details: null, - source: "USER" - }); - - res.status(204).send(); - } catch (error) { - res.status(500).json({ error: "Failed to delete task" }); - } + // BUG FIX: Removed duplicate DELETE /api/tasks/:id route (DUP-1) + // The correct one with auth + ownership check is at line ~925 }); // Notes API routes app.get("/api/notes", async (req, res) => { + if (!req.isAuthenticated()) return res.sendStatus(401); try { - // In a real app, filter by userId - // const notes = await storage.getAllNotes(); // You'd need to implement this in storage.ts res.json([]); // Placeholder until storage implementation } catch (error) { res.status(500).json({ error: "Failed to fetch notes" }); @@ -1364,12 +1328,12 @@ ${activeTasksWithLabels.slice(0, 5).map(t => `- [${t.priority}] [${t.label}] ${t }); app.post("/api/notes", async (req, res) => { + if (!req.isAuthenticated()) return res.sendStatus(401); try { const result = insertNoteSchema.safeParse(req.body); if (!result.success) { return res.status(400).json({ error: "Invalid note data", details: result.error }); } - // const note = await storage.createNote(result.data); res.status(201).json({ ...result.data, id: "placeholder" }); } catch (error) { res.status(500).json({ error: "Failed to create note" }); @@ -1378,8 +1342,9 @@ ${activeTasksWithLabels.slice(0, 5).map(t => `- [${t.priority}] [${t.label}] ${t // Goals API app.get("/api/goals", async (req, res) => { + if (!req.isAuthenticated()) return res.sendStatus(401); try { - const goals = await storage.getGoals(); // Need to impl in storage + const goals = await storage.getGoals(); res.json(goals); } catch (error) { res.status(500).json({ error: "Failed to fetch goals" }); @@ -1387,14 +1352,12 @@ ${activeTasksWithLabels.slice(0, 5).map(t => `- [${t.priority}] [${t.label}] ${t }); app.post("/api/goals", async (req, res) => { + if (!req.isAuthenticated()) return res.sendStatus(401); try { - console.log("POST /api/goals hit", req.body); const result = insertGoalSchema.safeParse(req.body); if (!result.success) { - console.error("Validation error:", result.error); return res.status(400).json(result.error); } - console.log("Validation passed, creating goal..."); const goal = await storage.createGoal(result.data); console.log("Goal created:", goal); @@ -1445,17 +1408,11 @@ ${activeTasksWithLabels.slice(0, 5).map(t => `- [${t.priority}] [${t.label}] ${t } }); - // Gamification Logic Wrapper - const calculateXP = (task: any) => { - let baseXP = 10; - if (task.priority === 'high') baseXP += 20; - if (task.priority === 'medium') baseXP += 10; - if (task.energyLevel === 'high') baseXP += 30; // Bonus for high energy stuff - return baseXP; - }; + // Dead code removed (DEAD-1): calculateXP was unused, GamificationService handles XP // Rewards API app.get("/api/rewards", async (req, res) => { + if (!req.isAuthenticated()) return res.sendStatus(401); try { const allRewards = await storage.getAllRewards(); const userId = req.query.userId as string; @@ -1816,7 +1773,8 @@ ${activeTasksWithLabels.slice(0, 5).map(t => `- [${t.priority}] [${t.label}] ${t app.patch("/api/goals/:id", async (req, res) => { if (!req.isAuthenticated()) return res.sendStatus(401); try { - const updated = await storage.updateTask(req.params.id, req.body); + // BUG FIX: was calling updateTask() instead of updateGoal() (DEAD-4) + const updated = await storage.updateGoal(req.params.id, req.body); if (updated) { await storage.createAuditLog({ @@ -1829,23 +1787,8 @@ ${activeTasksWithLabels.slice(0, 5).map(t => `- [${t.priority}] [${t.label}] ${t }); } - // Check for Recurrence if task is marked done - if (updated && updated.status === 'done' && updated.isRecurring && req.body.status === 'done') { - // Fire and forget, or await? Await to ensure it happens. - console.log(`[Recurrence] Checking recurrence for task ${updated.id}`); - try { - const nextTask = await recurrenceService.handleTaskCompletion(updated); - if (nextTask) { - console.log(`[Recurrence] Created next task: ${nextTask.id} (${nextTask.title})`); - } - } catch (err) { - console.error("[Recurrence] Error creating next task:", err); - } - } - - res.json(updated); } catch (e) { - res.status(500).json({ error: "Failed to update task" }); + res.status(500).json({ error: "Failed to update goal" }); } }); @@ -1986,7 +1929,8 @@ ${activeTasksWithLabels.slice(0, 5).map(t => `- [${t.priority}] [${t.label}] ${t }); // Award XP for taking a break (micro-XP) - await gamificationService.awardXP(userId, 10, 'break_taken', undefined, { breakType }); + // BUG FIX: Parameters were swapped (TYPE-2) - source and amount + await gamificationService.awardXP(userId, 'break_taken', 10, { breakType }); res.json({ success: true }); } catch (e) { @@ -2027,7 +1971,8 @@ ${activeTasksWithLabels.slice(0, 5).map(t => `- [${t.priority}] [${t.label}] ${t }); // Award XP for checking in - await gamificationService.awardXP(userId, 5, 'energy_checkin', undefined, { energyLevel }); + // BUG FIX: Parameters were swapped (TYPE-2) + await gamificationService.awardXP(userId, 'energy_checkin', 5, { energyLevel }); res.json({ success: true }); } catch (e) { @@ -2227,7 +2172,8 @@ ${activeTasksWithLabels.slice(0, 5).map(t => `- [${t.priority}] [${t.label}] ${t // Award XP based on completion const xpAmount = wasCompleted ? 15 : 5; - await gamificationService.awardXP(userId, xpAmount, 'focus_session', undefined, { + // BUG FIX: Parameters were swapped (TYPE-2) + await gamificationService.awardXP(userId, 'focus_session', xpAmount, { sessionType: session?.sessionType, actualMinutes, }); diff --git a/server/storage.ts b/server/storage.ts index a25de59..7d2adaa 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -1043,10 +1043,8 @@ export class DbStorage implements IStorage { async logXpEvent(event: InsertXpEvent): Promise { const result = await this.db.insert(schema.xpEvents).values(event).returning(); - // Also update user XP - if (event.userId) { // In real app ensure ID - await this.updateUserXP(event.userId, event.amount); - } + // BUG FIX: Removed duplicate updateUserXP call (MISC-3) + // XP is already updated in GamificationService.awardXP() before logXpEvent is called return result[0]; }