Skip to main content
Glama
jwt-auth.ts6.5 kB
import jwt from 'jsonwebtoken'; import bcrypt from 'bcrypt'; import { Request, Response, NextFunction } from 'express'; // ============================================ // Configuration // ============================================ const JWT_SECRET = process.env.JWT_SECRET || 'your-super-secret-key-change-in-production'; const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h'; const BCRYPT_ROUNDS = 12; // ============================================ // Types // ============================================ export interface TokenPayload { userId: string; email: string; role: 'user' | 'admin'; } export interface AuthRequest extends Request { user?: TokenPayload; } // ============================================ // Password Hashing // ============================================ export async function hashPassword(password: string): Promise<string> { return bcrypt.hash(password, BCRYPT_ROUNDS); } export async function verifyPassword(password: string, hash: string): Promise<boolean> { return bcrypt.compare(password, hash); } // ============================================ // JWT Token Functions // ============================================ export function generateToken(payload: TokenPayload): string { return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN }); } export function verifyToken(token: string): TokenPayload | null { try { return jwt.verify(token, JWT_SECRET) as TokenPayload; } catch { return null; } } export function decodeToken(token: string): TokenPayload | null { try { return jwt.decode(token) as TokenPayload; } catch { return null; } } // ============================================ // Middleware // ============================================ export function authMiddleware(req: AuthRequest, res: Response, next: NextFunction) { const authHeader = req.headers.authorization; if (!authHeader?.startsWith('Bearer ')) { return res.status(401).json({ error: 'No token provided' }); } const token = authHeader.split(' ')[1]; const payload = verifyToken(token); if (!payload) { return res.status(401).json({ error: 'Invalid or expired token' }); } req.user = payload; next(); } export function requireRole(...roles: string[]) { return (req: AuthRequest, res: Response, next: NextFunction) => { if (!req.user) { return res.status(401).json({ error: 'Authentication required' }); } if (!roles.includes(req.user.role)) { return res.status(403).json({ error: 'Insufficient permissions' }); } next(); }; } export function optionalAuth(req: AuthRequest, res: Response, next: NextFunction) { const authHeader = req.headers.authorization; if (authHeader?.startsWith('Bearer ')) { const token = authHeader.split(' ')[1]; const payload = verifyToken(token); if (payload) { req.user = payload; } } next(); } // ============================================ // Refresh Token (In-Memory - Use Redis in production) // ============================================ const refreshTokens = new Map<string, { userId: string; expiresAt: Date }>(); export function generateRefreshToken(userId: string): string { const token = Buffer.from(`${userId}-${Date.now()}-${Math.random()}`).toString('base64'); const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days refreshTokens.set(token, { userId, expiresAt }); return token; } export function validateRefreshToken(token: string): string | null { const data = refreshTokens.get(token); if (!data) return null; if (data.expiresAt < new Date()) { refreshTokens.delete(token); return null; } return data.userId; } export function revokeRefreshToken(token: string): void { refreshTokens.delete(token); } // ============================================ // Rate Limiting (Simple In-Memory) // ============================================ const requestCounts = new Map<string, { count: number; resetAt: Date }>(); export function rateLimiter(maxRequests: number, windowMs: number) { return (req: Request, res: Response, next: NextFunction) => { const ip = req.ip || req.socket.remoteAddress || 'unknown'; const now = new Date(); const record = requestCounts.get(ip); if (!record || record.resetAt < now) { requestCounts.set(ip, { count: 1, resetAt: new Date(now.getTime() + windowMs) }); return next(); } if (record.count >= maxRequests) { const retryAfter = Math.ceil((record.resetAt.getTime() - now.getTime()) / 1000); res.set('Retry-After', retryAfter.toString()); return res.status(429).json({ error: 'Too many requests', retryAfter, }); } record.count++; next(); }; } // ============================================ // CSRF Protection // ============================================ const csrfTokens = new Map<string, Date>(); export function generateCsrfToken(): string { const token = Buffer.from(`${Date.now()}-${Math.random()}`).toString('base64'); csrfTokens.set(token, new Date(Date.now() + 3600000)); // 1 hour return token; } export function csrfProtection(req: Request, res: Response, next: NextFunction) { if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) { return next(); } const token = req.headers['x-csrf-token'] as string; if (!token || !csrfTokens.has(token)) { return res.status(403).json({ error: 'Invalid CSRF token' }); } const expiry = csrfTokens.get(token)!; if (expiry < new Date()) { csrfTokens.delete(token); return res.status(403).json({ error: 'CSRF token expired' }); } next(); } // ============================================ // Security Headers Middleware // ============================================ export function securityHeaders(req: Request, res: Response, next: NextFunction) { res.set({ 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'DENY', 'X-XSS-Protection': '1; mode=block', 'Referrer-Policy': 'strict-origin-when-cross-origin', 'Content-Security-Policy': "default-src 'self'", }); next(); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/millsydotdev/Code-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server