169 lines
5.6 KiB
TypeScript
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,
|
|
};
|
|
}
|