Skip to main content
Glama

Industrial MCP Server

by intecrel
consent-grants.ts10.2 kB
/** * OAuth Consent Grants Management with Redis/Upstash * Handles user consent grant storage and retrieval */ import { Redis } from '@upstash/redis'; export interface ConsentGrant { id: string; user_email: string; user_id?: string; client_id: string; client_name: string; scopes: string[]; granted_at: string; last_used: string; status: 'active' | 'revoked'; } // Redis client for consent grants let redis: Redis | null = null; const useRedis = process.env.ENABLE_REDIS_STORAGE === 'true' && process.env.UPSTASH_REDIS_REST_URL && process.env.UPSTASH_REDIS_REST_TOKEN; // In-memory fallback for development const memoryGrants = new Map<string, ConsentGrant>(); // Environment-based key prefix const getKeyPrefix = (): string => { if (process.env.VERCEL_ENV === 'production') return 'prod:'; if (process.env.VERCEL_ENV === 'preview') return 'preview:'; return 'local:'; }; /** * Initialize Redis client if enabled */ function getRedisClient(): Redis | null { if (!useRedis) return null; if (!redis) { redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); } return redis; } /** * Generate unique grant ID */ function generateGrantId(): string { return `grant_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Add a consent grant */ export async function addConsentGrant( userEmail: string, clientId: string, clientName: string, scopes: string[], userId?: string ): Promise<string> { const grantId = generateGrantId(); const now = new Date().toISOString(); const grant: ConsentGrant = { id: grantId, user_email: userEmail, user_id: userId, client_id: clientId, client_name: clientName, scopes, granted_at: now, last_used: now, status: 'active' }; const client = getRedisClient(); if (client) { try { const grantKey = `${getKeyPrefix()}oauth:grants:${grantId}`; const userGrantsKey = `${getKeyPrefix()}oauth:user_grants:${userEmail}`; const clientGrantsKey = `${getKeyPrefix()}oauth:client_grants:${clientId}`; // Store grant details (Redis expects Record<string, string>) await client.hset(grantKey, { ...grant, scopes: JSON.stringify(grant.scopes) }); // Add to user's grants list await client.sadd(userGrantsKey, grantId); // Add to client's grants list (for admin view) await client.sadd(clientGrantsKey, grantId); // Set expiration for cleanup (1 year) await client.expire(grantKey, 31536000); await client.expire(userGrantsKey, 31536000); await client.expire(clientGrantsKey, 31536000); } catch (error) { console.warn('Redis consent grant add failed, using memory fallback:', error); memoryGrants.set(grantId, grant); } } else { memoryGrants.set(grantId, grant); } return grantId; } /** * Get consent grants for a user */ export async function getUserConsentGrants(userEmail: string): Promise<ConsentGrant[]> { const client = getRedisClient(); if (client) { try { const userGrantsKey = `${getKeyPrefix()}oauth:user_grants:${userEmail}`; const grantIds = await client.smembers(userGrantsKey); if (grantIds.length === 0) return []; const grants: ConsentGrant[] = []; for (const grantId of grantIds) { const grantKey = `${getKeyPrefix()}oauth:grants:${grantId}`; const grantData = await client.hgetall(grantKey); if (grantData && Object.keys(grantData).length > 0) { grants.push({ id: grantData.id as string, user_email: grantData.user_email as string, user_id: grantData.user_id as string, client_id: grantData.client_id as string, client_name: grantData.client_name as string, granted_at: grantData.granted_at as string, last_used: grantData.last_used as string, status: grantData.status as 'active' | 'revoked', scopes: typeof grantData.scopes === 'string' ? JSON.parse(grantData.scopes) : grantData.scopes }); } } return grants; } catch (error) { console.warn('Redis consent grant fetch failed, using memory fallback:', error); return Array.from(memoryGrants.values()).filter(grant => grant.user_email === userEmail); } } else { return Array.from(memoryGrants.values()).filter(grant => grant.user_email === userEmail); } } /** * Get all consent grants for admin view */ export async function getAllConsentGrants(): Promise<ConsentGrant[]> { const client = getRedisClient(); if (client) { try { const pattern = `${getKeyPrefix()}oauth:grants:*`; const keys = await client.keys(pattern); const grants: ConsentGrant[] = []; for (const key of keys) { const grantData = await client.hgetall(key); if (grantData && Object.keys(grantData).length > 0) { grants.push({ id: grantData.id as string, user_email: grantData.user_email as string, user_id: grantData.user_id as string, client_id: grantData.client_id as string, client_name: grantData.client_name as string, granted_at: grantData.granted_at as string, last_used: grantData.last_used as string, status: grantData.status as 'active' | 'revoked', scopes: typeof grantData.scopes === 'string' ? JSON.parse(grantData.scopes) : grantData.scopes }); } } return grants.sort((a, b) => new Date(b.granted_at).getTime() - new Date(a.granted_at).getTime()); } catch (error) { console.warn('Redis all grants fetch failed, using memory fallback:', error); return Array.from(memoryGrants.values()).sort((a, b) => new Date(b.granted_at).getTime() - new Date(a.granted_at).getTime()); } } else { return Array.from(memoryGrants.values()).sort((a, b) => new Date(b.granted_at).getTime() - new Date(a.granted_at).getTime()); } } /** * Update last used timestamp for a grant */ export async function updateLastUsed(userEmail: string, clientId: string): Promise<void> { const client = getRedisClient(); if (client) { try { const userGrantsKey = `${getKeyPrefix()}oauth:user_grants:${userEmail}`; const grantIds = await client.smembers(userGrantsKey); for (const grantId of grantIds) { const grantKey = `${getKeyPrefix()}oauth:grants:${grantId}`; const grantData = await client.hgetall(grantKey); if (grantData && grantData.client_id === clientId && grantData.status === 'active') { await client.hset(grantKey, { last_used: new Date().toISOString() }); break; } } } catch (error) { console.warn('Redis last used update failed, using memory fallback:', error); const grant = Array.from(memoryGrants.values()).find(g => g.user_email === userEmail && g.client_id === clientId && g.status === 'active' ); if (grant) { grant.last_used = new Date().toISOString(); } } } else { const grant = Array.from(memoryGrants.values()).find(g => g.user_email === userEmail && g.client_id === clientId && g.status === 'active' ); if (grant) { grant.last_used = new Date().toISOString(); } } } /** * Revoke a consent grant */ export async function revokeGrant(grantId: string, userEmail: string): Promise<boolean> { const client = getRedisClient(); if (client) { try { const grantKey = `${getKeyPrefix()}oauth:grants:${grantId}`; const grantData = await client.hgetall(grantKey); if (grantData && grantData.user_email === userEmail) { await client.hset(grantKey, { status: 'revoked' }); return true; } return false; } catch (error) { console.warn('Redis grant revoke failed, using memory fallback:', error); const grant = memoryGrants.get(grantId); if (grant && grant.user_email === userEmail) { grant.status = 'revoked'; return true; } return false; } } else { const grant = memoryGrants.get(grantId); if (grant && grant.user_email === userEmail) { grant.status = 'revoked'; return true; } return false; } } /** * Get grants by client ID (for admin view) */ export async function getClientGrants(clientId: string): Promise<ConsentGrant[]> { const client = getRedisClient(); if (client) { try { const clientGrantsKey = `${getKeyPrefix()}oauth:client_grants:${clientId}`; const grantIds = await client.smembers(clientGrantsKey); const grants: ConsentGrant[] = []; for (const grantId of grantIds) { const grantKey = `${getKeyPrefix()}oauth:grants:${grantId}`; const grantData = await client.hgetall(grantKey); if (grantData && Object.keys(grantData).length > 0) { grants.push({ id: grantData.id as string, user_email: grantData.user_email as string, user_id: grantData.user_id as string, client_id: grantData.client_id as string, client_name: grantData.client_name as string, granted_at: grantData.granted_at as string, last_used: grantData.last_used as string, status: grantData.status as 'active' | 'revoked', scopes: typeof grantData.scopes === 'string' ? JSON.parse(grantData.scopes) : grantData.scopes }); } } return grants; } catch (error) { console.warn('Redis client grants fetch failed, using memory fallback:', error); return Array.from(memoryGrants.values()).filter(grant => grant.client_id === clientId); } } else { return Array.from(memoryGrants.values()).filter(grant => grant.client_id === clientId); } }

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/intecrel/industrial-mcp'

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