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
JavaScript
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
;