dbx-mcp-server
Version:
A Model Context Protocol server for Dropbox integration with AI-powered PDF analysis, multi-directory indexing, and parallel processing
87 lines • 3.72 kB
JavaScript
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import * as dotenv from 'dotenv';
dotenv.config();
function getValidatedKey() {
const key = process.env.TOKEN_ENCRYPTION_KEY || '';
if (!key) {
throw new McpError(ErrorCode.InvalidParams, 'TOKEN_ENCRYPTION_KEY must be set in environment variables');
}
// Convert base64 key to buffer if it's base64 encoded
const validatedKey = key.includes('+') || key.includes('/') || key.includes('=')
? Buffer.from(key, 'base64')
: Buffer.from(key);
// Ensure key is 32 bytes
if (validatedKey.length < 32) {
throw new McpError(ErrorCode.InvalidParams, 'TOKEN_ENCRYPTION_KEY must be at least 32 bytes when decoded');
}
return validatedKey;
}
const ALGORITHM = 'aes-256-gcm';
export function encryptData(data) {
try {
// Generate a random initialization vector
const iv = randomBytes(16);
// Create cipher with key and iv
const cipher = createCipheriv(ALGORITHM, getValidatedKey().slice(0, 32), iv);
// Convert data to JSON string
const jsonStr = JSON.stringify(data);
// Encrypt the data
let encryptedData = cipher.update(jsonStr, 'utf8', 'hex');
encryptedData += cipher.final('hex');
// Get the auth tag
const authTag = cipher.getAuthTag();
// Combine the encrypted data and auth tag
const finalEncryptedData = encryptedData + authTag.toString('hex');
return {
iv: iv.toString('hex'),
encryptedData: finalEncryptedData
};
}
catch (error) {
console.error('Encryption error:', error);
throw new McpError(ErrorCode.InternalError, 'Failed to encrypt token data');
}
}
export function decryptData(encryptedData) {
try {
// Extract the auth tag from the end of the encrypted data (last 16 bytes)
const authTagLength = 32; // 16 bytes in hex = 32 characters
const encryptedHex = encryptedData.encryptedData;
const authTag = Buffer.from(encryptedHex.slice(-authTagLength), 'hex');
const encryptedContent = encryptedHex.slice(0, -authTagLength);
// Create decipher
const decipher = createDecipheriv(ALGORITHM, getValidatedKey().slice(0, 32), Buffer.from(encryptedData.iv, 'hex'));
// Set auth tag
decipher.setAuthTag(authTag);
// Decrypt the data
let decrypted = decipher.update(encryptedContent, 'hex', 'utf8');
decrypted += decipher.final('utf8');
if (!decrypted) {
throw new Error('Decryption produced empty result');
}
return JSON.parse(decrypted);
}
catch (error) {
console.error('Decryption error:', error);
throw new McpError(ErrorCode.InternalError, 'Failed to decrypt token data. The data may be corrupted or the encryption key may be invalid.');
}
}
export function validateCorsOrigin(origin) {
const allowedOrigins = process.env.CORS_ALLOWED_ORIGINS?.split(',') || [];
return allowedOrigins.includes(origin);
}
export class TokenRefreshError extends Error {
constructor(message, code, retryable = true) {
super(message);
this.code = code;
this.retryable = retryable;
this.name = 'TokenRefreshError';
}
}
export const TOKEN_REFRESH_CONFIG = {
maxRetries: parseInt(process.env.MAX_TOKEN_REFRESH_RETRIES || '3', 10),
retryDelay: parseInt(process.env.TOKEN_REFRESH_RETRY_DELAY_MS || '1000', 10),
thresholdMinutes: parseInt(process.env.TOKEN_REFRESH_THRESHOLD_MINUTES || '5', 10)
};
//# sourceMappingURL=security-utils.js.map