Skip to content

Eneza API Middleware

The API uses 14 middleware functions for authentication, authorization, rate limiting, and request processing.

Authentication Middleware

JWT Token Verification (utils/jwtUtils.ts)

Verifies JWT tokens for authenticated endpoints.

typescript
// Token verification middleware
export function verifyToken(req: CustomRequest, res: Response, next: NextFunction) {
  const token = req.header('Authorization')?.replace('Bearer ', '');

  if (!token) {
    return res.status(401).json({ message: 'No token provided' });
  }

  try {
    const decoded = jwt.verify(token, JWT_SECRET) as JwtPayload;
    req.user = decoded;
    next();
  } catch (error) {
    return res.status(401).json({ message: 'Invalid or expired token' });
  }
}

// Admin check middleware
export function isAdmin(req: CustomRequest, res: Response, next: NextFunction) {
  if (!req.user?.isAdmin) {
    return res.status(403).json({ message: 'Admin access required' });
  }
  next();
}

// Extended request type
interface CustomRequest extends Request {
  user?: {
    _id: string;
    phoneNumber?: string;
    email?: string;
    isAdmin?: boolean;
    role?: string;
    permissions?: string[];
  };
}

Admin Authentication (adminAuthMiddleware.ts)

Specialized middleware for admin routes.

typescript
export const adminAuthMiddleware = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const token = req.header('Authorization')?.replace('Bearer ', '');

  if (!token) {
    return res.status(401).json({ error: 'Authentication required' });
  }

  try {
    const decoded = jwt.verify(token, JWT_SECRET) as AdminJwtPayload;

    // Verify admin still exists and is active
    const admin = await prisma.adminUser.findUnique({
      where: { id: decoded.adminId }
    });

    if (!admin || admin.status !== 'active') {
      return res.status(401).json({ error: 'Admin account not active' });
    }

    // Attach admin to request
    req.admin = {
      id: admin.id,
      email: admin.email,
      role: admin.role,
      permissions: admin.permissions
    };

    // Update last login
    await prisma.adminUser.update({
      where: { id: admin.id },
      data: { lastLoginAt: new Date() }
    });

    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid token' });
  }
};

Permission Checking (adminMiddleware.ts)

Role-based access control for admin routes.

typescript
// Check if admin has specific permission
export const requirePermission = (permission: string) => {
  return (req: Request, res: Response, next: NextFunction) => {
    const admin = req.admin;

    if (!admin) {
      return res.status(401).json({ error: 'Authentication required' });
    }

    // Super admin has all permissions
    if (admin.role === 'super_admin') {
      return next();
    }

    // Check specific permission
    if (!admin.permissions.includes(permission)) {
      return res.status(403).json({
        error: 'Permission denied',
        required: permission
      });
    }

    next();
  };
};

// Available permissions
const PERMISSIONS = {
  // Users
  'users:read': 'View user details',
  'users:edit': 'Edit user profiles',
  'users:ban': 'Ban/unban users',

  // Ads
  'ads:read': 'View ad details',
  'ads:moderate': 'Approve/reject ads',
  'ads:edit': 'Edit ad details',
  'ads:delete': 'Delete ads',

  // Screenshots
  'screenshots:review': 'Review screenshot queue',
  'screenshots:approve': 'Approve screenshots',
  'screenshots:reject': 'Reject screenshots',

  // Finance
  'finance:read': 'View financial data',
  'finance:process': 'Process withdrawals',

  // Settings
  'settings:read': 'View settings',
  'settings:edit': 'Edit settings',

  // System
  'system:read': 'View system health',
  'system:admin': 'Manage admin users'
};

Geo Extraction Middleware

Basic Geo Extraction (geoExtractMiddleware.ts)

Extracts location from Cloudflare headers.

