Skip to content

YeboSafe Middleware

Authentication Middleware

Location: yebosafe-api/src/middleware/auth.ts

flexAuth

Flexible authentication supporting both API key and JWT.

typescript
import { Request, Response, NextFunction } from 'express';
import prisma from '../config/prisma';
import jwt from 'jsonwebtoken';

export interface AuthRequest extends Request {
  merchant?: {
    id: string;
    email: string;
    name: string;
  };
}

export const flexAuth = async (
  req: AuthRequest,
  res: Response,
  next: NextFunction
) => {
  try {
    const authHeader = req.headers.authorization;
    
    if (!authHeader?.startsWith('Bearer ')) {
      return res.status(401).json({
        success: false,
        error: { code: 'UNAUTHORIZED', message: 'No token provided' }
      });
    }
    
    const token = authHeader.substring(7);
    
    // Try API key first (starts with sk_)
    if (token.startsWith('sk_')) {
      const apiKey = await prisma.apiKey.findUnique({
        where: { key: token },
        include: { merchant: true }
      });
      
      if (!apiKey || !apiKey.isActive) {
        return res.status(401).json({
          success: false,
          error: { code: 'INVALID_API_KEY', message: 'Invalid API key' }
        });
      }
      
      if (!apiKey.merchant.isActive) {
        return res.status(401).json({
          success: false,
          error: { code: 'MERCHANT_INACTIVE', message: 'Merchant account inactive' }
        });
      }
      
      // Update last used
      await prisma.apiKey.update({
        where: { id: apiKey.id },
        data: { lastUsedAt: new Date() }
      });
      
      req.merchant = {
        id: apiKey.merchant.id,
        email: apiKey.merchant.email,
        name: apiKey.merchant.name
      };
      
      return next();
    }
    
    // Try JWT
    try {
      const decoded = jwt.verify(token, process.env.JWT_SECRET!) as {
        merchantId: string;
      };
      
      const merchant = await prisma.merchant.findUnique({
        where: { id: decoded.merchantId }
      });
      
      if (!merchant || !merchant.isActive) {
        return res.status(401).json({
          success: false,
          error: { code: 'INVALID_TOKEN', message: 'Invalid token' }
        });
      }
      
      req.merchant = {
        id: merchant.id,
        email: merchant.email,
        name: merchant.name
      };
      
      return next();
    } catch {
      return res.status(401).json({
        success: false,
        error: { code: 'INVALID_TOKEN', message: 'Invalid or expired token' }
      });
    }
  } catch (error) {
    return res.status(500).json({
      success: false,
      error: { code: 'AUTH_ERROR', message: 'Authentication failed' }
    });
  }
};

Request Validation

Location: yebosafe-api/src/routes/escrow.routes.ts

Using express-validator:

typescript
import { body } from 'express-validator';

// Create escrow validation
router.post(
  '/',
  [
    body('amount')
      .isFloat({ gt: 0 })
      .withMessage('Amount must be positive'),
    body('currency')
      .optional()
      .isLength({ min: 3, max: 3 }),
    body('description')
      .optional()
      .isString()
      .isLength({ max: 500 }),
  ],
  createEscrow
);

// Refuse escrow validation
router.post(
  '/:id/refuse',
  [
    body('reason')
      .notEmpty()
      .withMessage('Reason is required')
  ],
  refuseEscrow
);

// Complete escrow validation
router.post(
  '/:id/complete',
  [
    body('completionCode')
      .notEmpty()
      .withMessage('Completion code is required')
  ],
  completeEscrow
);

Error Handling

typescript
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  console.error('Error:', err);
  
  res.status(500).json({
    success: false,
    error: {
      code: 'INTERNAL_ERROR',
      message: process.env.NODE_ENV === 'production' 
        ? 'Internal server error'
        : err.message
    }
  });
});

CORS Configuration

typescript
import cors from 'cors';

app.use(cors({
  origin: [
    'https://dashboard.yebosafe.com',
    'https://yebosafe.com',
    'http://localhost:3000'  // Development
  ],
  credentials: true
}));

One chat. Everything done.