Skip to main content
Glama
jwt-auth.ts6.28 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