Skip to main content
Glama

Curupira

by drzln
session.ts7.54 kB
/** * @fileoverview Session repository for managing user sessions */ import { nanoid } from 'nanoid' import { createLogger, generateId } from '@curupira/shared' import type { AuthSession, User, CurupiraSessionId, CurupiraUserId } from '../types.js' const logger = createLogger({ name: 'session-repository' }) /** * In-memory session repository (replace with Redis in production) */ export class SessionRepository { private sessions = new Map<CurupiraSessionId, AuthSession>() private userSessions = new Map<CurupiraUserId, Set<CurupiraSessionId>>() constructor( private readonly maxSessionsPerUser = 10, private readonly sessionTtl = 24 * 60 * 60 * 1000 // 24 hours ) { this.startCleanupTimer() } /** * Create new session */ async createSession( user: User, userAgent?: string, ipAddress?: string ): Promise<AuthSession> { const sessionId = generateId() as CurupiraSessionId const now = Date.now() const session: AuthSession = { id: sessionId, userId: user.id, token: '', // Will be set by JWT provider refreshToken: nanoid(64), expiresAt: now + this.sessionTtl, createdAt: now, lastAccessAt: now, userAgent, ipAddress, active: true, metadata: {} } // Cleanup old sessions if user has too many await this.cleanupUserSessions(user.id) // Store session this.sessions.set(sessionId, session) // Update user session index if (!this.userSessions.has(user.id)) { this.userSessions.set(user.id, new Set()) } this.userSessions.get(user.id)!.add(sessionId) logger.debug({ sessionId, userId: user.id }, 'Session created') return session } /** * Find session by ID */ async findById(sessionId: CurupiraSessionId): Promise<AuthSession | null> { const session = this.sessions.get(sessionId) if (!session) { return null } // Check if session is expired if (!session.active || session.expiresAt < Date.now()) { await this.deleteSession(sessionId) return null } return session } /** * Find session by refresh token */ async findByRefreshToken(refreshToken: string): Promise<AuthSession | null> { for (const session of this.sessions.values()) { if (session.refreshToken === refreshToken && session.active) { // Check if session is expired if (session.expiresAt < Date.now()) { await this.deleteSession(session.id) return null } return session } } return null } /** * Update session token */ async updateToken(sessionId: CurupiraSessionId, token: string): Promise<boolean> { const session = this.sessions.get(sessionId) if (!session) { return false } session.token = token session.lastAccessAt = Date.now() return true } /** * Update last access time */ async updateLastAccess(sessionId: CurupiraSessionId): Promise<boolean> { const session = this.sessions.get(sessionId) if (!session) { return false } session.lastAccessAt = Date.now() return true } /** * Refresh session (extend expiration) */ async refreshSession(sessionId: CurupiraSessionId): Promise<AuthSession | null> { const session = this.sessions.get(sessionId) if (!session || !session.active) { return null } // Generate new refresh token session.refreshToken = nanoid(64) session.expiresAt = Date.now() + this.sessionTtl session.lastAccessAt = Date.now() logger.debug({ sessionId }, 'Session refreshed') return session } /** * Delete session */ async deleteSession(sessionId: CurupiraSessionId): Promise<boolean> { const session = this.sessions.get(sessionId) if (!session) { return false } // Remove from user session index const userSessions = this.userSessions.get(session.userId) if (userSessions) { userSessions.delete(sessionId) if (userSessions.size === 0) { this.userSessions.delete(session.userId) } } // Remove session this.sessions.delete(sessionId) logger.debug({ sessionId, userId: session.userId }, 'Session deleted') return true } /** * Delete all sessions for a user */ async deleteUserSessions(userId: CurupiraUserId): Promise<number> { const userSessions = this.userSessions.get(userId) if (!userSessions) { return 0 } let deletedCount = 0 for (const sessionId of userSessions) { if (await this.deleteSession(sessionId)) { deletedCount++ } } logger.debug({ userId, deletedCount }, 'User sessions deleted') return deletedCount } /** * Get all sessions for a user */ async getUserSessions(userId: CurupiraUserId): Promise<AuthSession[]> { const userSessions = this.userSessions.get(userId) if (!userSessions) { return [] } const sessions: AuthSession[] = [] for (const sessionId of userSessions) { const session = await this.findById(sessionId) if (session) { sessions.push(session) } } return sessions.sort((a, b) => b.lastAccessAt - a.lastAccessAt) } /** * Get session statistics */ async getStats(): Promise<{ total: number active: number expired: number byUser: number }> { const now = Date.now() const sessions = Array.from(this.sessions.values()) return { total: sessions.length, active: sessions.filter(s => s.active && s.expiresAt > now).length, expired: sessions.filter(s => s.expiresAt <= now).length, byUser: this.userSessions.size } } /** * Cleanup expired sessions */ async cleanupExpiredSessions(): Promise<number> { const now = Date.now() const expiredSessions: CurupiraSessionId[] = [] for (const [sessionId, session] of this.sessions) { if (session.expiresAt <= now) { expiredSessions.push(sessionId) } } let deletedCount = 0 for (const sessionId of expiredSessions) { if (await this.deleteSession(sessionId)) { deletedCount++ } } if (deletedCount > 0) { logger.debug({ deletedCount }, 'Expired sessions cleaned up') } return deletedCount } /** * Cleanup old sessions for a user (keep only recent ones) */ private async cleanupUserSessions(userId: CurupiraUserId): Promise<void> { const userSessions = await this.getUserSessions(userId) if (userSessions.length <= this.maxSessionsPerUser) { return } // Sort by last access (most recent first) const sortedSessions = userSessions.sort((a, b) => b.lastAccessAt - a.lastAccessAt) // Delete oldest sessions const sessionsToDelete = sortedSessions.slice(this.maxSessionsPerUser) for (const session of sessionsToDelete) { await this.deleteSession(session.id) } logger.debug( { userId, deleted: sessionsToDelete.length, remaining: this.maxSessionsPerUser }, 'User sessions cleaned up' ) } /** * Start periodic cleanup timer */ private startCleanupTimer(): void { const cleanupInterval = 60 * 60 * 1000 // 1 hour setInterval(async () => { try { await this.cleanupExpiredSessions() } catch (error) { logger.error({ error }, 'Session cleanup failed') } }, cleanupInterval) logger.debug({ cleanupInterval }, 'Session cleanup timer started') } }

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/drzln/curupira'

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