// SECURE - Environment-based API key management
class SecureAPIManager {
constructor() {
this.apis = {};
this.initialized = false;
}
async initialize() {
// Load from environment variables with validation
this.loadEnvironmentKeys();
// Or load from secrets manager
await this.loadFromSecretsManager();
// Validate all required keys
this.validateConfiguration();
this.initialized = true;
}
loadEnvironmentKeys() {
// Communication APIs
this.apis.twilio = {
accountSid: this.getRequiredEnv('TWILIO_ACCOUNT_SID'),
authToken: this.getRequiredEnv('TWILIO_AUTH_TOKEN'),
fromNumber: this.getRequiredEnv('TWILIO_FROM_NUMBER')
};
this.apis.sendgrid = {
apiKey: this.getRequiredEnv('SENDGRID_API_KEY'),
fromEmail: this.getRequiredEnv('SENDGRID_FROM_EMAIL')
};
// Analytics & Monitoring (optional keys)
this.apis.datadog = {
apiKey: process.env.DATADOG_API_KEY,
appKey: process.env.DATADOG_APP_KEY,
enabled: !!process.env.DATADOG_API_KEY
};
this.apis.sentry = {
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV || 'development',
enabled: !!process.env.SENTRY_DSN
};
}
async loadFromSecretsManager() {
// AWS Secrets Manager example
const AWS = require('aws-sdk'); // v2 SDK example
const secretsManager = new AWS.SecretsManager();
try {
const secretName = 'prod/api-keys';
const secret = await secretsManager.getSecretValue({
SecretId: secretName
}).promise();
const keys = JSON.parse(secret.SecretString);
// Merge with existing configuration
// Note: This is a simple merge; a more robust solution would be needed
Object.assign(this.apis, keys);
} catch (error) {
console.error('Failed to load secrets, falling back to environment variables:', error);
}
}
getRequiredEnv(key) {
const value = process.env[key];
if (!value) {
throw new Error(`Required environment variable ${key} is not set`);
}
return value;
}
validateConfiguration() {
// Validate API key formats
const validators = {
twilio: (config) => {
if (!config.accountSid?.startsWith('AC')) {
throw new Error('Invalid Twilio Account SID format');
}
},
sendgrid: (config) => {
if (!config.apiKey?.startsWith('SG.')) {
throw new Error('Invalid SendGrid API key format');
}
},
stripe: (config) => { // Example if stripe was loaded
if (config.apiKey?.startsWith('sk_live_') &&
process.env.NODE_ENV !== 'production') {
throw new Error('Live Stripe key used in non-production environment');
}
}
};
for (const [name, validator] of Object.entries(validators)) {
if (this.apis[name]) {
validator(this.apis[name]);
}
}
}
// Rate limiting wrapper (conceptual example)
createRateLimitedClient(apiName, client) {
// const rateLimit = require('express-rate-limit'); // Example library
const limits = {
twilio: { requests: 100, window: 60000 }, // 100 req/min
sendgrid: { requests: 100, window: 1000 }, // 100 req/sec
openai: { requests: 60, window: 60000 }, // 60 req/min
};
const limit = limits[apiName] || { requests: 100, window: 60000 };
// This is pseudo-code; real implementation would be more complex
return new Proxy(client, {
get: (target, prop) => {
if (typeof target[prop] === 'function') {
return async (...args) => {
// Implement actual rate limiting logic here
// await this.checkRateLimit(apiName, limit);
return target[prop](...args);
};
}
return target[prop];
}
});
}
// Secure client initialization
getTwilioClient() {
if (!this.apis.twilio) {
throw new Error('Twilio not configured');
}
const twilio = require('twilio');
const client = twilio(
this.apis.twilio.accountSid,
this.apis.twilio.authToken,
{
lazyLoading: true,
edge: 'sydney', // Use edge location
logLevel: 'error'
}
);
return this.createRateLimitedClient('twilio', client);
}
getSendGridClient() {
if (!this.apis.sendgrid) {
throw new Error('SendGrid not configured');
}
const sgMail = require('@sendgrid/mail');
sgMail.setApiKey(this.apis.sendgrid.apiKey);
// Add request interceptor for monitoring
sgMail.setSubstitutionWrappers('{{', '}}');
return sgMail;
}
}