Skip to content

YeboNa Middleware

Authentication and request handling middleware.

Authentication Middleware

Location: api/src/middleware/auth.ts

authMiddleware

Verifies JWT and attaches user to request.

typescript
import { Response, NextFunction } from 'express';
import { verifyToken, extractToken, AuthRequest } from '../utils/jwt';

export const authMiddleware = (
  req: AuthRequest,
  res: Response,
  next: NextFunction
) => {
  try {
    const token = extractToken(req);
    
    if (!token) {
      return res.status(401).json({
        success: false,
        error: 'Authentication required',
      });
    }

    const payload = verifyToken(token);
    
    if (!payload) {
      return res.status(401).json({
        success: false,
        error: 'Invalid or expired token',
      });
    }

    // Attach user to request
    req.user = payload;
    next();
  } catch (error) {
    return res.status(401).json({
      success: false,
      error: 'Authentication failed',
    });
  }
};

Usage:

typescript
router.get('/me', authMiddleware, getCurrentUser);

optionalAuth

For routes where auth is optional.

typescript
export const optionalAuth = (
  req: AuthRequest,
  res: Response,
  next: NextFunction
) => {
  try {
    const token = extractToken(req);
    
    if (token) {
      const payload = verifyToken(token);
      if (payload) {
        req.user = payload;
      }
    }
    
    next();
  } catch (error) {
    // Continue without auth
    next();
  }
};

Rate Limiting

Location: api/src/app.ts

Authentication Rate Limiter

Stricter limits for auth endpoints.

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

const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 20,                    // 20 requests
  message: {
    success: false,
    error: 'Too many auth attempts, please try again later'
  },
  validate: { trustProxy: false }
});

app.use('/api/auth', authLimiter, authRoutes);

Public Rate Limiter

General API endpoints.

typescript
const publicLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 100,                   // 100 requests
  message: {
    success: false,
    error: 'Too many requests, please try again later'
  },
  validate: { trustProxy: false }
});

app.use('/api/providers', publicLimiter, providerRoutes);

CORS Configuration

typescript
import cors from 'cors';

app.use(cors({
  origin: '*',
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
}));

Request Parsing

typescript
app.use(express.json());

Trust Proxy

Required for Cloud Run / load balancers:

typescript
app.set('trust proxy', 1);

Error Handler

typescript
app.use((
  err: Error,
  _req: express.Request,
  res: express.Response,
  _next: express.NextFunction
) => {
  console.error('Unhandled error:', err);
  
  res.status(500).json({
    success: false,
    error: config.env === 'production' 
      ? 'Internal server error' 
      : err.message
  });
});

404 Handler

typescript
app.use((_req, res) => {
  res.status(404).json({
    success: false,
    error: 'Not found'
  });
});

JWT Utilities

Location: api/src/utils/jwt.ts

Token Generation

typescript
export function generateToken(payload: TokenPayload): string {
  return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRY });
}

export async function generateTokenPair(payload: TokenPayload): Promise<{
  accessToken: string;
  refreshToken: string;
}> {
  const accessToken = generateToken(payload);
  const refreshToken = crypto.randomUUID();
  
  // Store refresh token
  await db.refreshTokens.create({
    token: refreshToken,
    userId: payload.userId,
    phoneNumber: payload.phoneNumber,
    expiresAt: new Date(Date.now() + REFRESH_EXPIRY_MS)
  });
  
  return { accessToken, refreshToken };
}

Token Verification

typescript
export function verifyToken(token: string): TokenPayload | null {
  try {
    return jwt.verify(token, JWT_SECRET) as TokenPayload;
  } catch {
    return null;
  }
}

export async function verifyRefreshToken(token: string): Promise<{
  userId: string;
  phoneNumber: string;
} | null> {
  const record = await db.refreshTokens.findByToken(token);
  
  if (!record || new Date(record.expires_at) < new Date()) {
    return null;
  }
  
  return {
    userId: record.user_id,
    phoneNumber: record.phone_number
  };
}

Token Extraction

typescript
export function extractToken(req: AuthRequest): string | null {
  const authHeader = req.headers.authorization;
  
  if (authHeader?.startsWith('Bearer ')) {
    return authHeader.substring(7);
  }
  
  return null;
}

Token Revocation

typescript
export async function revokeRefreshToken(token: string): Promise<boolean> {
  return db.refreshTokens.delete(token);
}

export async function revokeAllUserTokens(userId: string): Promise<number> {
  return db.refreshTokens.deleteAllForUser(userId);
}

Middleware Stack Order

typescript
// app.ts

// 1. Trust proxy
app.set('trust proxy', 1);

// 2. CORS
app.use(cors({ ... }));

// 3. Body parser
app.use(express.json());

// 4. Routes with rate limiting
app.use('/api/auth', authLimiter, authRoutes);
app.use('/api/users', publicLimiter, userRoutes);
// ...

// 5. 404 handler
app.use(notFoundHandler);

// 6. Error handler (last)
app.use(errorHandler);

One chat. Everything done.