Files
cryptowallet/apps/api/src/config/env.ts

169 lines
5.6 KiB
TypeScript

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<void> {
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,
};
}