212 lines
6.4 KiB
JavaScript
212 lines
6.4 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Redis Cache Helper for Partner Authentication
|
|
* Provides a distributed cache that can be shared across different processes
|
|
*/
|
|
|
|
const Redis = require('ioredis');
|
|
const env = require('./env');
|
|
const logger = require('./logger');
|
|
const pino = logger.child('redis_cache');
|
|
|
|
class RedisCache {
|
|
constructor() {
|
|
this.redis = null;
|
|
this.isConnected = false;
|
|
this.fallbackCache = new Map(); // In-memory fallback
|
|
this.initialize();
|
|
}
|
|
|
|
initialize() {
|
|
try {
|
|
this.redis = new Redis({
|
|
host: env.REDIS_HOST || 'localhost',
|
|
port: env.REDIS_PORT || 6379,
|
|
password: env.REDIS_PWD,
|
|
retryDelayOnFailover: 100,
|
|
enableReadyCheck: false,
|
|
lazyConnect: true,
|
|
maxRetriesPerRequest: 3
|
|
});
|
|
|
|
this.redis.on('connect', () => {
|
|
this.isConnected = true;
|
|
pino.info('Redis cache connected');
|
|
});
|
|
|
|
this.redis.on('error', (err) => {
|
|
this.isConnected = false;
|
|
pino.error({ err }, 'Redis cache error');
|
|
});
|
|
|
|
this.redis.on('close', () => {
|
|
this.isConnected = false;
|
|
pino.warn('Redis cache connection closed');
|
|
});
|
|
|
|
} catch (error) {
|
|
pino.error({ err: error }, 'Failed to initialize Redis cache');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate cache key for partner authentication
|
|
* @param {string} partnerCode - Partner code (e.g., 'SATLOC')
|
|
* @param {string} customerId - Customer ID
|
|
* @returns {string} Cache key
|
|
*/
|
|
getAuthCacheKey(partnerCode, customerId) {
|
|
return `partner:auth:${partnerCode}:${customerId}`;
|
|
}
|
|
|
|
/**
|
|
* Set authentication data in cache with expiration
|
|
* @param {string} partnerCode - Partner code
|
|
* @param {string} customerId - Customer ID
|
|
* @param {object} authData - Authentication data to cache
|
|
* @param {number} ttlSeconds - Time to live in seconds (default: 1 hour)
|
|
* @returns {Promise<boolean>} Success status
|
|
*/
|
|
async setAuth(partnerCode, customerId, authData, ttlSeconds = 3600) {
|
|
const key = this.getAuthCacheKey(partnerCode, customerId);
|
|
const dataToCache = {
|
|
...authData,
|
|
cachedAt: Date.now(),
|
|
expiresAt: Date.now() + (ttlSeconds * 1000)
|
|
};
|
|
|
|
if (this.isConnected) {
|
|
try {
|
|
const serializedData = JSON.stringify(dataToCache);
|
|
await this.redis.setex(key, ttlSeconds, serializedData);
|
|
pino.debug(`Cached auth data in Redis for ${partnerCode}:${customerId}`);
|
|
return true;
|
|
} catch (error) {
|
|
pino.error({ err: error }, 'Failed to cache auth data in Redis');
|
|
}
|
|
}
|
|
|
|
// Fallback to in-memory cache
|
|
this.fallbackCache.set(key, dataToCache);
|
|
pino.debug(`Cached auth data in memory for ${partnerCode}:${customerId}`);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get authentication data from cache
|
|
* @param {string} partnerCode - Partner code
|
|
* @param {string} customerId - Customer ID
|
|
* @returns {Promise<object|null>} Cached auth data or null
|
|
*/
|
|
async getAuth(partnerCode, customerId) {
|
|
const key = this.getAuthCacheKey(partnerCode, customerId);
|
|
|
|
if (this.isConnected) {
|
|
try {
|
|
const cachedData = await this.redis.get(key);
|
|
if (cachedData) {
|
|
const authData = JSON.parse(cachedData);
|
|
pino.debug(`Retrieved cached auth data from Redis for ${partnerCode}:${customerId}`);
|
|
return authData;
|
|
}
|
|
} catch (error) {
|
|
pino.error({ err: error }, 'Failed to retrieve cached auth data from Redis');
|
|
}
|
|
}
|
|
|
|
// Fallback to in-memory cache
|
|
const memoryData = this.fallbackCache.get(key);
|
|
if (memoryData) {
|
|
// Check if in-memory data is expired
|
|
if (memoryData.expiresAt && memoryData.expiresAt > Date.now()) {
|
|
pino.debug(`Retrieved cached auth data from memory for ${partnerCode}:${customerId}`);
|
|
return memoryData;
|
|
} else {
|
|
// Remove expired data
|
|
this.fallbackCache.delete(key);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Delete authentication data from cache
|
|
* @param {string} partnerCode - Partner code
|
|
* @param {string} customerId - Customer ID (optional, if not provided clears all for partner)
|
|
* @returns {Promise<boolean>} Success status
|
|
*/
|
|
async deleteAuth(partnerCode, customerId = null) {
|
|
let deleted = false;
|
|
|
|
if (this.isConnected) {
|
|
try {
|
|
if (customerId) {
|
|
const key = this.getAuthCacheKey(partnerCode, customerId);
|
|
await this.redis.del(key);
|
|
pino.debug(`Deleted cached auth data from Redis for ${partnerCode}:${customerId}`);
|
|
} else {
|
|
// Delete all auth data for the partner
|
|
const pattern = this.getAuthCacheKey(partnerCode, '*');
|
|
const keys = await this.redis.keys(pattern);
|
|
if (keys.length > 0) {
|
|
await this.redis.del(...keys);
|
|
pino.debug(`Deleted ${keys.length} cached auth entries from Redis for ${partnerCode}`);
|
|
}
|
|
}
|
|
deleted = true;
|
|
} catch (error) {
|
|
pino.error({ err: error }, 'Failed to delete cached auth data from Redis');
|
|
}
|
|
}
|
|
|
|
// Also clean from fallback cache
|
|
if (customerId) {
|
|
const key = this.getAuthCacheKey(partnerCode, customerId);
|
|
this.fallbackCache.delete(key);
|
|
} else {
|
|
// Delete all entries for the partner from memory
|
|
const keysToDelete = [];
|
|
for (const key of this.fallbackCache.keys()) {
|
|
if (key.startsWith(`partner:auth:${partnerCode}:`)) {
|
|
keysToDelete.push(key);
|
|
}
|
|
}
|
|
keysToDelete.forEach(key => this.fallbackCache.delete(key));
|
|
pino.debug(`Deleted ${keysToDelete.length} cached auth entries from memory for ${partnerCode}`);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check if auth data is still valid based on expiration time
|
|
* @param {object} authData - Cached auth data
|
|
* @param {number} healthCheckInterval - Health check interval in ms
|
|
* @returns {boolean} Whether auth data is still valid
|
|
*/
|
|
isAuthValid(authData, healthCheckInterval = 30000) {
|
|
if (!authData) return false;
|
|
|
|
const now = Date.now();
|
|
return authData.expiresAt > now &&
|
|
authData.lastHealthCheck &&
|
|
(now - authData.lastHealthCheck) < healthCheckInterval;
|
|
}
|
|
|
|
/**
|
|
* Close Redis connection
|
|
*/
|
|
async disconnect() {
|
|
if (this.redis) {
|
|
await this.redis.disconnect();
|
|
this.isConnected = false;
|
|
pino.info('Redis cache disconnected');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export singleton instance
|
|
module.exports = new RedisCache();
|