trading-platform-mcp-produc.../src/middleware/auth.middleware.ts
2026-01-16 08:33:17 -06:00

128 lines
3.1 KiB
TypeScript

/**
* Auth Middleware for MCP Products
* Verifies JWT tokens and sets user context
*/
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { logger } from '../utils/logger';
// JWT configuration (should match mcp-auth config)
const JWT_SECRET = process.env.JWT_SECRET || 'dev-jwt-secret-change-in-production-min-256-bits';
// JWT Payload interface
export interface JWTPayload {
sub: string; // user_id
email: string;
tenantId: string;
isOwner: boolean;
iat: number;
exp: number;
}
// Extend Express Request to include auth info
declare global {
namespace Express {
interface Request {
userId?: string;
tenantId?: string;
userEmail?: string;
isOwner?: boolean;
isAuthenticated?: boolean;
}
}
}
/**
* Auth middleware that verifies JWT tokens
* If valid, sets userId, tenantId, and userEmail on request
*/
export function authMiddleware(req: Request, res: Response, next: NextFunction): void {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
res.status(401).json({
error: 'Unauthorized',
code: 'MISSING_TOKEN',
message: 'No authentication token provided',
});
return;
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, JWT_SECRET) as JWTPayload;
// Set auth info on request
req.userId = decoded.sub;
req.tenantId = decoded.tenantId;
req.userEmail = decoded.email;
req.isOwner = decoded.isOwner;
req.isAuthenticated = true;
// Also set tenant ID header for RLS queries
req.headers['x-tenant-id'] = decoded.tenantId;
req.headers['x-user-id'] = decoded.sub;
next();
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
res.status(401).json({
error: 'Unauthorized',
code: 'TOKEN_EXPIRED',
message: 'Token has expired',
});
return;
}
if (error instanceof jwt.JsonWebTokenError) {
res.status(401).json({
error: 'Unauthorized',
code: 'INVALID_TOKEN',
message: 'Invalid token',
});
return;
}
logger.error('Auth middleware error', { error });
res.status(500).json({
error: 'Internal server error',
code: 'AUTH_ERROR',
});
}
}
/**
* Optional auth middleware
* Sets auth info if token is valid, but allows unauthenticated requests
*/
export function optionalAuthMiddleware(req: Request, _res: Response, next: NextFunction): void {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
req.isAuthenticated = false;
next();
return;
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, JWT_SECRET) as JWTPayload;
req.userId = decoded.sub;
req.tenantId = decoded.tenantId;
req.userEmail = decoded.email;
req.isOwner = decoded.isOwner;
req.isAuthenticated = true;
req.headers['x-tenant-id'] = decoded.tenantId;
req.headers['x-user-id'] = decoded.sub;
} catch {
req.isAuthenticated = false;
}
next();
}