Initial commit
This commit is contained in:
207
mcp-server/auth/redisClient.js
Normal file
207
mcp-server/auth/redisClient.js
Normal file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* Redis Client for Session Persistence
|
||||
*
|
||||
* Provides persistent storage for user credentials across server restarts.
|
||||
* Falls back to in-memory Map if Redis is unavailable.
|
||||
*/
|
||||
|
||||
import { createClient } from 'redis';
|
||||
|
||||
const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379';
|
||||
const USER_CACHE_TTL = 30 * 60; // 30 minutes in seconds
|
||||
|
||||
let redisClient = null;
|
||||
let redisAvailable = false;
|
||||
|
||||
// Fallback in-memory cache (used if Redis is unavailable)
|
||||
const memoryCache = new Map();
|
||||
|
||||
/**
|
||||
* Initialize Redis client
|
||||
*/
|
||||
export async function initRedis() {
|
||||
try {
|
||||
console.error('[Redis] Connecting to Redis at', REDIS_URL);
|
||||
|
||||
redisClient = createClient({
|
||||
url: REDIS_URL,
|
||||
socket: {
|
||||
connectTimeout: 5000,
|
||||
reconnectStrategy: (retries) => {
|
||||
if (retries > 3) {
|
||||
console.error('[Redis] Max reconnection attempts reached, falling back to memory cache');
|
||||
redisAvailable = false;
|
||||
return false; // Stop reconnecting
|
||||
}
|
||||
return Math.min(retries * 100, 3000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
redisClient.on('error', (err) => {
|
||||
console.error('[Redis] Error:', err.message);
|
||||
redisAvailable = false;
|
||||
});
|
||||
|
||||
redisClient.on('connect', () => {
|
||||
console.error('[Redis] Connected successfully');
|
||||
redisAvailable = true;
|
||||
});
|
||||
|
||||
redisClient.on('reconnecting', () => {
|
||||
console.error('[Redis] Reconnecting...');
|
||||
});
|
||||
|
||||
await redisClient.connect();
|
||||
redisAvailable = true;
|
||||
console.error('[Redis] Ready to use');
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Redis] Failed to initialize:', error.message);
|
||||
console.error('[Redis] Falling back to in-memory cache');
|
||||
redisAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user credentials in cache (Redis or memory)
|
||||
*/
|
||||
export async function setUserCredentials(userIdentifier, credentials) {
|
||||
if (!userIdentifier) {
|
||||
console.error('[Redis] Cannot set credentials: no userIdentifier');
|
||||
return false;
|
||||
}
|
||||
|
||||
const key = `user:creds:${userIdentifier}`;
|
||||
const value = JSON.stringify({
|
||||
...credentials,
|
||||
lastUsed: Date.now()
|
||||
});
|
||||
|
||||
if (redisAvailable && redisClient) {
|
||||
try {
|
||||
await redisClient.setEx(key, USER_CACHE_TTL, value);
|
||||
console.error(`[Redis] Saved credentials for user ${userIdentifier} (TTL: ${USER_CACHE_TTL}s)`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[Redis] Error saving to Redis:', error.message);
|
||||
console.error('[Redis] Falling back to memory cache for this operation');
|
||||
redisAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to memory cache
|
||||
memoryCache.set(key, {
|
||||
value,
|
||||
expiresAt: Date.now() + (USER_CACHE_TTL * 1000)
|
||||
});
|
||||
console.error(`[Redis] Saved credentials for user ${userIdentifier} to memory cache`);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user credentials from cache (Redis or memory)
|
||||
*/
|
||||
export async function getUserCredentials(userIdentifier) {
|
||||
if (!userIdentifier) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const key = `user:creds:${userIdentifier}`;
|
||||
|
||||
if (redisAvailable && redisClient) {
|
||||
try {
|
||||
const value = await redisClient.get(key);
|
||||
if (value) {
|
||||
console.error(`[Redis] Retrieved credentials for user ${userIdentifier} from Redis`);
|
||||
const creds = JSON.parse(value);
|
||||
|
||||
// Update lastUsed timestamp
|
||||
await setUserCredentials(userIdentifier, {
|
||||
website: creds.website,
|
||||
token: creds.token,
|
||||
tokenHash: creds.tokenHash,
|
||||
profileName: creds.profileName
|
||||
});
|
||||
|
||||
return creds;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Redis] Error reading from Redis:', error.message);
|
||||
console.error('[Redis] Falling back to memory cache');
|
||||
redisAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to memory cache
|
||||
const cached = memoryCache.get(key);
|
||||
if (cached) {
|
||||
if (Date.now() < cached.expiresAt) {
|
||||
console.error(`[Redis] Retrieved credentials for user ${userIdentifier} from memory cache`);
|
||||
const creds = JSON.parse(cached.value);
|
||||
|
||||
// Update expiration
|
||||
memoryCache.set(key, {
|
||||
value: cached.value,
|
||||
expiresAt: Date.now() + (USER_CACHE_TTL * 1000)
|
||||
});
|
||||
|
||||
return creds;
|
||||
} else {
|
||||
console.error(`[Redis] Memory cache expired for user ${userIdentifier}`);
|
||||
memoryCache.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user credentials from cache
|
||||
*/
|
||||
export async function deleteUserCredentials(userIdentifier) {
|
||||
if (!userIdentifier) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = `user:creds:${userIdentifier}`;
|
||||
|
||||
if (redisAvailable && redisClient) {
|
||||
try {
|
||||
await redisClient.del(key);
|
||||
console.error(`[Redis] Deleted credentials for user ${userIdentifier} from Redis`);
|
||||
} catch (error) {
|
||||
console.error('[Redis] Error deleting from Redis:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Also delete from memory cache
|
||||
memoryCache.delete(key);
|
||||
console.error(`[Redis] Deleted credentials for user ${userIdentifier} from memory cache`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Redis health status
|
||||
*/
|
||||
export function getRedisStatus() {
|
||||
return {
|
||||
available: redisAvailable,
|
||||
connected: redisClient?.isOpen || false,
|
||||
url: REDIS_URL,
|
||||
fallbackCacheSize: memoryCache.size
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Close Redis connection (for graceful shutdown)
|
||||
*/
|
||||
export async function closeRedis() {
|
||||
if (redisClient) {
|
||||
try {
|
||||
await redisClient.quit();
|
||||
console.error('[Redis] Connection closed');
|
||||
} catch (error) {
|
||||
console.error('[Redis] Error closing connection:', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user