Skip to main content
Glama
northernvariables

FedMCP - Federal Parliamentary Information

auth.ts7.87 kB
/** * Authentication and Authorization utilities for GraphQL API * * Provides API key management, JWT verification, and access control */ import jwt from 'jsonwebtoken'; import crypto from 'crypto'; /** * API Key structure */ export interface APIKey { key: string; name: string; created: Date; expiresAt?: Date; rateLimit: number; // requests per hour permissions: string[]; } /** * User context extracted from authentication */ export interface AuthContext { apiKey?: string; keyName?: string; permissions: string[]; authenticated: boolean; ip?: string; } /** * Generate a cryptographically secure API key * * @returns A 64-character hexadecimal API key */ export function generateAPIKey(): string { return crypto.randomBytes(32).toString('hex'); } /** * Hash an API key for secure storage * * @param apiKey - The API key to hash * @returns SHA-256 hash of the API key */ export function hashAPIKey(apiKey: string): string { return crypto.createHash('sha256').update(apiKey).digest('hex'); } /** * In-memory API key store * In production, this should be replaced with a database or secret manager */ const API_KEYS: Map<string, APIKey> = new Map(); /** * Initialize default API keys from environment variables */ export function initializeAPIKeys(): void { // Frontend API key - full access const frontendKey = process.env.FRONTEND_API_KEY; if (frontendKey) { const hashedKey = hashAPIKey(frontendKey); API_KEYS.set(hashedKey, { key: hashedKey, name: 'frontend', created: new Date(), rateLimit: 10000, // 10k requests/hour permissions: ['read', 'write'], }); console.log('✓ Frontend API key registered'); } // Public read-only API key (optional - for public access) const publicKey = process.env.PUBLIC_API_KEY; if (publicKey) { const hashedKey = hashAPIKey(publicKey); API_KEYS.set(hashedKey, { key: hashedKey, name: 'public', created: new Date(), rateLimit: 1000, // 1k requests/hour permissions: ['read'], }); console.log('✓ Public API key registered'); } // Admin API key - for management operations const adminKey = process.env.ADMIN_API_KEY; if (adminKey) { const hashedKey = hashAPIKey(adminKey); API_KEYS.set(hashedKey, { key: hashedKey, name: 'admin', created: new Date(), rateLimit: 50000, // 50k requests/hour permissions: ['read', 'write', 'admin'], }); console.log('✓ Admin API key registered'); } if (API_KEYS.size === 0) { console.warn('⚠️ WARNING: No API keys configured! API will be publicly accessible.'); console.warn(' Set FRONTEND_API_KEY, PUBLIC_API_KEY, or ADMIN_API_KEY in environment'); } } /** * Validate an API key * * @param apiKey - The API key to validate * @returns The API key object if valid, null otherwise */ export function validateAPIKey(apiKey: string): APIKey | null { const hashedKey = hashAPIKey(apiKey); const keyData = API_KEYS.get(hashedKey); if (!keyData) { return null; } // Check if key has expired if (keyData.expiresAt && keyData.expiresAt < new Date()) { return null; } return keyData; } /** * Extract API key from request headers * Supports multiple authentication methods: * - Authorization: Bearer <api-key> * - X-API-Key: <api-key> * * @param request - The incoming HTTP request * @returns The API key if found, null otherwise */ export function extractAPIKey(request: any): string | null { // Check Authorization header (Bearer token) const authHeader = request.headers.get('authorization'); if (authHeader) { const match = authHeader.match(/^Bearer\s+(.+)$/i); if (match) { return match[1]; } } // Check X-API-Key header const apiKeyHeader = request.headers.get('x-api-key'); if (apiKeyHeader) { return apiKeyHeader; } return null; } /** * Extract client IP address from request * Handles X-Forwarded-For header for proxied requests * * @param request - The incoming HTTP request * @returns The client IP address */ export function extractClientIP(request: any): string { // Check X-Forwarded-For header (Cloud Run, proxies) const forwardedFor = request.headers.get('x-forwarded-for'); if (forwardedFor) { // Take the first IP in the list (original client) return forwardedFor.split(',')[0].trim(); } // Check X-Real-IP header const realIP = request.headers.get('x-real-ip'); if (realIP) { return realIP; } // Fallback to connection remote address (not available in all environments) return 'unknown'; } /** * Authenticate a request and return auth context * * @param request - The incoming HTTP request * @returns Authentication context */ export async function authenticateRequest(request: any): Promise<AuthContext> { const apiKey = extractAPIKey(request); const ip = extractClientIP(request); // If no API key provided if (!apiKey) { // Check if API keys are required (if any keys are configured) if (API_KEYS.size > 0) { return { authenticated: false, permissions: [], ip, }; } else { // No API keys configured - allow unauthenticated access (backward compatibility) console.warn('⚠️ Unauthenticated request allowed (no API keys configured)'); return { authenticated: true, permissions: ['read', 'write'], // Full access when no keys configured ip, }; } } // Validate API key const keyData = validateAPIKey(apiKey); if (!keyData) { return { authenticated: false, permissions: [], ip, }; } return { authenticated: true, apiKey: keyData.key, keyName: keyData.name, permissions: keyData.permissions, ip, }; } /** * Check if authenticated user has a specific permission * * @param context - Authentication context * @param permission - Required permission * @returns True if user has permission */ export function hasPermission(context: AuthContext, permission: string): boolean { if (!context.authenticated) { return false; } return context.permissions.includes(permission) || context.permissions.includes('admin'); } /** * Require authentication middleware * Throws an error if user is not authenticated * * @param context - Authentication context */ export function requireAuth(context: AuthContext): void { if (!context.authenticated) { throw new Error('Authentication required. Provide a valid API key via Authorization header or X-API-Key header.'); } } /** * Require specific permission middleware * Throws an error if user does not have required permission * * @param context - Authentication context * @param permission - Required permission */ export function requirePermission(context: AuthContext, permission: string): void { requireAuth(context); if (!hasPermission(context, permission)) { throw new Error(`Permission denied. Required permission: ${permission}`); } } /** * Generate a JWT token (optional - for session-based auth) * * @param payload - JWT payload * @param secret - JWT signing secret * @param expiresIn - Token expiration (default: 24h) * @returns Signed JWT token */ export function generateJWT( payload: Record<string, any>, secret: string, expiresIn: string | number = '24h' ): string { return jwt.sign(payload, secret, { expiresIn } as jwt.SignOptions); } /** * Verify a JWT token (optional - for session-based auth) * * @param token - JWT token to verify * @param secret - JWT signing secret * @returns Decoded token payload, or null if invalid */ export function verifyJWT(token: string, secret: string): Record<string, any> | null { try { return jwt.verify(token, secret) as Record<string, any>; } catch (error) { return null; } }

Latest Blog Posts

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/northernvariables/FedMCP'

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