Spaces:
Runtime error
Runtime error
| 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', | |
| }, | |
| }); | |
| }; | |