import { Request, Response, NextFunction } from 'express'; import { z } from 'zod'; import { supabase } from '../config/supabase'; import { logError } from '../utils/logger'; import { logActivity } from '../services/activityLog'; export interface AuthRequest extends Request { userId?: string; userEmail?: string; } export const validateRequest = (schema: z.ZodSchema, target: 'body' | 'query' | 'params' = 'body') => { return (req: Request, res: Response, next: NextFunction) => { const result = schema.safeParse(req[target]); if (!result.success) { logActivity({ action: 'SUSPICIOUS_INPUT', details: `Validation failed on ${target}: ${result.error.issues.map(i => i.message).join(', ')}`, ip: req.ip, user_agent: req.get('user-agent'), }); res.status(400).json({ success: false, error: { code: 'VALIDATION_ERROR', message: 'Invalid request data', details: result.error.flatten().fieldErrors, }, }); return; } req[target] = result.data; next(); }; }; export const uuidParam = z.string().uuid(); export const authenticate = async (req: AuthRequest, res: Response, next: NextFunction) => { try { const authHeader = req.headers.authorization; if (!authHeader?.startsWith('Bearer ')) { logActivity({ action: 'UNAUTHENTICATED_ACCESS', details: `Missing auth header on ${req.method} ${req.originalUrl}`, ip: req.ip, user_agent: req.get('user-agent'), }); res.status(401).json({ success: false, error: { code: 'UNAUTHORIZED', message: 'Missing or invalid authorization header', }, }); return; } const token = authHeader.split(' ')[1]; const { data: { user }, error } = await supabase.auth.getUser(token); if (error || !user) { logActivity({ action: 'INVALID_TOKEN', details: `Invalid/expired token on ${req.method} ${req.originalUrl}: ${error?.message || 'no user'}`, ip: req.ip, user_agent: req.get('user-agent'), }); res.status(401).json({ success: false, error: { code: 'UNAUTHORIZED', message: 'Invalid or expired token', }, }); return; } // Check if user is banned const { data: profile } = await supabase .from('profiles') .select('is_banned') .eq('id', user.id) .single(); if (profile?.is_banned) { logActivity({ user_id: user.id, action: 'BANNED_ACCESS_ATTEMPT', details: `Banned user accessed ${req.method} ${req.originalUrl}`, ip: req.ip, user_agent: req.get('user-agent'), }); res.status(403).json({ success: false, error: { code: 'ACCOUNT_BANNED', message: 'Your account has been suspended', }, }); return; } req.userId = user.id; req.userEmail = user.email; next(); } catch { res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: 'Authentication failed', }, }); } }; export const errorHandler = (err: Error, _req: Request, res: Response, _next: NextFunction) => { // Log full error server-side only — never expose to client logError(`Unhandled error: ${err.constructor.name}`, 'auth'); res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: 'An unexpected error occurred', }, }); };