UNPKG

ci-validation

Version:

πŸ‡ΊπŸ‡Ύ Complete TypeScript/JavaScript library for validating Uruguayan CI (CΓ©dula de Identidad) with official algorithm and government service integration

258 lines β€’ 9.61 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.RedisSessionStorage = void 0; /** * Redis-based session storage implementation * Compatible with Vercel serverless environment */ class RedisSessionStorage { constructor(options = {}) { this.keyPrefix = options.keyPrefix || "ci-validation:session:"; this.expirationTime = options.expirationTime || 24 * 60 * 60 * 1000; // 24 hours this.autoCleanup = options.autoCleanup !== false; // Initialize Redis connection this.initializeRedis(options.redisUrl); } /** * Initialize Redis connection */ async initializeRedis(redisUrl) { try { // Dynamically import Redis to avoid issues in environments where it's not available const Redis = await Promise.resolve().then(() => __importStar(require("ioredis"))); const connectionUrl = redisUrl || process.env.REDIS_URL || process.env.KV_URL || "redis://localhost:6379"; this.redis = new Redis.default(connectionUrl, { maxRetriesPerRequest: 3, lazyConnect: true, family: 4, // Use IPv4 }); // Test connection await this.redis.ping(); console.log("βœ… Redis connection established"); } catch (error) { console.error("❌ Redis connection failed:", error); throw new Error(`Redis connection failed: ${error}`); } } /** * Get the Redis key for a session */ getSessionKey(sessionId) { return `${this.keyPrefix}${sessionId}`; } /** * Save session data to Redis */ async saveSession(sessionId, sessionData) { try { const now = Date.now(); const enrichedSessionData = { ...sessionData, createdAt: sessionData.createdAt || now, lastUsed: now, expiresAt: now + this.expirationTime, }; const key = this.getSessionKey(sessionId); const serializedData = JSON.stringify(enrichedSessionData); // Set with TTL (Time To Live) in seconds const ttlSeconds = Math.floor(this.expirationTime / 1000); await this.redis.setex(key, ttlSeconds, serializedData); console.log(`βœ… Session saved to Redis: ${sessionId}`); } catch (error) { console.error(`❌ Error saving session to Redis ${sessionId}:`, error); throw error; } } /** * Load session data from Redis */ async loadSession(sessionId) { try { const key = this.getSessionKey(sessionId); const sessionData = await this.redis.get(key); if (!sessionData) { console.log(`ℹ️ Session not found in Redis: ${sessionId}`); return null; } const parsedSession = JSON.parse(sessionData); // Check if session is expired (additional check) if (parsedSession.expiresAt && Date.now() > parsedSession.expiresAt) { console.log(`⏰ Session ${sessionId} has expired, removing...`); await this.deleteSession(sessionId); return null; } // Update last used timestamp await this.touchSession(sessionId); console.log(`βœ… Session loaded from Redis: ${sessionId}`); return parsedSession; } catch (error) { console.error(`❌ Error loading session from Redis ${sessionId}:`, error); throw error; } } /** * Delete session from Redis */ async deleteSession(sessionId) { try { const key = this.getSessionKey(sessionId); const result = await this.redis.del(key); if (result === 1) { console.log(`πŸ—‘οΈ Session deleted from Redis: ${sessionId}`); } else { console.log(`ℹ️ Session already deleted from Redis: ${sessionId}`); } } catch (error) { console.error(`❌ Error deleting session from Redis ${sessionId}:`, error); throw error; } } /** * Check if session exists in Redis */ async sessionExists(sessionId) { try { const key = this.getSessionKey(sessionId); const exists = await this.redis.exists(key); return exists === 1; } catch (error) { console.error(`❌ Error checking session existence in Redis ${sessionId}:`, error); return false; } } /** * Update session's last used timestamp */ async touchSession(sessionId) { try { const key = this.getSessionKey(sessionId); const sessionData = await this.redis.get(key); if (sessionData) { const session = JSON.parse(sessionData); session.lastUsed = Date.now(); // Update with same TTL const ttl = await this.redis.ttl(key); if (ttl > 0) { await this.redis.setex(key, ttl, JSON.stringify(session)); } else { // If TTL is -1 (no expiration) or -2 (key doesn't exist), use default const ttlSeconds = Math.floor(this.expirationTime / 1000); await this.redis.setex(key, ttlSeconds, JSON.stringify(session)); } } } catch (error) { console.error(`❌ Error touching session in Redis ${sessionId}:`, error); } } /** * Clean up expired sessions (Redis handles this automatically with TTL) * This method is provided for interface compliance but isn't necessary for Redis */ async cleanupExpiredSessions() { try { // Redis automatically handles TTL expiration, but we can scan for manual cleanup if needed if (this.autoCleanup) { const pattern = `${this.keyPrefix}*`; const keys = await this.redis.keys(pattern); let cleanedCount = 0; for (const key of keys) { const sessionData = await this.redis.get(key); if (sessionData) { const session = JSON.parse(sessionData); if (session.expiresAt && Date.now() > session.expiresAt) { await this.redis.del(key); cleanedCount++; } } } if (cleanedCount > 0) { console.log(`🧹 Manually cleaned up ${cleanedCount} expired sessions from Redis`); } } } catch (error) { console.error("❌ Error during Redis session cleanup:", error); } } /** * Get session statistics (useful for debugging) */ async getSessionStats() { try { const pattern = `${this.keyPrefix}*`; const keys = await this.redis.keys(pattern); // Get memory usage if available let memoryUsage; try { const info = await this.redis.info("memory"); const match = info.match(/used_memory_human:(.+)/); if (match) { memoryUsage = match[1].trim(); } } catch { // Memory info not available } return { totalSessions: keys.length, memoryUsage, }; } catch (error) { console.error("❌ Error getting session stats:", error); return { totalSessions: 0 }; } } /** * Cleanup resources */ destroy() { if (this.redis) { this.redis.disconnect(); console.log("βœ… Redis connection closed"); } } } exports.RedisSessionStorage = RedisSessionStorage; //# sourceMappingURL=RedisSessionStorage.js.map