UNPKG

defarm-sdk

Version:

DeFarm SDK - On-premise blockchain data processing and tokenization engine for agriculture supply chain

340 lines (295 loc) 9.19 kB
/** * API Key Manager - Authentication and Authorization * * Controls access to DeFarm SDK features based on API keys and subscription plans */ const crypto = require('crypto'); const axios = require('axios'); class APIKeyManager { constructor(config = {}) { this.config = { apiEndpoint: config.apiEndpoint || process.env.DEFARM_API_ENDPOINT || 'https://api.defarm.io', cacheTimeout: config.cacheTimeout || 300000, // 5 minutes cache offlineMode: config.offlineMode || false, ...config }; // Cache validated API keys this.keyCache = new Map(); // Usage tracking this.usageTracker = new Map(); // Subscription plans and their limits this.plans = { trial: { name: 'Trial', tokenizations: 10, processings: 100, validityDays: 14, features: ['basic_processing', 'basic_validation'] }, starter: { name: 'Starter', tokenizations: 1000, processings: 10000, validityDays: 30, features: ['basic_processing', 'validation', 'duplicate_detection', 'basic_analytics'] }, professional: { name: 'Professional', tokenizations: 10000, processings: 100000, validityDays: 30, features: ['all_processing', 'validation', 'duplicate_detection', 'analytics', 'provenance', 'compliance'] }, enterprise: { name: 'Enterprise', tokenizations: 100000, processings: -1, // unlimited validityDays: 30, features: ['all'] // all features }, government: { name: 'Government', tokenizations: -1, // unlimited processings: -1, // unlimited validityDays: 365, features: ['all'] } }; } /** * Validate API key with DeFarm servers */ async validateAPIKey(apiKey) { if (!apiKey) { throw new Error('API key is required'); } // Check cache first const cached = this.keyCache.get(apiKey); if (cached && cached.expiresAt > Date.now()) { return cached.data; } // In offline mode, check if key format is valid if (this.config.offlineMode) { return this.validateOffline(apiKey); } try { // Validate with DeFarm API const response = await axios.post( `${this.config.apiEndpoint}/v1/auth/validate`, {}, { headers: { 'X-API-Key': apiKey, 'X-SDK-Version': '1.0.0', 'X-SDK-Platform': process.platform }, timeout: 5000 } ); const validationData = response.data; // Cache the result this.keyCache.set(apiKey, { data: validationData, expiresAt: Date.now() + this.config.cacheTimeout }); // Initialize usage tracking if (!this.usageTracker.has(apiKey)) { this.usageTracker.set(apiKey, { tokenizations: 0, processings: 0, lastReset: Date.now() }); } return validationData; } catch (error) { // If API is unreachable, check cache or use offline mode if (error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') { console.warn('⚠️ DeFarm API unreachable, using offline validation'); return this.validateOffline(apiKey); } if (error.response?.status === 401) { throw new Error('Invalid API key'); } else if (error.response?.status === 403) { throw new Error('API key expired or suspended'); } else if (error.response?.status === 429) { throw new Error('Rate limit exceeded'); } throw error; } } /** * Offline validation (basic format check) */ validateOffline(apiKey) { // Accept different key formats: // - Production: sk_live_ or sk_test_ followed by 32+ chars // - Demo: demo_ followed by any chars // - Development: dev_ followed by any chars const patterns = [ /^sk_(live|test)_[a-zA-Z0-9]{32,}$/, // Production keys /^demo_[a-zA-Z0-9_]{8,}$/, // Demo keys /^dev_[a-zA-Z0-9_]{8,}$/ // Development keys ]; const isValidFormat = patterns.some(pattern => pattern.test(apiKey)); if (!isValidFormat) { throw new Error('Invalid API key format'); } // Determine plan based on key type let plan = 'professional'; if (apiKey.startsWith('demo_')) { plan = 'trial'; } else if (apiKey.startsWith('dev_')) { plan = 'starter'; } else if (apiKey.includes('live')) { plan = 'professional'; } // In offline mode, provide appropriate plan features return { valid: true, plan: plan, organization: 'offline-mode', features: ['basic_processing', 'validation', 'tokenization'], limits: { tokenizations: plan === 'trial' ? 10 : plan === 'starter' ? 100 : 1000, // Limited in offline mode processings: 1000 }, offline: true }; } /** * Check if feature is available for API key */ async hasFeature(apiKey, feature) { const validation = await this.validateAPIKey(apiKey); if (!validation.valid) { return false; } const plan = this.plans[validation.plan]; if (!plan) { return false; } return plan.features.includes('all') || plan.features.includes(feature); } /** * Check and update usage limits */ async checkUsageLimit(apiKey, usageType = 'processings') { const validation = await this.validateAPIKey(apiKey); if (!validation.valid) { throw new Error('Invalid API key'); } const plan = this.plans[validation.plan]; if (!plan) { throw new Error('Unknown plan'); } // Get current usage let usage = this.usageTracker.get(apiKey); if (!usage) { usage = { tokenizations: 0, processings: 0, lastReset: Date.now() }; this.usageTracker.set(apiKey, usage); } // Check if we need to reset monthly usage const daysSinceReset = (Date.now() - usage.lastReset) / (1000 * 60 * 60 * 24); if (daysSinceReset >= 30) { usage.tokenizations = 0; usage.processings = 0; usage.lastReset = Date.now(); } // Check limits const limit = plan[usageType]; if (limit === -1) { return true; // Unlimited } if (usage[usageType] >= limit) { throw new Error(`${usageType} limit exceeded for plan ${plan.name}. Upgrade at https://defarm.io/pricing`); } // Increment usage usage[usageType]++; // Report usage to server (async, don't wait) this.reportUsage(apiKey, usageType).catch(console.error); return true; } /** * Report usage to DeFarm servers */ async reportUsage(apiKey, usageType) { if (this.config.offlineMode) { return; } try { await axios.post( `${this.config.apiEndpoint}/v1/usage/report`, { type: usageType, timestamp: Date.now(), metadata: { sdk_version: '1.0.0', platform: process.platform, node_version: process.version } }, { headers: { 'X-API-Key': apiKey }, timeout: 2000 // Quick timeout for usage reporting } ); } catch (error) { // Silently fail - don't interrupt operations console.debug('Usage reporting failed:', error.message); } } /** * Get current usage stats */ async getUsageStats(apiKey) { const validation = await this.validateAPIKey(apiKey); if (!validation.valid) { throw new Error('Invalid API key'); } const usage = this.usageTracker.get(apiKey) || { tokenizations: 0, processings: 0, lastReset: Date.now() }; const plan = this.plans[validation.plan]; return { plan: plan.name, usage: { tokenizations: { used: usage.tokenizations, limit: plan.tokenizations, remaining: plan.tokenizations === -1 ? 'unlimited' : Math.max(0, plan.tokenizations - usage.tokenizations) }, processings: { used: usage.processings, limit: plan.processings, remaining: plan.processings === -1 ? 'unlimited' : Math.max(0, plan.processings - usage.processings) } }, resetDate: new Date(usage.lastReset + (30 * 24 * 60 * 60 * 1000)).toISOString() }; } /** * Generate a test API key for development */ static generateTestKey() { const randomBytes = crypto.randomBytes(24).toString('hex'); return `sk_test_${randomBytes}`; } /** * Validate webhook signature from DeFarm */ validateWebhookSignature(payload, signature, secret) { const expectedSignature = crypto .createHmac('sha256', secret) .update(JSON.stringify(payload)) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); } } module.exports = { APIKeyManager };