The middleware package uses a comprehensive configuration system designed to provide flexibility, security, and maintainability across different deployment environments. Configuration management follows the 12-factor app principles, ensuring that configuration is stored in the environment rather than code.
Our configuration system adheres to the following 12-factor app principles:
- One codebase, many deployments: Same code runs in development, staging, and production
- Explicitly declare and isolate dependencies: All dependencies declared in package.json
- Store config in the environment: All configuration comes from environment variables
- Treat backing services as attached resources: Database, Redis, and external services configured via URLs
- Strict separation of config and code: No hardcoded configuration values
- Execute the app as one or more stateless processes: Configuration makes processes stateless
- Export services via port binding: Port configuration via environment
- Scale out via the process model: Configuration supports horizontal scaling
- Maximize robustness with fast startup and graceful shutdown: Health check configuration
- Keep development, staging, and production as similar as possible: Consistent config structure
- Treat logs as event streams: Log level and format configuration
- Admin processes should run as one-off processes: Configuration supports admin tools
Configuration is loaded in the following order of precedence (highest to lowest):
- Environment Variables - Runtime environment variables
- .env Files - Local environment files (development only)
- Default Values - Built-in safe defaults
// Configuration loading order
const config = {
// 1. Environment variables (highest priority)
jwtSecret: process.env.JWT_SECRET,
// 2. .env file values
jwtExpiration: process.env.JWT_EXPIRATION || '1h',
// 3. Default values (lowest priority)
rateLimitMax: parseInt(process.env.RATE_LIMIT_MAX || '100'),
};- Type: String
- Required: Yes
- Description: Secret key used for signing and verifying JWT tokens
- Example:
"your-super-secret-jwt-key-minimum-32-characters-long" - Security: Never commit to Git, use different secrets per environment
- Validation: Must be at least 32 characters long
# Generate a secure JWT secret
JWT_SECRET=$(openssl rand -base64 32)- Type: String
- Required: No
- Default:
"1h" - Description: Token expiration time for access tokens
- Format: Zeit/ms format (e.g., "2h", "7d", "10m", "30s")
- Examples:
"15m"- 15 minutes"2h"- 2 hours"7d"- 7 days"30d"- 30 days
- Type: String
- Required: No
- Default:
"7d" - Description: Expiration time for refresh tokens
- Format: Zeit/ms format
- Security: Should be longer than access token expiration
- Type: String
- Required: No
- Default:
"mindblock-api" - Description: JWT token issuer claim
- Validation: Must match between services in distributed systems
- Type: String
- Required: No
- Default:
"mindblock-users" - Description: JWT token audience claim
- Security: Restricts token usage to specific audiences
- Type: Number (milliseconds)
- Required: No
- Default:
900000(15 minutes) - Description: Time window for rate limiting in milliseconds
- Examples:
60000- 1 minute300000- 5 minutes900000- 15 minutes3600000- 1 hour
- Type: Number
- Required: No
- Default:
100 - Description: Maximum number of requests per window per IP/user
- Examples:
10- Very restrictive (admin endpoints)100- Standard API endpoints1000- Permissive (public endpoints)
- Type: String
- Required: No
- Description: Redis connection URL for distributed rate limiting
- Format: Redis connection string
- Example:
"redis://localhost:6379" - Note: If not provided, rate limiting falls back to in-memory storage
- Type: Boolean
- Required: No
- Default:
false - Description: Whether to count successful requests against rate limit
- Values:
true,false
- Type: String
- Required: No
- Default:
"ip" - Description: Strategy for generating rate limit keys
- Values:
"ip","user","ip+path","user+path"
- Type: String (comma-separated)
- Required: No
- Default:
"*" - Description: Allowed origins for cross-origin requests
- Examples:
"*"- Allow all origins (development only)"https://mindblock.app"- Single origin"https://mindblock.app,https://admin.mindblock.app"- Multiple origins"false"- Disable CORS
- Type: Boolean
- Required: No
- Default:
true - Description: Allow credentials (cookies, authorization headers) in CORS requests
- Values:
true,false
- Type: String (comma-separated)
- Required: No
- Default:
"GET,POST,PUT,DELETE,OPTIONS" - Description: HTTP methods allowed for CORS requests
- Type: String (comma-separated)
- Required: No
- Default:
"Content-Type,Authorization" - Description: HTTP headers allowed in CORS requests
- Type: Number (seconds)
- Required: No
- Default:
86400(24 hours) - Description: How long results of a preflight request can be cached
- Type: Number (seconds)
- Required: No
- Default:
31536000(1 year) - Description: HTTP Strict Transport Security max-age value
- Security: Set to 0 to disable HSTS in development
- Type: Boolean
- Required: No
- Default:
true - Description: Whether to include subdomains in HSTS policy
- Type: Boolean
- Required: No
- Default:
false - Description: Whether to include preload directive in HSTS policy
- Type: String
- Required: No
- Default:
"default-src 'self'" - Description: Content Security Policy directives
- Examples:
"default-src 'self'; script-src 'self' 'unsafe-inline'""default-src 'self'; img-src 'self' data: https:"
- Type: Boolean
- Required: No
- Default:
false - Description: Enable CSP report-only mode for testing
- Type: String
- Required: No
- Default:
"info" - Description: Minimum log level to output
- Values:
"debug","info","warn","error" - Hierarchy:
debugβinfoβwarnβerror
- Type: String
- Required: No
- Default:
"json" - Description: Log output format
- Values:
"json","pretty","simple"
- Type: String
- Required: No
- Description: Path to log file (if logging to file)
- Example:
"/var/log/mindblock/middleware.log"
- Type: String
- Required: No
- Default:
"10m" - Description: Maximum log file size before rotation
- Format: Human-readable size (e.g., "10m", "100M", "1G")
- Type: Number
- Required: No
- Default:
5 - Description: Maximum number of log files to keep
- Type: Boolean
- Required: No
- Default:
false - Description: Whether to log request bodies (security consideration)
- Type: Boolean
- Required: No
- Default:
false - Description: Whether to log response bodies (security consideration)
- Type: Boolean
- Required: No
- Default:
true - Description: Enable response compression
- Values:
true,false
- Type: Number
- Required: No
- Default:
6 - Description: Compression level (1-9, where 9 is maximum compression)
- Trade-off: Higher compression = more CPU, less bandwidth
- Type: Number (bytes)
- Required: No
- Default:
1024 - Description: Minimum response size to compress
- Example:
1024(1KB)
- Type: String (comma-separated)
- Required: No
- Default:
"text/html,text/css,text/javascript,application/json" - Description: MIME types to compress
- Type: Number (milliseconds)
- Required: No
- Default:
30000(30 seconds) - Description: Default request timeout
- Examples:
5000- 5 seconds (fast APIs)30000- 30 seconds (standard)120000- 2 minutes (slow operations)
- Type: Number (milliseconds)
- Required: No
- Default:
5000(5 seconds) - Description: Keep-alive timeout for HTTP connections
- Type: Number (milliseconds)
- Required: No
- Default:
60000(1 minute) - Description: Timeout for receiving headers
- Type: Boolean
- Required: No
- Default:
true - Description: Enable metrics collection
- Values:
true,false
- Type: Number
- Required: No
- Default:
9090 - Description: Port for metrics endpoint
- Note: Must be different from main application port
- Type: String
- Required: No
- Default:
"/metrics" - Description: Path for metrics endpoint
- Type: String
- Required: No
- Default:
"mindblock_middleware_" - Description: Prefix for all metric names
- Type: Boolean
- Required: No
- Default:
false - Description: Enable distributed tracing
- Values:
true,false
- Type: String
- Required: No
- Description: Jaeger collector endpoint
- Example:
"http://localhost:14268/api/traces"
- Type: String
- Required: No
- Description: Zipkin collector endpoint
- Example:
"http://localhost:9411/api/v2/spans"
- Type: Boolean
- Required: No
- Default:
true - Description: Enable strict validation mode
- Values:
true,false
- Type: Boolean
- Required: No
- Default:
true - Description: Strip non-whitelisted properties from input
- Values:
true,false
- Type: Boolean
- Required: No
- Default:
true - Description: Transform input to match expected types
- Values:
true,false
- Type: Boolean
- Required: No
- Default:
true - Description: Reject requests with non-whitelisted properties
- Values:
true,false
- Type: String
- Required: No
- Default:
"10mb" - Description: Maximum request body size
- Format: Human-readable size (e.g., "1mb", "100kb")
- Type: Number
- Required: No
- Default:
2048 - Description: Maximum URL length in characters
# Development environment configuration
NODE_ENV=development
# JWT Configuration (less secure for development)
JWT_SECRET=dev-secret-key-for-development-only-not-secure
JWT_EXPIRATION=24h
JWT_REFRESH_EXPIRATION=7d
# Rate Limiting (relaxed for development)
RATE_LIMIT_WINDOW=60000
RATE_LIMIT_MAX_REQUESTS=1000
RATE_LIMIT_SKIP_SUCCESSFUL_REQUESTS=false
# CORS (permissive for development)
CORS_ORIGIN=*
CORS_CREDENTIALS=true
# Security Headers (relaxed for development)
HSTS_MAX_AGE=0
CSP_DIRECTIVES=default-src 'self' 'unsafe-inline' 'unsafe-eval'
# Logging (verbose for development)
LOG_LEVEL=debug
LOG_FORMAT=pretty
LOG_REQUEST_BODY=true
LOG_RESPONSE_BODY=true
# Performance (optimized for development)
COMPRESSION_ENABLED=false
REQUEST_TIMEOUT=60000
# Monitoring (enabled for development)
ENABLE_METRICS=true
METRICS_PORT=9090
# Validation (relaxed for development)
VALIDATION_STRICT=false
# Database (local development)
DATABASE_URL=postgresql://localhost:5432/mindblock_dev
REDIS_URL=redis://localhost:6379
# External Services (local development)
EXTERNAL_API_BASE_URL=http://localhost:3001# Staging environment configuration
NODE_ENV=staging
# JWT Configuration (secure)
JWT_SECRET=staging-super-secret-jwt-key-32-chars-minimum
JWT_EXPIRATION=2h
JWT_REFRESH_EXPIRATION=7d
JWT_ISSUER=staging-mindblock-api
JWT_AUDIENCE=staging-mindblock-users
# Rate Limiting (moderate restrictions)
RATE_LIMIT_WINDOW=300000
RATE_LIMIT_MAX_REQUESTS=200
RATE_LIMIT_REDIS_URL=redis://staging-redis:6379
RATE_LIMIT_SKIP_SUCCESSFUL_REQUESTS=false
# CORS (staging domains)
CORS_ORIGIN=https://staging.mindblock.app,https://admin-staging.mindblock.app
CORS_CREDENTIALS=true
# Security Headers (standard security)
HSTS_MAX_AGE=31536000
HSTS_INCLUDE_SUBDOMAINS=true
CSP_DIRECTIVES=default-src 'self'; script-src 'self' 'unsafe-inline'
# Logging (standard logging)
LOG_LEVEL=info
LOG_FORMAT=json
LOG_REQUEST_BODY=false
LOG_RESPONSE_BODY=false
# Performance (production-like)
COMPRESSION_ENABLED=true
COMPRESSION_LEVEL=6
REQUEST_TIMEOUT=30000
# Monitoring (full monitoring)
ENABLE_METRICS=true
ENABLE_TRACING=true
JAEGER_ENDPOINT=http://jaeger-staging:14268/api/traces
# Validation (standard validation)
VALIDATION_STRICT=true
MAX_REQUEST_SIZE=5mb
# Database (staging)
DATABASE_URL=postgresql://staging-db:5432/mindblock_staging
REDIS_URL=redis://staging-redis:6379
# External Services (staging)
EXTERNAL_API_BASE_URL=https://api-staging.mindblock.app# Production environment configuration
NODE_ENV=production
# JWT Configuration (maximum security)
JWT_SECRET=production-super-secret-jwt-key-64-chars-minimum-length
JWT_EXPIRATION=1h
JWT_REFRESH_EXPIRATION=7d
JWT_ISSUER=production-mindblock-api
JWT_AUDIENCE=production-mindblock-users
# Rate Limiting (strict restrictions)
RATE_LIMIT_WINDOW=900000
RATE_LIMIT_MAX_REQUESTS=100
RATE_LIMIT_REDIS_URL=redis://prod-redis-cluster:6379
RATE_LIMIT_SKIP_SUCCESSFUL_REQUESTS=true
# CORS (production domains only)
CORS_ORIGIN=https://mindblock.app,https://admin.mindblock.app
CORS_CREDENTIALS=true
# Security Headers (maximum security)
HSTS_MAX_AGE=31536000
HSTS_INCLUDE_SUBDOMAINS=true
HSTS_PRELOAD=true
CSP_DIRECTIVES=default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
CSP_REPORT_ONLY=false
# Logging (error-only for production)
LOG_LEVEL=error
LOG_FORMAT=json
LOG_REQUEST_BODY=false
LOG_RESPONSE_BODY=false
LOG_FILE_PATH=/var/log/mindblock/middleware.log
LOG_MAX_FILE_SIZE=100M
LOG_MAX_FILES=10
# Performance (optimized for production)
COMPRESSION_ENABLED=true
COMPRESSION_LEVEL=9
COMPRESSION_THRESHOLD=512
REQUEST_TIMEOUT=15000
KEEP_ALIVE_TIMEOUT=5000
# Monitoring (full observability)
ENABLE_METRICS=true
ENABLE_TRACING=true
METRICS_PREFIX=mindblock_prod_middleware_
JAEGER_ENDPOINT=https://jaeger-production.internal/api/traces
# Validation (strict validation)
VALIDATION_STRICT=true
VALIDATION_FORBID_NON_WHITELISTED=true
MAX_REQUEST_SIZE=1mb
MAX_URL_LENGTH=1024
# Database (production)
DATABASE_URL=postgresql://prod-db-cluster:5432/mindblock_prod
REDIS_URL=redis://prod-redis-cluster:6379
# External Services (production)
EXTERNAL_API_BASE_URL=https://api.mindblock.app
EXTERNAL_API_TIMEOUT=5000// Configuration loading implementation
export class ConfigLoader {
static load(): MiddlewareConfig {
// 1. Load from environment variables
const envConfig = this.loadFromEnvironment();
// 2. Validate configuration
this.validate(envConfig);
// 3. Apply defaults
const config = this.applyDefaults(envConfig);
// 4. Transform/clean configuration
return this.transform(config);
}
private static loadFromEnvironment(): Partial<MiddlewareConfig> {
return {
// JWT Configuration
jwt: {
secret: process.env.JWT_SECRET,
expiration: process.env.JWT_EXPIRATION || '1h',
refreshExpiration: process.env.JWT_REFRESH_EXPIRATION || '7d',
issuer: process.env.JWT_ISSUER || 'mindblock-api',
audience: process.env.JWT_AUDIENCE || 'mindblock-users',
},
// Rate Limiting
rateLimit: {
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW || '900000'),
maxRequests: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100'),
redisUrl: process.env.RATE_LIMIT_REDIS_URL,
skipSuccessfulRequests: process.env.RATE_LIMIT_SKIP_SUCCESSFUL_REQUESTS === 'true',
},
// CORS
cors: {
origin: this.parseArray(process.env.CORS_ORIGIN || '*'),
credentials: process.env.CORS_CREDENTIALS !== 'false',
methods: this.parseArray(process.env.CORS_METHODS || 'GET,POST,PUT,DELETE,OPTIONS'),
allowedHeaders: this.parseArray(process.env.CORS_ALLOWED_HEADERS || 'Content-Type,Authorization'),
maxAge: parseInt(process.env.CORS_MAX_AGE || '86400'),
},
// Security Headers
security: {
hsts: {
maxAge: parseInt(process.env.HSTS_MAX_AGE || '31536000'),
includeSubdomains: process.env.HSTS_INCLUDE_SUBDOMAINS !== 'false',
preload: process.env.HSTS_PRELOAD === 'true',
},
csp: {
directives: process.env.CSP_DIRECTIVES || "default-src 'self'",
reportOnly: process.env.CSP_REPORT_ONLY === 'true',
},
},
// Logging
logging: {
level: (process.env.LOG_LEVEL as LogLevel) || 'info',
format: (process.env.LOG_FORMAT as LogFormat) || 'json',
filePath: process.env.LOG_FILE_PATH,
maxFileSize: process.env.LOG_MAX_FILE_SIZE || '10m',
maxFiles: parseInt(process.env.LOG_MAX_FILES || '5'),
logRequestBody: process.env.LOG_REQUEST_BODY === 'true',
logResponseBody: process.env.LOG_RESPONSE_BODY === 'true',
},
// Performance
performance: {
compression: {
enabled: process.env.COMPRESSION_ENABLED !== 'false',
level: parseInt(process.env.COMPRESSION_LEVEL || '6'),
threshold: parseInt(process.env.COMPRESSION_THRESHOLD || '1024'),
types: this.parseArray(process.env.COMPRESSION_TYPES || 'text/html,text/css,text/javascript,application/json'),
},
timeout: {
request: parseInt(process.env.REQUEST_TIMEOUT || '30000'),
keepAlive: parseInt(process.env.KEEP_ALIVE_TIMEOUT || '5000'),
headers: parseInt(process.env.HEADERS_TIMEOUT || '60000'),
},
},
// Monitoring
monitoring: {
metrics: {
enabled: process.env.ENABLE_METRICS !== 'false',
port: parseInt(process.env.METRICS_PORT || '9090'),
path: process.env.METRICS_PATH || '/metrics',
prefix: process.env.METRICS_PREFIX || 'mindblock_middleware_',
},
tracing: {
enabled: process.env.ENABLE_TRACING === 'true',
jaegerEndpoint: process.env.JAEGER_ENDPOINT,
zipkinEndpoint: process.env.ZIPKIN_ENDPOINT,
},
},
// Validation
validation: {
strict: process.env.VALIDATION_STRICT !== 'false',
whitelist: process.env.VALIDATION_WHITELIST !== 'false',
transform: process.env.VALIDATION_TRANSFORM !== 'false',
forbidNonWhitelisted: process.env.VALIDATION_FORBID_NON_WHITELISTED !== 'false',
maxRequestSize: process.env.MAX_REQUEST_SIZE || '10mb',
maxUrlLength: parseInt(process.env.MAX_URL_LENGTH || '2048'),
},
};
}
private static parseArray(value: string): string[] {
return value.split(',').map(item => item.trim()).filter(Boolean);
}
}// Configuration precedence example
export class ConfigManager {
private config: MiddlewareConfig;
constructor() {
this.config = this.loadConfiguration();
}
private loadConfiguration(): MiddlewareConfig {
// 1. Start with defaults (lowest priority)
let config = this.getDefaultConfig();
// 2. Load from .env files (medium priority)
config = this.mergeConfig(config, this.loadFromEnvFiles());
// 3. Load from environment variables (highest priority)
config = this.mergeConfig(config, this.loadFromEnvironment());
return config;
}
private mergeConfig(base: MiddlewareConfig, override: Partial<MiddlewareConfig>): MiddlewareConfig {
return {
jwt: { ...base.jwt, ...override.jwt },
rateLimit: { ...base.rateLimit, ...override.rateLimit },
cors: { ...base.cors, ...override.cors },
security: { ...base.security, ...override.security },
logging: { ...base.logging, ...override.logging },
performance: { ...base.performance, ...override.performance },
monitoring: { ...base.monitoring, ...override.monitoring },
validation: { ...base.validation, ...override.validation },
};
}
}// Configuration validation
export class ConfigValidator {
static validate(config: MiddlewareConfig): ValidationResult {
const errors: ValidationError[] = [];
// Validate JWT configuration
this.validateJwt(config.jwt, errors);
// Validate rate limiting
this.validateRateLimit(config.rateLimit, errors);
// Validate CORS
this.validateCors(config.cors, errors);
// Validate security headers
this.validateSecurity(config.security, errors);
// Validate logging
this.validateLogging(config.logging, errors);
// Validate performance
this.validatePerformance(config.performance, errors);
// Validate monitoring
this.validateMonitoring(config.monitoring, errors);
// Validate validation settings (meta!)
this.validateValidation(config.validation, errors);
return {
isValid: errors.length === 0,
errors,
};
}
private static validateJwt(jwt: JwtConfig, errors: ValidationError[]): void {
if (!jwt.secret) {
errors.push({
field: 'jwt.secret',
message: 'JWT_SECRET is required',
severity: 'error',
});
} else if (jwt.secret.length < 32) {
errors.push({
field: 'jwt.secret',
message: 'JWT_SECRET must be at least 32 characters long',
severity: 'error',
});
}
if (jwt.expiration && !this.isValidDuration(jwt.expiration)) {
errors.push({
field: 'jwt.expiration',
message: 'Invalid JWT_EXPIRATION format',
severity: 'error',
});
}
}
private static validateRateLimit(rateLimit: RateLimitConfig, errors: ValidationError[]): void {
if (rateLimit.windowMs < 1000) {
errors.push({
field: 'rateLimit.windowMs',
message: 'RATE_LIMIT_WINDOW must be at least 1000ms',
severity: 'error',
});
}
if (rateLimit.maxRequests < 1) {
errors.push({
field: 'rateLimit.maxRequests',
message: 'RATE_LIMIT_MAX_REQUESTS must be at least 1',
severity: 'error',
});
}
if (rateLimit.redisUrl && !this.isValidRedisUrl(rateLimit.redisUrl)) {
errors.push({
field: 'rateLimit.redisUrl',
message: 'Invalid RATE_LIMIT_REDIS_URL format',
severity: 'error',
});
}
}
private static isValidDuration(duration: string): boolean {
const durationRegex = /^\d+(ms|s|m|h|d|w)$/;
return durationRegex.test(duration);
}
private static isValidRedisUrl(url: string): boolean {
try {
new URL(url);
return url.startsWith('redis://') || url.startsWith('rediss://');
} catch {
return false;
}
}
}
// Validation result interface
interface ValidationResult {
isValid: boolean;
errors: ValidationError[];
}
interface ValidationError {
field: string;
message: string;
severity: 'warning' | 'error';
}// Required variable handling
export class RequiredConfigHandler {
static handleMissing(required: string[]): never {
const missing = required.filter(name => !process.env[name]);
if (missing.length > 0) {
console.error('β Missing required environment variables:');
missing.forEach(name => {
console.error(` - ${name}`);
});
console.error('\nPlease set these environment variables and restart the application.');
console.error('Refer to the documentation for required values and formats.\n');
process.exit(1);
}
}
static handleOptionalMissing(optional: string[]): void {
const missing = optional.filter(name => !process.env[name]);
if (missing.length > 0) {
console.warn('β οΈ Optional environment variables not set (using defaults):');
missing.forEach(name => {
const defaultValue = this.getDefaultValue(name);
console.warn(` - ${name} (default: ${defaultValue})`);
});
}
}
private static getDefaultValue(name: string): string {
const defaults: Record<string, string> = {
'JWT_EXPIRATION': '1h',
'RATE_LIMIT_WINDOW': '900000',
'RATE_LIMIT_MAX_REQUESTS': '100',
'LOG_LEVEL': 'info',
'COMPRESSION_ENABLED': 'true',
'ENABLE_METRICS': 'true',
};
return defaults[name] || 'not specified';
}
}| Variable | Default | Description | Category |
|---|---|---|---|
JWT_SECRET |
required | JWT signing secret | Auth |
JWT_EXPIRATION |
"1h" |
Access token expiration | Auth |
JWT_REFRESH_EXPIRATION |
"7d" |
Refresh token expiration | Auth |
JWT_ISSUER |
"mindblock-api" |
Token issuer | Auth |
JWT_AUDIENCE |
"mindblock-users" |
Token audience | Auth |
RATE_LIMIT_WINDOW |
900000 |
Rate limit window (15 min) | Security |
RATE_LIMIT_MAX_REQUESTS |
100 |
Max requests per window | Security |
RATE_LIMIT_REDIS_URL |
undefined |
Redis URL for distributed limiting | Security |
RATE_LIMIT_SKIP_SUCCESSFUL_REQUESTS |
false |
Skip successful requests | Security |
CORS_ORIGIN |
"*" |
Allowed origins | Security |
CORS_CREDENTIALS |
true |
Allow credentials | Security |
CORS_METHODS |
"GET,POST,PUT,DELETE,OPTIONS" |
Allowed methods | Security |
CORS_ALLOWED_HEADERS |
"Content-Type,Authorization" |
Allowed headers | Security |
CORS_MAX_AGE |
86400 |
Preflight cache duration | Security |
HSTS_MAX_AGE |
31536000 |
HSTS max age (1 year) | Security |
HSTS_INCLUDE_SUBDOMAINS |
true |
Include subdomains in HSTS | Security |
HSTS_PRELOAD |
false |
HSTS preload directive | Security |
CSP_DIRECTIVES |
"default-src 'self'" |
Content Security Policy | Security |
CSP_REPORT_ONLY |
false |
CSP report-only mode | Security |
LOG_LEVEL |
"info" |
Minimum log level | Monitoring |
LOG_FORMAT |
"json" |
Log output format | Monitoring |
LOG_FILE_PATH |
undefined |
Log file path | Monitoring |
LOG_MAX_FILE_SIZE |
"10m" |
Max log file size | Monitoring |
LOG_MAX_FILES |
5 |
Max log files to keep | Monitoring |
LOG_REQUEST_BODY |
false |
Log request bodies | Monitoring |
LOG_RESPONSE_BODY |
false |
Log response bodies | Monitoring |
COMPRESSION_ENABLED |
true |
Enable compression | Performance |
COMPRESSION_LEVEL |
6 |
Compression level (1-9) | Performance |
COMPRESSION_THRESHOLD |
1024 |
Min size to compress | Performance |
COMPRESSION_TYPES |
"text/html,text/css,text/javascript,application/json" |
Types to compress | Performance |
REQUEST_TIMEOUT |
30000 |
Request timeout (30s) | Performance |
KEEP_ALIVE_TIMEOUT |
5000 |
Keep-alive timeout | Performance |
HEADERS_TIMEOUT |
60000 |
Headers timeout | Performance |
ENABLE_METRICS |
true |
Enable metrics collection | Monitoring |
METRICS_PORT |
9090 |
Metrics endpoint port | Monitoring |
METRICS_PATH |
"/metrics" |
Metrics endpoint path | Monitoring |
METRICS_PREFIX |
"mindblock_middleware_" |
Metrics name prefix | Monitoring |
ENABLE_TRACING |
false |
Enable distributed tracing | Monitoring |
JAEGER_ENDPOINT |
undefined |
Jaeger collector endpoint | Monitoring |
ZIPKIN_ENDPOINT |
undefined |
Zipkin collector endpoint | Monitoring |
VALIDATION_STRICT |
true |
Strict validation mode | Validation |
VALIDATION_WHITELIST |
true |
Strip non-whitelisted props | Validation |
VALIDATION_TRANSFORM |
true |
Transform input types | Validation |
VALIDATION_FORBID_NON_WHITELISTED |
true |
Reject non-whitelisted | Validation |
MAX_REQUEST_SIZE |
"10mb" |
Max request body size | Validation |
MAX_URL_LENGTH |
2048 |
Max URL length | Validation |
# .gitignore - Always include these patterns
.env
.env.local
.env.development
.env.staging
.env.production
*.key
*.pem
*.p12
secrets/// Secure configuration loading
export class SecureConfigLoader {
static load(): SecureConfig {
// Never log secrets
const config = {
jwtSecret: process.env.JWT_SECRET, // Don't log this
databaseUrl: process.env.DATABASE_URL, // Don't log this
};
// Validate without exposing values
if (!config.jwtSecret || config.jwtSecret.length < 32) {
throw new Error('JWT_SECRET must be at least 32 characters');
}
return config;
}
}// AWS Secrets Manager integration
export class AWSSecretsManager {
static async loadSecret(secretName: string): Promise<string> {
const client = new SecretsManagerClient();
try {
const response = await client.send(new GetSecretValueCommand({
SecretId: secretName,
}));
return response.SecretString as string;
} catch (error) {
console.error(`Failed to load secret ${secretName}:`, error);
throw error;
}
}
static async loadAllSecrets(): Promise<Record<string, string>> {
const secrets = {
JWT_SECRET: await this.loadSecret('mindblock/jwt-secret'),
DATABASE_URL: await this.loadSecret('mindblock/database-url'),
REDIS_URL: await this.loadSecret('mindblock/redis-url'),
};
return secrets;
}
}// Vault integration
export class VaultSecretLoader {
static async loadSecret(path: string): Promise<any> {
const vault = new Vault({
endpoint: process.env.VAULT_ENDPOINT,
token: process.env.VAULT_TOKEN,
});
try {
const result = await vault.read(path);
return result.data;
} catch (error) {
console.error(`Failed to load secret from Vault: ${path}`, error);
throw error;
}
}
}// Secret rotation monitoring
export class SecretRotationMonitor {
static checkSecretAge(secretName: string, maxAge: number): void {
const createdAt = process.env[`${secretName}_CREATED_AT`];
if (createdAt) {
const age = Date.now() - parseInt(createdAt);
if (age > maxAge) {
console.warn(`β οΈ Secret ${secretName} is ${Math.round(age / (24 * 60 * 60 * 1000))} days old. Consider rotation.`);
}
}
}
static monitorAllSecrets(): void {
this.checkSecretAge('JWT_SECRET', 90 * 24 * 60 * 60 * 1000); // 90 days
this.checkSecretAge('DATABASE_PASSWORD', 30 * 24 * 60 * 60 * 1000); // 30 days
this.checkSecretAge('API_KEY', 60 * 24 * 60 * 60 * 1000); // 60 days
}
}# Environment-specific secret naming convention
# Development
JWT_SECRET_DEV=dev-secret-1
DATABASE_URL_DEV=postgresql://localhost:5432/mindblock_dev
# Staging
JWT_SECRET_STAGING=staging-secret-1
DATABASE_URL_STAGING=postgresql://staging-db:5432/mindblock_staging
# Production
JWT_SECRET_PROD=prod-secret-1
DATABASE_URL_PROD=postgresql://prod-db:5432/mindblock_prod// Environment-specific secret loading
export class EnvironmentSecretLoader {
static loadSecret(baseName: string): string {
const env = process.env.NODE_ENV || 'development';
const envSpecificName = `${baseName}_${env.toUpperCase()}`;
return process.env[envSpecificName] || process.env[baseName];
}
static loadAllSecrets(): Record<string, string> {
return {
jwtSecret: this.loadSecret('JWT_SECRET'),
databaseUrl: this.loadSecret('DATABASE_URL'),
redisUrl: this.loadSecret('REDIS_URL'),
};
}
}// Secret strength validation
export class SecretStrengthValidator {
static validateJwtSecret(secret: string): ValidationResult {
const errors: string[] = [];
if (secret.length < 32) {
errors.push('JWT_SECRET must be at least 32 characters long');
}
if (secret.length < 64) {
errors.push('JWT_SECRET should be at least 64 characters for production');
}
if (!this.hasEnoughEntropy(secret)) {
errors.push('JWT_SECRET should contain a mix of letters, numbers, and symbols');
}
return {
isValid: errors.length === 0,
errors,
};
}
static hasEnoughEntropy(secret: string): boolean {
const hasLetters = /[a-zA-Z]/.test(secret);
const hasNumbers = /\d/.test(secret);
const hasSymbols = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(secret);
return (hasLetters && hasNumbers && hasSymbols) || secret.length >= 128;
}
}# Generate secure secrets using different methods
# OpenSSL (recommended)
JWT_SECRET=$(openssl rand -base64 32)
JWT_SECRET_LONG=$(openssl rand -base64 64)
# Node.js crypto
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
# Python secrets
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
# UUID (less secure, but better than nothing)
JWT_SECRET=$(uuidgen | tr -d '-')// Programmatic secret generation
export class SecretGenerator {
static generateSecureSecret(length: number = 64): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?';
const randomBytes = require('crypto').randomBytes(length);
return Array.from(randomBytes)
.map(byte => chars[byte % chars.length])
.join('');
}
static generateJwtSecret(): string {
return this.generateSecureSecret(64);
}
static generateApiKey(): string {
return `mk_${this.generateSecureSecret(32)}`;
}
}# Relaxed rate limiting
RATE_LIMIT_WINDOW=900000
RATE_LIMIT_MAX_REQUESTS=1000
RATE_LIMIT_SKIP_SUCCESSFUL_REQUESTS=false# Standard rate limiting
RATE_LIMIT_WINDOW=300000
RATE_LIMIT_MAX_REQUESTS=500
RATE_LIMIT_SKIP_SUCCESSFUL_REQUESTS=true
RATE_LIMIT_REDIS_URL=redis://localhost:6379# Strict rate limiting with Redis
RATE_LIMIT_WINDOW=60000
RATE_LIMIT_MAX_REQUESTS=100
RATE_LIMIT_SKIP_SUCCESSFUL_REQUESTS=true
RATE_LIMIT_REDIS_URL=redis://redis-cluster:6379# Very strict rate limiting
RATE_LIMIT_WINDOW=10000
RATE_LIMIT_MAX_REQUESTS=10
RATE_LIMIT_REDIS_URL=redis://edge-redis:6379# Minimal compression
COMPRESSION_ENABLED=true
COMPRESSION_LEVEL=1
COMPRESSION_THRESHOLD=2048
COMPRESSION_TYPES=text/html,text/css# Balanced compression
COMPRESSION_ENABLED=true
COMPRESSION_LEVEL=6
COMPRESSION_THRESHOLD=1024
COMPRESSION_TYPES=text/html,text/css,text/javascript,application/json# Maximum compression
COMPRESSION_ENABLED=true
COMPRESSION_LEVEL=9
COMPRESSION_THRESHOLD=512
COMPRESSION_TYPES=text/html,text/css,text/javascript,application/json,application/xmlREQUEST_TIMEOUT=5000
KEEP_ALIVE_TIMEOUT=2000
HEADERS_TIMEOUT=10000REQUEST_TIMEOUT=15000
KEEP_ALIVE_TIMEOUT=5000
HEADERS_TIMEOUT=30000REQUEST_TIMEOUT=60000
KEEP_ALIVE_TIMEOUT=10000
HEADERS_TIMEOUT=60000REQUEST_TIMEOUT=300000
KEEP_ALIVE_TIMEOUT=15000
HEADERS_TIMEOUT=120000
MAX_REQUEST_SIZE=100mb# Long cache for static assets
CACHE_TTL_STATIC=86400000 # 24 hours
CACHE_TTL_IMAGES=31536000000 # 1 year# Short cache for dynamic content
CACHE_TTL_API=300000 # 5 minutes
CACHE_TTL_USER_DATA=60000 # 1 minute
CACHE_TTL_PUBLIC_DATA=1800000 # 30 minutes# Rate limit cache duration
RATE_LIMIT_CACHE_TTL=900000 # 15 minutes
RATE_LIMIT_CLEANUP_INTERVAL=300000 # 5 minutesREDIS_POOL_MIN=2
REDIS_POOL_MAX=10
REDIS_POOL_ACQUIRE_TIMEOUT=30000REDIS_POOL_MIN=5
REDIS_POOL_MAX=20
REDIS_POOL_ACQUIRE_TIMEOUT=15000REDIS_POOL_MIN=10
REDIS_POOL_MAX=50
REDIS_POOL_ACQUIRE_TIMEOUT=10000# Very permissive for development
RATE_LIMIT_WINDOW=60000
RATE_LIMIT_MAX_REQUESTS=10000
RATE_LIMIT_SKIP_SUCCESSFUL_REQUESTS=false
# No Redis required for development
# RATE_LIMIT_REDIS_URL not set# Debug logging with full details
LOG_LEVEL=debug
LOG_FORMAT=pretty
LOG_REQUEST_BODY=true
LOG_RESPONSE_BODY=true
# Console output (no file logging)
# LOG_FILE_PATH not set# Relaxed security for testing
HSTS_MAX_AGE=0
CSP_DIRECTIVES=default-src 'self' 'unsafe-inline' 'unsafe-eval'
CORS_ORIGIN=*
# Compression disabled for easier debugging
COMPRESSION_ENABLED=false# Local development services
DATABASE_URL=postgresql://localhost:5432/mindblock_dev
REDIS_URL=redis://localhost:6379
EXTERNAL_API_BASE_URL=http://localhost:3001# Production-like but more permissive
RATE_LIMIT_WINDOW=300000
RATE_LIMIT_MAX_REQUESTS=500
RATE_LIMIT_REDIS_URL=redis://staging-redis:6379
RATE_LIMIT_SKIP_SUCCESSFUL_REQUESTS=true# Production-like logging
LOG_LEVEL=info
LOG_FORMAT=json
LOG_REQUEST_BODY=false
LOG_RESPONSE_BODY=false
# File logging enabled
LOG_FILE_PATH=/var/log/mindblock/staging.log
LOG_MAX_FILE_SIZE=50M
LOG_MAX_FILES=5# Standard security settings
HSTS_MAX_AGE=86400 # 1 day instead of 1 year
HSTS_PRELOAD=false
CSP_DIRECTIVES=default-src 'self'; script-src 'self' 'unsafe-inline'
CSP_REPORT_ONLY=true
# Compression enabled
COMPRESSION_ENABLED=true
COMPRESSION_LEVEL=6# Staging environment services
DATABASE_URL=postgresql://staging-db:5432/mindblock_staging
REDIS_URL=redis://staging-redis:6379
EXTERNAL_API_BASE_URL=https://api-staging.mindblock.app# Production rate limiting
RATE_LIMIT_WINDOW=900000
RATE_LIMIT_MAX_REQUESTS=100
RATE_LIMIT_REDIS_URL=redis://prod-redis-cluster:6379
RATE_LIMIT_SKIP_SUCCESSFUL_REQUESTS=true# Minimal logging for production
LOG_LEVEL=error
LOG_FORMAT=json
LOG_REQUEST_BODY=false
LOG_RESPONSE_BODY=false
# File logging with rotation
LOG_FILE_PATH=/var/log/mindblock/production.log
LOG_MAX_FILE_SIZE=100M
LOG_MAX_FILES=10# Maximum security
HSTS_MAX_AGE=31536000
HSTS_INCLUDE_SUBDOMAINS=true
HSTS_PRELOAD=true
CSP_DIRECTIVES=default-src 'self'; script-src 'self'; object-src 'none'
CSP_REPORT_ONLY=false
# Maximum compression
COMPRESSION_ENABLED=true
COMPRESSION_LEVEL=9# Production services with failover
DATABASE_URL=postgresql://prod-db-cluster:5432/mindblock_prod
DATABASE_URL_FAILOVER=postgresql://prod-db-backup:5432/mindblock_prod
REDIS_URL=redis://prod-redis-cluster:6379
EXTERNAL_API_BASE_URL=https://api.mindblock.app# Optimized timeouts
REQUEST_TIMEOUT=15000
KEEP_ALIVE_TIMEOUT=5000
HEADERS_TIMEOUT=30000
# Connection pooling
REDIS_POOL_MIN=10
REDIS_POOL_MAX=50
REDIS_POOL_ACQUIRE_TIMEOUT=10000
# Monitoring enabled
ENABLE_METRICS=true
ENABLE_TRACING=true
METRICS_PREFIX=prod_mindblock_Symptoms:
- 401 Unauthorized responses
- "Invalid token" errors
- Authentication failures
Causes:
- JWT_SECRET not set or incorrect
- JWT_SECRET differs between services
- Token expired
Solutions:
# Check JWT_SECRET is set
echo $JWT_SECRET
# Verify JWT_SECRET length (should be >= 32 chars)
echo $JWT_SECRET | wc -c
# Check token expiration
JWT_EXPIRATION=2h # Increase for testing
# Verify JWT_SECRET matches between services
# Ensure all services use the same JWT_SECRETSymptoms:
- No rate limiting effect
- All requests allowed
- Rate limit headers not present
Causes:
- RATE_LIMIT_REDIS_URL not configured for distributed setup
- Redis connection failed
- Rate limiting middleware not applied correctly
Solutions:
# Check Redis configuration
echo $RATE_LIMIT_REDIS_URL
# Test Redis connection
redis-cli -u $RATE_LIMIT_REDIS_URL ping
# Verify Redis is running
docker ps | grep redis
# Check rate limit values
echo "Window: $RATE_LIMIT_WINDOW ms"
echo "Max requests: $RATE_LIMIT_MAX_REQUESTS"
# For single instance, remove Redis URL
unset RATE_LIMIT_REDIS_URLSymptoms:
- Browser CORS errors
- "No 'Access-Control-Allow-Origin' header"
- Preflight request failures
Causes:
- CORS_ORIGIN doesn't include frontend URL
- Credentials mismatch
- Preflight methods not allowed
Solutions:
# Check CORS origin
echo $CORS_ORIGIN
# Add your frontend URL
CORS_ORIGIN=https://your-frontend-domain.com
# For multiple origins
CORS_ORIGIN=https://domain1.com,https://domain2.com
# Check credentials setting
echo $CORS_CREDENTIALS # Should be 'true' if using cookies/auth
# Check allowed methods
echo $CORS_METHODS # Should include your HTTP methodsSymptoms:
- Missing security headers in responses
- Security scanner warnings
- HSTS not applied
Causes:
- Security middleware not applied
- Configuration values set to disable features
- Headers being overridden by other middleware
Solutions:
# Check security header configuration
echo $HSTS_MAX_AGE
echo $CSP_DIRECTIVES
# Ensure HSTS is enabled (not 0)
HSTS_MAX_AGE=31536000
# Check CSP is not empty
CSP_DIRECTIVES=default-src 'self'
# Verify middleware is applied in correct order
# Security middleware should be applied before other middlewareSymptoms:
- Default values being used
- Environment variables ignored
- Configuration validation errors
Causes:
- .env file not in correct location
- Environment variables not exported
- Configuration loading order issues
Solutions:
# Check .env file location
ls -la .env*
# Verify .env file is being loaded
cat .env
# Export environment variables manually (for testing)
export JWT_SECRET="test-secret-32-chars-long"
export LOG_LEVEL="debug"
# Restart application after changing .env
npm run restart# Error: JWT_SECRET must be at least 32 characters long
# Solution: Generate a proper secret
JWT_SECRET=$(openssl rand -base64 32)
export JWT_SECRET# Error: RATE_LIMIT_WINDOW must be at least 1000ms
# Solution: Use valid time window
RATE_LIMIT_WINDOW=900000 # 15 minutes
export RATE_LIMIT_WINDOW# Error: Invalid RATE_LIMIT_REDIS_URL format
# Solution: Use correct Redis URL format
RATE_LIMIT_REDIS_URL=redis://localhost:6379
# or
RATE_LIMIT_REDIS_URL=redis://user:pass@host:port/db
export RATE_LIMIT_REDIS_URL# Error: Invalid LOG_LEVEL
# Solution: Use valid log level
LOG_LEVEL=debug # or info, warn, error
export LOG_LEVEL# Check compression level
echo $COMPRESSION_LEVEL # Lower for better performance
# Check timeout values
echo $REQUEST_TIMEOUT # Lower for faster failure
# Check rate limit configuration
echo $RATE_LIMIT_MAX_REQUESTS # Higher if too restrictive# Check rate limit cache settings
RATE_LIMIT_CACHE_TTL=300000 # Lower TTL
RATE_LIMIT_CLEANUP_INTERVAL=60000 # More frequent cleanup
# Check log file size limits
LOG_MAX_FILE_SIZE=10M # Lower max file size
LOG_MAX_FILES=3 # Fewer files# Check database URL format
echo $DATABASE_URL
# Test database connection
psql $DATABASE_URL -c "SELECT 1"
# Check connection pool settings
echo $DB_POOL_MIN
echo $DB_POOL_MAX// Add to your application startup
if (process.env.NODE_ENV === 'development') {
console.log('π§ Configuration Debug:');
console.log('Environment:', process.env.NODE_ENV);
console.log('JWT Secret set:', !!process.env.JWT_SECRET);
console.log('Rate Limit Window:', process.env.RATE_LIMIT_WINDOW);
console.log('Log Level:', process.env.LOG_LEVEL);
console.log('CORS Origin:', process.env.CORS_ORIGIN);
}// Add comprehensive validation
import { ConfigValidator } from '@mindblock/middleware/config';
const validation = ConfigValidator.validate(config);
if (!validation.isValid) {
console.error('β Configuration validation failed:');
validation.errors.forEach(error => {
console.error(` ${error.field}: ${error.message}`);
});
process.exit(1);
} else {
console.log('β
Configuration validation passed');
}// Test middleware configuration individually
import { RateLimitingMiddleware } from '@mindblock/middleware/security';
try {
const rateLimit = new RateLimitingMiddleware(config.rateLimit);
console.log('β
Rate limiting middleware configured successfully');
} catch (error) {
console.error('β Rate limiting middleware configuration failed:', error.message);
}This comprehensive configuration documentation provides complete guidance for configuring the middleware package in any environment, with detailed troubleshooting information and best practices for security and performance.