first
This commit is contained in:
438
src/config/ConfigManager.js
Normal file
438
src/config/ConfigManager.js
Normal file
@@ -0,0 +1,438 @@
|
||||
/**
|
||||
* @fileoverview Configuration Manager for Laravel Healthcare MCP Server
|
||||
* Handles environment variables, validation, and configuration loading
|
||||
*/
|
||||
|
||||
import dotenv from "dotenv";
|
||||
import { logger } from "../utils/logger.js";
|
||||
import { ConfigurationError } from "../utils/errors.js";
|
||||
|
||||
/**
|
||||
* Configuration Manager class
|
||||
* Manages application configuration and environment variables
|
||||
*/
|
||||
export class ConfigManager {
|
||||
/**
|
||||
* Create ConfigManager instance
|
||||
* @param {string} envPath - Path to .env file (optional)
|
||||
*/
|
||||
constructor(envPath = null) {
|
||||
this.config = {};
|
||||
this.validationRules = this._defineValidationRules();
|
||||
|
||||
// Load environment variables
|
||||
this._loadEnvironment(envPath);
|
||||
|
||||
// Load and validate configuration
|
||||
this._loadConfiguration();
|
||||
this._validateConfiguration();
|
||||
|
||||
logger.info("Configuration loaded and validated successfully");
|
||||
}
|
||||
|
||||
/**
|
||||
* Load environment variables from .env file
|
||||
* @private
|
||||
* @param {string} envPath - Path to .env file
|
||||
*/
|
||||
_loadEnvironment(envPath) {
|
||||
try {
|
||||
const result = dotenv.config({ path: envPath });
|
||||
|
||||
if (result.error && envPath) {
|
||||
logger.warn(
|
||||
`Failed to load .env file from ${envPath}:`,
|
||||
result.error.message
|
||||
);
|
||||
}
|
||||
|
||||
logger.debug("Environment variables loaded");
|
||||
} catch (error) {
|
||||
logger.warn("Error loading environment variables:", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from environment variables
|
||||
* @private
|
||||
*/
|
||||
_loadConfiguration() {
|
||||
// Laravel API Configuration
|
||||
this.config.LARAVEL_API_BASE_URL = process.env.LARAVEL_API_BASE_URL;
|
||||
this.config.LARAVEL_API_TIMEOUT =
|
||||
parseInt(process.env.LARAVEL_API_TIMEOUT) || 30000;
|
||||
this.config.LARAVEL_API_RETRY_ATTEMPTS =
|
||||
parseInt(process.env.LARAVEL_API_RETRY_ATTEMPTS) || 3;
|
||||
this.config.LARAVEL_API_RETRY_DELAY =
|
||||
parseInt(process.env.LARAVEL_API_RETRY_DELAY) || 1000;
|
||||
|
||||
// MCP Server Configuration
|
||||
this.config.MCP_SERVER_NAME =
|
||||
process.env.MCP_SERVER_NAME || "laravel-healthcare-mcp-server";
|
||||
this.config.MCP_SERVER_VERSION = process.env.MCP_SERVER_VERSION || "1.0.0";
|
||||
this.config.MCP_SERVER_PORT = parseInt(process.env.MCP_SERVER_PORT) || 3000;
|
||||
|
||||
// Authentication Configuration
|
||||
this.config.ADMIN_USERNAME = process.env.ADMIN_USERNAME;
|
||||
this.config.ADMIN_PASSWORD = process.env.ADMIN_PASSWORD;
|
||||
this.config.ADMIN_LOGIN_ENDPOINT =
|
||||
process.env.ADMIN_LOGIN_ENDPOINT || "/api/admin/login";
|
||||
this.config.ADMIN_TOKEN_REFRESH_THRESHOLD =
|
||||
parseInt(process.env.ADMIN_TOKEN_REFRESH_THRESHOLD) || 300;
|
||||
|
||||
this.config.AGENT_USERNAME = process.env.AGENT_USERNAME;
|
||||
this.config.AGENT_PASSWORD = process.env.AGENT_PASSWORD;
|
||||
this.config.AGENT_LOGIN_ENDPOINT =
|
||||
process.env.AGENT_LOGIN_ENDPOINT || "/agent/login/post";
|
||||
this.config.AGENT_TOKEN_REFRESH_THRESHOLD =
|
||||
parseInt(process.env.AGENT_TOKEN_REFRESH_THRESHOLD) || 300;
|
||||
|
||||
this.config.PATIENT_USERNAME = process.env.PATIENT_USERNAME;
|
||||
this.config.PATIENT_PASSWORD = process.env.PATIENT_PASSWORD;
|
||||
this.config.PATIENT_LOGIN_ENDPOINT =
|
||||
process.env.PATIENT_LOGIN_ENDPOINT || "/api/frontend/login";
|
||||
this.config.PATIENT_TOKEN_REFRESH_THRESHOLD =
|
||||
parseInt(process.env.PATIENT_TOKEN_REFRESH_THRESHOLD) || 300;
|
||||
|
||||
this.config.PRACTITIONER_USERNAME = process.env.PRACTITIONER_USERNAME;
|
||||
this.config.PRACTITIONER_PASSWORD = process.env.PRACTITIONER_PASSWORD;
|
||||
this.config.PRACTITIONER_LOGIN_ENDPOINT =
|
||||
process.env.PRACTITIONER_LOGIN_ENDPOINT || "/api/practitioner/login";
|
||||
this.config.PRACTITIONER_TOKEN_REFRESH_THRESHOLD =
|
||||
parseInt(process.env.PRACTITIONER_TOKEN_REFRESH_THRESHOLD) || 300;
|
||||
|
||||
this.config.AFFILIATE_USERNAME = process.env.AFFILIATE_USERNAME;
|
||||
this.config.AFFILIATE_PASSWORD = process.env.AFFILIATE_PASSWORD;
|
||||
this.config.AFFILIATE_LOGIN_ENDPOINT =
|
||||
process.env.AFFILIATE_LOGIN_ENDPOINT || "/api/affiliate/login";
|
||||
this.config.AFFILIATE_TOKEN_REFRESH_THRESHOLD =
|
||||
parseInt(process.env.AFFILIATE_TOKEN_REFRESH_THRESHOLD) || 300;
|
||||
|
||||
this.config.PARTNER_USERNAME = process.env.PARTNER_USERNAME;
|
||||
this.config.PARTNER_PASSWORD = process.env.PARTNER_PASSWORD;
|
||||
this.config.PARTNER_LOGIN_ENDPOINT =
|
||||
process.env.PARTNER_LOGIN_ENDPOINT || "/api/partner/login";
|
||||
this.config.PARTNER_TOKEN_REFRESH_THRESHOLD =
|
||||
parseInt(process.env.PARTNER_TOKEN_REFRESH_THRESHOLD) || 300;
|
||||
|
||||
this.config.NETWORK_USERNAME = process.env.NETWORK_USERNAME;
|
||||
this.config.NETWORK_PASSWORD = process.env.NETWORK_PASSWORD;
|
||||
this.config.NETWORK_LOGIN_ENDPOINT =
|
||||
process.env.NETWORK_LOGIN_ENDPOINT || "/api/network/login";
|
||||
this.config.NETWORK_TOKEN_REFRESH_THRESHOLD =
|
||||
parseInt(process.env.NETWORK_TOKEN_REFRESH_THRESHOLD) || 300;
|
||||
|
||||
this.config.DOCTOR_USERNAME = process.env.DOCTOR_USERNAME;
|
||||
this.config.DOCTOR_PASSWORD = process.env.DOCTOR_PASSWORD;
|
||||
this.config.DOCTOR_LOGIN_ENDPOINT =
|
||||
process.env.DOCTOR_LOGIN_ENDPOINT || "/api/doctor/login";
|
||||
this.config.DOCTOR_TOKEN_REFRESH_THRESHOLD =
|
||||
parseInt(process.env.DOCTOR_TOKEN_REFRESH_THRESHOLD) || 300;
|
||||
|
||||
this.config.PROVIDER_USERNAME = process.env.PROVIDER_USERNAME;
|
||||
this.config.PROVIDER_PASSWORD = process.env.PROVIDER_PASSWORD;
|
||||
this.config.PROVIDER_LOGIN_ENDPOINT =
|
||||
process.env.PROVIDER_LOGIN_ENDPOINT || "/api/provider/login";
|
||||
this.config.PROVIDER_TOKEN_REFRESH_THRESHOLD =
|
||||
parseInt(process.env.PROVIDER_TOKEN_REFRESH_THRESHOLD) || 300;
|
||||
|
||||
// Token Management
|
||||
this.config.TOKEN_CACHE_DURATION =
|
||||
parseInt(process.env.TOKEN_CACHE_DURATION) || 3600;
|
||||
this.config.TOKEN_REFRESH_BUFFER =
|
||||
parseInt(process.env.TOKEN_REFRESH_BUFFER) || 300;
|
||||
this.config.MAX_CONCURRENT_REQUESTS =
|
||||
parseInt(process.env.MAX_CONCURRENT_REQUESTS) || 10;
|
||||
|
||||
// Logging Configuration
|
||||
this.config.LOG_LEVEL = process.env.LOG_LEVEL || "info";
|
||||
this.config.LOG_FILE_PATH =
|
||||
process.env.LOG_FILE_PATH || "./logs/mcp-server.log";
|
||||
this.config.LOG_MAX_SIZE = process.env.LOG_MAX_SIZE || "10m";
|
||||
this.config.LOG_MAX_FILES = process.env.LOG_MAX_FILES || "5";
|
||||
this.config.LOG_DATE_PATTERN = process.env.LOG_DATE_PATTERN || "YYYY-MM-DD";
|
||||
this.config.ENABLE_REQUEST_LOGGING =
|
||||
process.env.ENABLE_REQUEST_LOGGING || "true";
|
||||
this.config.MASK_SENSITIVE_DATA = process.env.MASK_SENSITIVE_DATA || "true";
|
||||
|
||||
// Error Handling
|
||||
this.config.ENABLE_DETAILED_ERRORS =
|
||||
process.env.ENABLE_DETAILED_ERRORS === "true";
|
||||
this.config.HIPAA_COMPLIANCE_MODE =
|
||||
process.env.HIPAA_COMPLIANCE_MODE !== "false";
|
||||
this.config.ERROR_REPORTING_LEVEL =
|
||||
process.env.ERROR_REPORTING_LEVEL || "production";
|
||||
|
||||
// Rate Limiting
|
||||
this.config.RATE_LIMIT_ENABLED = process.env.RATE_LIMIT_ENABLED !== "false";
|
||||
this.config.RATE_LIMIT_WINDOW =
|
||||
parseInt(process.env.RATE_LIMIT_WINDOW) || 60000;
|
||||
this.config.RATE_LIMIT_MAX_REQUESTS =
|
||||
parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 100;
|
||||
|
||||
// Health Check
|
||||
this.config.HEALTH_CHECK_ENABLED =
|
||||
process.env.HEALTH_CHECK_ENABLED !== "false";
|
||||
this.config.HEALTH_CHECK_INTERVAL =
|
||||
parseInt(process.env.HEALTH_CHECK_INTERVAL) || 30000;
|
||||
this.config.HEALTH_CHECK_ENDPOINT =
|
||||
process.env.HEALTH_CHECK_ENDPOINT || "/health";
|
||||
|
||||
// Development Settings
|
||||
this.config.NODE_ENV = process.env.NODE_ENV || "production";
|
||||
this.config.DEBUG_MODE = process.env.DEBUG_MODE === "true";
|
||||
this.config.ENABLE_CORS = process.env.ENABLE_CORS !== "false";
|
||||
this.config.CORS_ORIGINS = process.env.CORS_ORIGINS || "*";
|
||||
}
|
||||
|
||||
/**
|
||||
* Define validation rules for configuration
|
||||
* @private
|
||||
* @returns {Object} Validation rules
|
||||
*/
|
||||
_defineValidationRules() {
|
||||
return {
|
||||
required: ["LARAVEL_API_BASE_URL"],
|
||||
optional: ["MCP_SERVER_NAME", "MCP_SERVER_VERSION"],
|
||||
authCredentials: [
|
||||
"ADMIN_USERNAME",
|
||||
"ADMIN_PASSWORD",
|
||||
"AGENT_USERNAME",
|
||||
"AGENT_PASSWORD",
|
||||
"PATIENT_USERNAME",
|
||||
"PATIENT_PASSWORD",
|
||||
"PRACTITIONER_USERNAME",
|
||||
"PRACTITIONER_PASSWORD",
|
||||
"AFFILIATE_USERNAME",
|
||||
"AFFILIATE_PASSWORD",
|
||||
"PARTNER_USERNAME",
|
||||
"PARTNER_PASSWORD",
|
||||
"NETWORK_USERNAME",
|
||||
"NETWORK_PASSWORD",
|
||||
"DOCTOR_USERNAME",
|
||||
"DOCTOR_PASSWORD",
|
||||
"PROVIDER_USERNAME",
|
||||
"PROVIDER_PASSWORD",
|
||||
],
|
||||
numeric: [
|
||||
"LARAVEL_API_TIMEOUT",
|
||||
"LARAVEL_API_RETRY_ATTEMPTS",
|
||||
"LARAVEL_API_RETRY_DELAY",
|
||||
"MCP_SERVER_PORT",
|
||||
"TOKEN_CACHE_DURATION",
|
||||
"TOKEN_REFRESH_BUFFER",
|
||||
"MAX_CONCURRENT_REQUESTS",
|
||||
],
|
||||
urls: ["LARAVEL_API_BASE_URL"],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate configuration
|
||||
* @private
|
||||
* @throws {ConfigurationError} If validation fails
|
||||
*/
|
||||
_validateConfiguration() {
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
|
||||
// Check required fields
|
||||
this.validationRules.required.forEach((key) => {
|
||||
if (!this.config[key]) {
|
||||
errors.push(`Required configuration missing: ${key}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Validate URLs
|
||||
this.validationRules.urls.forEach((key) => {
|
||||
if (this.config[key]) {
|
||||
try {
|
||||
new URL(this.config[key]);
|
||||
} catch (error) {
|
||||
errors.push(`Invalid URL format for ${key}: ${this.config[key]}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Validate numeric values
|
||||
this.validationRules.numeric.forEach((key) => {
|
||||
if (
|
||||
this.config[key] !== undefined &&
|
||||
(isNaN(this.config[key]) || this.config[key] < 0)
|
||||
) {
|
||||
errors.push(`Invalid numeric value for ${key}: ${this.config[key]}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Check authentication credentials (warnings only)
|
||||
const authTypes = [
|
||||
"ADMIN",
|
||||
"AGENT",
|
||||
"PATIENT",
|
||||
"PRACTITIONER",
|
||||
"AFFILIATE",
|
||||
"PARTNER",
|
||||
"NETWORK",
|
||||
"DOCTOR",
|
||||
"PROVIDER",
|
||||
];
|
||||
authTypes.forEach((authType) => {
|
||||
const usernameKey = `${authType}_USERNAME`;
|
||||
const passwordKey = `${authType}_PASSWORD`;
|
||||
|
||||
if (!this.config[usernameKey] || !this.config[passwordKey]) {
|
||||
warnings.push(`${authType} authentication credentials not configured`);
|
||||
}
|
||||
});
|
||||
|
||||
// Security warnings
|
||||
if (
|
||||
this.config.ENABLE_REQUEST_LOGGING === "true" &&
|
||||
this.config.MASK_SENSITIVE_DATA !== "true"
|
||||
) {
|
||||
warnings.push(
|
||||
"Request logging enabled without sensitive data masking - potential security risk"
|
||||
);
|
||||
}
|
||||
|
||||
if (this.config.HIPAA_COMPLIANCE_MODE !== true) {
|
||||
warnings.push(
|
||||
"HIPAA compliance mode disabled - ensure this is intentional for healthcare data"
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
this.config.DEBUG_MODE === true &&
|
||||
this.config.NODE_ENV === "production"
|
||||
) {
|
||||
warnings.push("Debug mode enabled in production environment");
|
||||
}
|
||||
|
||||
// Log warnings
|
||||
warnings.forEach((warning) =>
|
||||
logger.warn(`Configuration warning: ${warning}`)
|
||||
);
|
||||
|
||||
// Throw error if validation fails
|
||||
if (errors.length > 0) {
|
||||
const errorMessage = `Configuration validation failed:\n${errors.join(
|
||||
"\n"
|
||||
)}`;
|
||||
logger.error(errorMessage);
|
||||
throw new ConfigurationError(errorMessage, "VALIDATION_FAILED", {
|
||||
errors,
|
||||
warnings,
|
||||
});
|
||||
}
|
||||
|
||||
if (warnings.length > 0) {
|
||||
logger.info(`Configuration loaded with ${warnings.length} warnings`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration value
|
||||
* @param {string} key - Configuration key
|
||||
* @param {*} defaultValue - Default value if key not found
|
||||
* @returns {*} Configuration value
|
||||
*/
|
||||
get(key, defaultValue = undefined) {
|
||||
return this.config[key] !== undefined ? this.config[key] : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set configuration value
|
||||
* @param {string} key - Configuration key
|
||||
* @param {*} value - Configuration value
|
||||
*/
|
||||
set(key, value) {
|
||||
this.config[key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all configuration
|
||||
* @param {boolean} includeSensitive - Include sensitive values
|
||||
* @returns {Object} Configuration object
|
||||
*/
|
||||
getAll(includeSensitive = false) {
|
||||
if (includeSensitive) {
|
||||
return { ...this.config };
|
||||
}
|
||||
|
||||
// Mask sensitive values
|
||||
const masked = { ...this.config };
|
||||
const sensitiveKeys = this.validationRules.authCredentials.filter((key) =>
|
||||
key.includes("PASSWORD")
|
||||
);
|
||||
|
||||
sensitiveKeys.forEach((key) => {
|
||||
if (masked[key]) {
|
||||
masked[key] = "[MASKED]";
|
||||
}
|
||||
});
|
||||
|
||||
return masked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if configuration is valid
|
||||
* @returns {boolean} True if configuration is valid
|
||||
*/
|
||||
isValid() {
|
||||
try {
|
||||
this._validateConfiguration();
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration summary
|
||||
* @returns {Object} Configuration summary
|
||||
*/
|
||||
getSummary() {
|
||||
const authTypesConfigured = [];
|
||||
const authTypes = [
|
||||
"ADMIN",
|
||||
"AGENT",
|
||||
"PATIENT",
|
||||
"PRACTITIONER",
|
||||
"AFFILIATE",
|
||||
"PARTNER",
|
||||
"NETWORK",
|
||||
"DOCTOR",
|
||||
"PROVIDER",
|
||||
];
|
||||
|
||||
authTypes.forEach((authType) => {
|
||||
const usernameKey = `${authType}_USERNAME`;
|
||||
const passwordKey = `${authType}_PASSWORD`;
|
||||
|
||||
if (this.config[usernameKey] && this.config[passwordKey]) {
|
||||
authTypesConfigured.push(authType.toLowerCase());
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
serverName: this.config.MCP_SERVER_NAME,
|
||||
serverVersion: this.config.MCP_SERVER_VERSION,
|
||||
apiBaseUrl: this.config.LARAVEL_API_BASE_URL,
|
||||
authTypesConfigured,
|
||||
hipaaCompliance: this.config.HIPAA_COMPLIANCE_MODE,
|
||||
logLevel: this.config.LOG_LEVEL,
|
||||
environment: this.config.NODE_ENV,
|
||||
debugMode: this.config.DEBUG_MODE,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload configuration from environment
|
||||
*/
|
||||
reload() {
|
||||
logger.info("Reloading configuration...");
|
||||
this._loadConfiguration();
|
||||
this._validateConfiguration();
|
||||
logger.info("Configuration reloaded successfully");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user