typescript
export function extractGeoData(req: GeoRequest, res: Response, next: NextFunction) {
  // Extract IP address (Cloudflare → standard headers → fallback)
  const ipAddress =
    (req.headers['cf-connecting-ip'] as string) ||
    (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ||
    (req.headers['x-real-ip'] as string) ||
    req.ip ||
    'unknown';

  // Extract country code (Cloudflare header)
  const country = ((req.headers['cf-ipcountry'] as string) || 'XX').toUpperCase();

  // Extract optional geo data
  const city = req.headers['cf-ipcity'] as string;
  const asn = parseInt(req.headers['cf-ipasn'] as string, 10);
  const asOrganization = req.headers['cf-ipasorganization'] as string;

  // Attach to request
  req.geo = {
    ipAddress,
    country,
    city,
    asn: isNaN(asn) ? undefined : asn,
    asOrganization
  };

  next();
}

Enhanced Geo Extraction

Uses external IP geolocation API when Cloudflare returns "XX".

typescript
export async function extractGeoDataEnhanced(
  req: GeoRequest,
  res: Response,
  next: NextFunction
) {
  // First, extract basic geo data
  extractGeoData(req, res, () => {});

  // If country is unknown, use external service
  if (req.geo?.country === 'XX' && req.geo.ipAddress !== 'unknown') {
    try {
      const geoData = await ipGeolocationService.lookup(req.geo.ipAddress);
      if (geoData) {
        req.geo = {
          ...req.geo,
          country: geoData.countryCode || 'XX',
          city: geoData.city,
          region: geoData.region
        };
      }
    } catch (error) {
      logger.warn('Enhanced geo lookup failed:', error);
    }
  }

  next();
}

Security Middleware

API Key Middleware (apiKeyMiddleware.ts)

Validates API keys for public endpoints.

typescript
export const verifyApiKey = (req: Request, res: Response, next: NextFunction) => {
  const apiKey = req.header('X-API-Key');

  if (!apiKey) {
    return res.status(401).json({ error: 'API key required' });
  }

  // Verify against stored key
  const validKey = process.env.DASHBOARD_API_KEY;

  if (apiKey !== validKey) {
    return res.status(403).json({ error: 'Invalid API key' });
  }

  next();
};

Job Authentication (jobAuthMiddleware.ts)

Authenticates Cloud Scheduler jobs.

typescript
export const verifyJobSecret = (req: Request, res: Response, next: NextFunction) => {
  const jobSecret = req.header('X-Job-Secret');

  if (!jobSecret || jobSecret !== process.env.JOB_SECRET) {
    logger.warn('Unauthorized job request attempt');
    return res.status(403).json({ error: 'Unauthorized' });
  }

  next();
};

Pub/Sub Authentication (pubsubAuth.ts)

Verifies Pub/Sub push subscription messages.

typescript
export const verifyPubSubToken = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    const token = req.query.token as string;

    if (!token) {
      return res.status(403).json({ error: 'No token provided' });
    }

    // Verify token matches configured secret
    if (token !== process.env.PUBSUB_VERIFICATION_TOKEN) {
      return res.status(403).json({ error: 'Invalid token' });
    }

    // Parse Pub/Sub message
    const message = req.body.message;
    if (!message || !message.data) {
      return res.status(400).json({ error: 'Invalid Pub/Sub message' });
    }

    // Decode base64 data
    const data = JSON.parse(Buffer.from(message.data, 'base64').toString());
    req.pubsubData = data;

    next();
  } catch (error) {
    logger.error('Pub/Sub auth error:', error);
    return res.status(400).json({ error: 'Invalid message format' });
  }
};

Kill Switch (killSwitchMiddleware.ts)

Emergency shutdown capability.

typescript
export const verifyKillSwitch = (req: Request, res: Response, next: NextFunction) => {
  const killCode = req.body.killCode;

  if (!killCode || killCode !== process.env.KILL_SWITCH_CODE) {
    return res.status(403).json({ error: 'Invalid kill code' });
  }

  next();
};

Rate Limiting

Rate Limit Middleware (rateLimitMiddleware.ts)

Protects endpoints from abuse.

typescript
import rateLimit from 'express-rate-limit';

// OTP request limiter
export const otpRateLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 3, // 3 requests per minute
  message: { error: 'Too many OTP requests, please try again later' },
  keyGenerator: (req) => req.body.phoneNumber || req.ip,
  skipSuccessfulRequests: false
});

