import dotenv from 'dotenv'; import path from 'path'; import { vaultAppRoleLogin, fetchVaultKV2 } from './vault'; import { logger } from '../lib/logger'; dotenv.config({ path: path.resolve(__dirname, '../../../../.env') }); const p = process.env; export let env = { db: { host: p.DB_HOST || 'localhost', port: parseInt(p.DB_PORT || '5432'), user: p.DB_USER || 'postgres', password: p.DB_PASSWORD || 'postgres', name: p.DB_NAME || 'cryptowallet_v2', poolSize: parseInt(p.DATABASE_POOL_SIZE || '10'), maxOverflow: parseInt(p.DATABASE_MAX_OVERFLOW || '20'), poolTimeout: parseInt(p.DATABASE_POOL_TIMEOUT || '30'), poolRecycle: parseInt(p.DATABASE_POOL_RECYCLE || '3600'), echo: p.DATABASE_ECHO === 'true', }, jwt: { algorithm: p.JWT_ALGORITHM || 'RS256', issuer: p.JWT_ISSUER || 'auth-service', audience: p.JWT_AUDIENCE || 'bitforce', accessTtl: parseInt(p.JWT_ACCESS_TTL_SECONDS || '900'), refreshTtl: parseInt(p.JWT_REFRESH_TTL_SECONDS || '2592000'), }, vault: { addr: p.VAULT_ADDR || '', roleId: p.VAULT_ROLE_ID || '', secretId: p.VAULT_SECRET_ID || '', mount: p.VAULT_MOUNT_POINT || 'dev-secrets', secretPath: p.VAULT_SECRET_PATH || 'database', jwtKidPath: p.VAULT_JWT_KID_PATH || 'jwt/kid', jwtKidsPrefix: p.VAULT_JWT_KIDS_PREFIX || 'jwt/kids', csrfSecretPath: p.VAULT_CSRF_SECRET_PATH || 'cryptowallet/csrf', secretsRefreshMs: parseInt(p.VAULT_SECRETS_REFRESH_MS || '3600000', 10), }, csrf: { cookieSecure: p.CSRF_COOKIE_SECURE === 'true', cookieHttpOnly: p.CSRF_COOKIE_HTTPONLY !== 'false', cookieSameSite: p.CSRF_COOKIE_SAMESITE || 'Lax', cookiePath: p.CSRF_COOKIE_PATH || '/', cookieDomain: p.CSRF_COOKIE_DOMAIN || '', }, docs: { username: p.DOCS_USERNAME || 'admin', password: p.DOCS_PASSWORD || 'admin', }, redis: { host: p.REDIS_HOST || 'keydb', port: parseInt(p.REDIS_PORT || '6379'), password: p.REDIS_PASSWORD || 'keydb', db: parseInt(p.REDIS_DB || '0'), }, rabbit: { emailCodeQueue: p.RABBIT_EMAIL_CODE_QUEUE || 'email.verification_code', publishPersist: p.RABBIT_PUBLISH_PERSIST !== 'false', connectTimeout: parseInt(p.RABBIT_CONNECT_TIMEOUT || '5'), }, log: { level: p.LOG_LEVEL || 'INFO', format: p.LOG_FORMAT || 'JSON', }, cors: { origins: (p.CORS_ORIGINS || 'http://localhost:3000').split(','), allowCredentials: p.CORS_ALLOW_CREDENTIALS !== 'false', }, rateLimit: { requests: parseInt(p.RATE_LIMIT_REQUESTS || '60'), window: parseInt(p.RATE_LIMIT_WINDOW || '60'), }, port: parseInt(p.API_PORT || '3001'), frontendUrl: p.FRONTEND_URL || 'http://localhost:3000', relayApiKey: p.RELAY_API_KEY || null, tronApiKey: p.TRON_API_KEY || null, jupiterApiKey: p.JUPITER_API_KEY || null, jupiterReferralAccount: p.JUPITER_REFERRAL_ACCOUNT || null, jupiterFeeBps: parseInt(p.JUPITER_FEE_BPS || '70'), }; let vaultToken: string | null = null; export function getVaultToken(): string | null { return vaultToken; } export async function initEnv(): Promise { const { addr, roleId, secretId, mount, secretPath } = env.vault; if (!addr || !roleId || !secretId) { logger.info('Vault not configured, using .env'); return; } const token = await vaultAppRoleLogin(addr, roleId, secretId); if (!token) { logger.warn('Vault AppRole login failed, using .env fallback'); return; } vaultToken = token; logger.info('Vault AppRole login successful'); const secrets = await fetchVaultKV2(addr, token, mount, secretPath); if (!secrets) { logger.warn('Failed to read DB secrets from Vault'); return; } logger.info('Loaded DB secrets from Vault'); const maybeCsrf = secrets.CSRF_SECRET_KEY || secrets.key; if (maybeCsrf && maybeCsrf.length >= 32) { const mod = await import('../services/csrf.service'); mod.setCsrfSigningKey(maybeCsrf); logger.info('CSRF signing key loaded from Vault (primary secret)'); } const s = (key: string) => secrets[key]; const si = (key: string, fallback: number) => { const v = secrets[key]; return v ? parseInt(v) : fallback; }; env = { ...env, db: { host: s('DB_HOST') || env.db.host, port: si('DB_PORT', env.db.port), user: s('DB_USER') || env.db.user, password: s('DB_PASSWORD') || env.db.password, name: s('DB_NAME') || env.db.name, poolSize: si('DATABASE_POOL_SIZE', env.db.poolSize), maxOverflow: si('DATABASE_MAX_OVERFLOW', env.db.maxOverflow), poolTimeout: si('DATABASE_POOL_TIMEOUT', env.db.poolTimeout), poolRecycle: si('DATABASE_POOL_RECYCLE', env.db.poolRecycle), echo: secrets['DATABASE_ECHO'] === 'true', }, jwt: { ...env.jwt, issuer: s('JWT_ISSUER') || env.jwt.issuer, audience: s('JWT_AUDIENCE') || env.jwt.audience, accessTtl: si('JWT_ACCESS_TTL_SECONDS', env.jwt.accessTtl), refreshTtl: si('JWT_REFRESH_TTL_SECONDS', env.jwt.refreshTtl), }, redis: { host: s('REDIS_HOST') || env.redis.host, port: si('REDIS_PORT', env.redis.port), password: s('REDIS_PASSWORD') || env.redis.password, db: si('REDIS_DB', env.redis.db), }, cors: { origins: s('CORS_ORIGINS') ? s('CORS_ORIGINS')!.split(',') : env.cors.origins, allowCredentials: secrets['CORS_ALLOW_CREDENTIALS'] !== 'false', }, rateLimit: { requests: si('RATE_LIMIT_REQUESTS', env.rateLimit.requests), window: si('RATE_LIMIT_WINDOW', env.rateLimit.window), }, relayApiKey: s('RELAY_API_KEY') || env.relayApiKey, tronApiKey: s('TRON_API_KEY') || env.tronApiKey, jupiterApiKey: s('JUPITER_API_KEY') || env.jupiterApiKey, }; }