import Joi from 'joi'; export interface RuntimeConfig { service: { name: string; }; http: { host: string; port: number; }; logging: { level: string; requestIdHeader: string; }; database: { url: string; }; redis: { url: string; }; activeCalls: { mode: 'http' | 'ssh'; httpUrl: string; sshHost: string; sshConfig?: string; remoteCommand: string; timeoutMs: number; }; auth: { accessTokenSecret: string; accessTokenTtlSeconds: number; refreshTokenTtlDays: number; lockMaxFailures: number; lockWindowSeconds: number; loginThrottleMaxFailures: number; loginThrottleWindowSeconds: number; cookieSecure: boolean; tokenIssuer: string; tokenAudience: string; }; } export const validationSchema = Joi.object({ NODE_ENV: Joi.string().valid('development', 'test', 'production').default('development'), LISGLOSIPS_SERVICE_NAME: Joi.string().default('api'), LISGLOSIPS_HOST: Joi.string().hostname().default('127.0.0.1'), LISGLOSIPS_PORT: Joi.number().port().default(3000), LISGLOSIPS_LOG_LEVEL: Joi.string().valid('trace', 'debug', 'info', 'warn', 'error', 'fatal', 'silent').default('info'), LISGLOSIPS_REQUEST_ID_HEADER: Joi.string().default('x-request-id'), DATABASE_URL: Joi.string().uri({ scheme: ['mysql'] }).required(), REDIS_URL: Joi.string().uri({ scheme: ['redis', 'rediss'] }).required(), ACTIVE_CALLS_MODE: Joi.string().valid('http', 'ssh').default('http'), ACTIVE_CALLS_HTTP_URL: Joi.string().uri({ scheme: ['http', 'https'] }).default('http://127.0.0.1:8888/mi'), ACTIVE_CALLS_SSH_HOST: Joi.string().allow('').default('lisglosips-a'), ACTIVE_CALLS_SSH_CONFIG: Joi.string().allow('').optional(), ACTIVE_CALLS_REMOTE_COMMAND: Joi.string().default('/usr/local/sbin/lisglosips-call-control'), ACTIVE_CALLS_TIMEOUT_MS: Joi.number().integer().min(1000).max(30000).default(5000), AUTH_ACCESS_TOKEN_SECRET: Joi.string().min(32).when('NODE_ENV', { is: 'production', then: Joi.required(), otherwise: Joi.string().default('dev-only-change-this-auth-secret-32-bytes-min') }), AUTH_ACCESS_TOKEN_TTL_SECONDS: Joi.number().integer().min(60).max(86_400).default(900), AUTH_REFRESH_TOKEN_TTL_DAYS: Joi.number().integer().min(1).max(30).default(7), AUTH_LOCK_MAX_FAILURES: Joi.number().integer().min(3).max(20).default(5), AUTH_LOCK_WINDOW_SECONDS: Joi.number().integer().min(60).max(86_400).default(900), AUTH_LOGIN_THROTTLE_MAX_FAILURES: Joi.number().integer().min(3).max(100).default(10), AUTH_LOGIN_THROTTLE_WINDOW_SECONDS: Joi.number().integer().min(60).max(86_400).default(300), AUTH_COOKIE_SECURE: Joi.any().default(true), AUTH_TOKEN_ISSUER: Joi.string().default('lisglosips-api'), AUTH_TOKEN_AUDIENCE: Joi.string().default('lisglosips-web') }); export function appConfig(): RuntimeConfig { const cookieSecureValue = (process.env.AUTH_COOKIE_SECURE ?? 'true').trim().replace(/^['"]|['"]$/g, '').toLowerCase(); return { service: { name: process.env.LISGLOSIPS_SERVICE_NAME ?? 'api' }, http: { host: process.env.LISGLOSIPS_HOST ?? '127.0.0.1', port: Number(process.env.LISGLOSIPS_PORT ?? 3000) }, logging: { level: process.env.LISGLOSIPS_LOG_LEVEL ?? 'info', requestIdHeader: process.env.LISGLOSIPS_REQUEST_ID_HEADER ?? 'x-request-id' }, database: { url: process.env.DATABASE_URL ?? '' }, redis: { url: process.env.REDIS_URL ?? '' }, activeCalls: { mode: process.env.ACTIVE_CALLS_MODE === 'ssh' ? 'ssh' : 'http', httpUrl: process.env.ACTIVE_CALLS_HTTP_URL ?? 'http://127.0.0.1:8888/mi', sshHost: process.env.ACTIVE_CALLS_SSH_HOST || 'lisglosips-a', sshConfig: process.env.ACTIVE_CALLS_SSH_CONFIG || undefined, remoteCommand: process.env.ACTIVE_CALLS_REMOTE_COMMAND ?? '/usr/local/sbin/lisglosips-call-control', timeoutMs: Number(process.env.ACTIVE_CALLS_TIMEOUT_MS ?? 5000) }, auth: { accessTokenSecret: process.env.AUTH_ACCESS_TOKEN_SECRET ?? 'dev-only-change-this-auth-secret-32-bytes-min', accessTokenTtlSeconds: Number(process.env.AUTH_ACCESS_TOKEN_TTL_SECONDS ?? 900), refreshTokenTtlDays: Number(process.env.AUTH_REFRESH_TOKEN_TTL_DAYS ?? 7), lockMaxFailures: Number(process.env.AUTH_LOCK_MAX_FAILURES ?? 5), lockWindowSeconds: Number(process.env.AUTH_LOCK_WINDOW_SECONDS ?? 900), loginThrottleMaxFailures: Number(process.env.AUTH_LOGIN_THROTTLE_MAX_FAILURES ?? 10), loginThrottleWindowSeconds: Number(process.env.AUTH_LOGIN_THROTTLE_WINDOW_SECONDS ?? 300), cookieSecure: cookieSecureValue !== 'false' && cookieSecureValue !== '0', tokenIssuer: process.env.AUTH_TOKEN_ISSUER ?? 'lisglosips-api', tokenAudience: process.env.AUTH_TOKEN_AUDIENCE ?? 'lisglosips-web' } }; }