Skip to main content
Glama
oauth-session-manager.ts5.88 kB
/** * OAuth 2.1 Session Manager for Multi-tenant MCP Server * Handles stateless session management with 24-hour expiration */ import crypto from 'crypto'; import jwt from 'jsonwebtoken'; export interface Session { sessionId: string; username: string; userKey: string; accountKey: string; divisionId: number; token: string; expiresAt: Date; createdAt: Date; realm?: string; isMSP: boolean; } export interface OAuthTokenPayload { sessionId: string; username: string; userKey: string; accountKey: string; divisionId: number; realm?: string; isMSP: boolean; iat: number; exp: number; } export class OAuthSessionManager { private sessions: Map<string, Session> = new Map(); private readonly JWT_SECRET: string; private readonly SESSION_DURATION_HOURS = 24; constructor() { // Use environment variable or generate a secure random secret this.JWT_SECRET = process.env.OAUTH_JWT_SECRET || crypto.randomBytes(64).toString('hex'); // Clean up expired sessions every hour setInterval(() => this.cleanupExpiredSessions(), 60 * 60 * 1000); } /** * Create a new session after successful authentication */ createSession( username: string, userKey: string, accountKey: string, divisionId: number, token: string, realm?: string, isMSP: boolean = false ): { sessionId: string; bearerToken: string; authUrl: string } { const sessionId = this.generateSessionId(); const expiresAt = new Date(); expiresAt.setHours(expiresAt.getHours() + this.SESSION_DURATION_HOURS); const session: Session = { sessionId, username, userKey, accountKey, divisionId, token, expiresAt, createdAt: new Date(), realm, isMSP }; this.sessions.set(sessionId, session); // Create JWT bearer token const bearerToken = this.createBearerToken(session); // Generate OAuth callback URL const authUrl = this.generateAuthUrl(sessionId, bearerToken); console.error(`[OAUTH] Created session ${sessionId} for ${username}, expires at ${expiresAt.toISOString()}`); return { sessionId, bearerToken, authUrl }; } /** * Create a JWT bearer token for the session */ private createBearerToken(session: Session): string { const payload: OAuthTokenPayload = { sessionId: session.sessionId, username: session.username, userKey: session.userKey, accountKey: session.accountKey, divisionId: session.divisionId, realm: session.realm, isMSP: session.isMSP, iat: Math.floor(Date.now() / 1000), exp: Math.floor(session.expiresAt.getTime() / 1000) }; return jwt.sign(payload, this.JWT_SECRET, { algorithm: 'HS256' }); } /** * Validate bearer token and return session */ validateBearerToken(bearerToken: string): Session | null { try { // Remove 'Bearer ' prefix if present const token = bearerToken.replace(/^Bearer\s+/i, ''); // Verify and decode JWT const payload = jwt.verify(token, this.JWT_SECRET) as OAuthTokenPayload; // Check if session exists and is not expired const session = this.sessions.get(payload.sessionId); if (!session) { console.error(`[OAUTH] Session ${payload.sessionId} not found`); return null; } if (new Date() > session.expiresAt) { console.error(`[OAUTH] Session ${payload.sessionId} expired`); this.sessions.delete(payload.sessionId); return null; } return session; } catch (error) { console.error('[OAUTH] Invalid bearer token:', error); return null; } } /** * Get session by ID */ getSession(sessionId: string): Session | null { const session = this.sessions.get(sessionId); if (!session) { return null; } // Check if expired if (new Date() > session.expiresAt) { this.sessions.delete(sessionId); return null; } return session; } /** * Remove a session */ removeSession(sessionId: string): void { this.sessions.delete(sessionId); console.error(`[OAUTH] Removed session ${sessionId}`); } /** * Generate a secure session ID */ private generateSessionId(): string { return crypto.randomBytes(32).toString('hex'); } /** * Generate OAuth authentication URL */ private generateAuthUrl(sessionId: string, bearerToken: string): string { const baseUrl = process.env.MCP_BASE_URL || 'https://mcp.umbrellacost.io'; const params = new URLSearchParams({ session_id: sessionId, token: bearerToken, redirect_uri: `${baseUrl}/oauth/callback` }); return `${baseUrl}/oauth/authorize?${params.toString()}`; } /** * Clean up expired sessions */ private cleanupExpiredSessions(): void { const now = new Date(); let cleaned = 0; for (const [sessionId, session] of this.sessions.entries()) { if (now > session.expiresAt) { this.sessions.delete(sessionId); cleaned++; } } if (cleaned > 0) { console.error(`[OAUTH] Cleaned up ${cleaned} expired sessions`); } } /** * Get active session count */ getActiveSessionCount(): number { return this.sessions.size; } /** * Get all active sessions (for monitoring) */ getActiveSessions(): Array<{ sessionId: string; username: string; expiresAt: Date }> { const activeSessions = []; const now = new Date(); for (const [sessionId, session] of this.sessions.entries()) { if (now <= session.expiresAt) { activeSessions.push({ sessionId, username: session.username, expiresAt: session.expiresAt }); } } return activeSessions; } }

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/daviddraiumbrella/invoice-monitoring'

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