// General API limiter
export const apiRateLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // 100 requests per 15 minutes
  message: { error: 'Too many requests, please try again later' },
  standardHeaders: true,
  legacyHeaders: false
});

// Screenshot upload limiter
export const screenshotRateLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 10, // 10 screenshots per hour
  message: { error: 'Screenshot upload limit reached' },
  keyGenerator: (req) => (req as CustomRequest).user?._id || req.ip
});

Request Logging

Request Logger (requestLogger.ts)

Logs all incoming requests.

typescript
const requestLogger = (req: Request, res: Response, next: NextFunction) => {
  const start = Date.now();

  // Log request
  logger.info(`→ ${req.method} ${req.originalUrl}`, {
    ip: req.ip,
    userAgent: req.get('user-agent'),
    contentLength: req.get('content-length')
  });

  // Log response on finish
  res.on('finish', () => {
    const duration = Date.now() - start;
    const logLevel = res.statusCode >= 400 ? 'warn' : 'info';

    logger[logLevel](`← ${req.method} ${req.originalUrl} ${res.statusCode}`, {
      duration: `${duration}ms`,
      contentLength: res.get('content-length')
    });
  });

  next();
};

export default requestLogger;

Input Validation

Validator Middleware (validatorMiddleware.ts)

Validates request body/params using Joi or similar.

typescript
import Joi from 'joi';

export const validate = (schema: Joi.ObjectSchema) => {
  return (req: Request, res: Response, next: NextFunction) => {
    const { error, value } = schema.validate(req.body, {
      abortEarly: false,
      stripUnknown: true
    });

    if (error) {
      const errors = error.details.map(d => ({
        field: d.path.join('.'),
        message: d.message
      }));

      return res.status(400).json({
        error: 'Validation failed',
        details: errors
      });
    }

    req.body = value;
    next();
  };
};

// Example schemas
export const createAdSchema = Joi.object({
  title: Joi.string().required().max(255),
  video: Joi.string().uri().required(),
  categoryId: Joi.string().uuid().required(),
  budgetUsd: Joi.number().positive().required(),
  demographics: Joi.object({
    ageMin: Joi.number().min(13).max(100).required(),
    ageMax: Joi.number().min(13).max(100).required(),
    gender: Joi.string().valid('male', 'female', 'both').default('both'),
    countryId: Joi.string().uuid(),
    cityIds: Joi.array().items(Joi.string().uuid())
  })
});

Dashboard Authentication

Dashboard Auth (dashboardAuth.ts)

API key authentication for CEO dashboard.

typescript
export const dashboardAuth = (req: Request, res: Response, next: NextFunction) => {
  const apiKey = req.header('X-API-Key');

  if (!apiKey) {
    return res.status(401).json({
      error: 'API key required',
      hint: 'Include X-API-Key header'
    });
  }

  // Verify against dashboard API key from Secret Manager
  const validKey = process.env.DASHBOARD_API_KEY;

  if (apiKey !== validKey) {
    logger.warn('Invalid dashboard API key attempt', {
      ip: req.ip,
      providedKey: apiKey.substring(0, 8) + '...'
    });
    return res.status(403).json({ error: 'Invalid API key' });
  }

  next();
};

Middleware Application Order

typescript
// In app.ts - order matters!

// 1. CORS (before everything)
app.use(cors(corsOptions));

// 2. Stripe webhooks (needs raw body - before bodyParser)
app.use('/stripe', stripeRoutes);
app.use('/api/campaign-payments', campaignPaymentRoutes);
app.use('/api/deposits', depositRoutes);
app.use('/api/stripe-billing', stripeBillingRoutes);

// 3. Body parsing
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));

// 4. File upload
app.use(fileUpload({
  limits: { fileSize: 200 * 1024 * 1024 },
  useTempFiles: true,
  tempFileDir: '/tmp/'
}));

// 5. Request logging
app.use(requestLogger);

// 6. Routes (each with own auth middleware)
app.use('/auth', authRoutes);  // No auth required
app.use('/users', verifyToken, userRoutes);  // User JWT required
app.use('/admin', adminAuthMiddleware, adminRoutes);  // Admin JWT required

One chat. Everything done.