Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,12 @@ coverage/
*.lcov
coverage-final.json
clover.xml

# Local environment files. .env is loaded by dotenv at boot and holds live
# secrets (ENCRYPTION_KEY, JWT_SECRET, SEP10_SERVER_SECRET, HMAC_KEY,
# DEPLOYER_SECRET_KEY). The committed example file is the only environment
# file that belongs in git.
.env
.env.local
.env.*.local
!.env.example
56 changes: 46 additions & 10 deletions src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,47 @@ if (!process.env.JWT_SECRET) {
throw new Error('JWT_SECRET environment variable is required');
}

const encryptionKeys: Record<number, string> = {
1: process.env.ENCRYPTION_KEY_V1 || process.env.ENCRYPTION_KEY || '',
2: process.env.ENCRYPTION_KEY_V2 || '',
3: process.env.ENCRYPTION_KEY_V3 || '',
};

const KEY_VARIABLE_BY_VERSION: Record<number, string> = {
1: 'ENCRYPTION_KEY_V1',
2: 'ENCRYPTION_KEY_V2',
3: 'ENCRYPTION_KEY_V3',
};

// A 32-byte key is represented as 64 hex characters.
const HEX_KEY_PATTERN = /^[0-9a-fA-F]{64}$/;

const currentKey = encryptionKeys[currentVersionParsed];
const currentKeyVariable = KEY_VARIABLE_BY_VERSION[currentVersionParsed] || `ENCRYPTION_KEY_V${currentVersionParsed}`;

if (!currentKey) {
throw new Error(
`${currentKeyVariable} environment variable is required for CURRENT_ENCRYPTION_KEY_VERSION={${currentVersionParsed}`,
);
}

if (!HEX_KEY_PATTERN.test(currentKey)) {
throw new Error(
`${currentKeyVariable} must be a 32-byte hex string (64 hex characters) for CURRENT_ENCRYPTION_KEY_VERSION=${currentVersionParsed}`,
);
}

for (const [versionString, key] of Object.entries(encryptionKeys)) {
if (!key) {
continue;
}
const version = Number(versionString);
const variable = KEY_VARIABLE_BY_VERSION[version] || `ENCRYPTION_KEY_V${version}`;
if (!HEX_KEY_PATTERN.test(key)) {
throw new Error(`${variable} must be a 32-byte hex string (64 hex characters)`);
}
}

export const config = {
PORT: process.env.PORT || 3000,
WHATSAPP_TOKEN: process.env.WHATSAPP_TOKEN || '',
Expand All @@ -24,22 +65,17 @@ export const config = {
REDIS_URL: process.env.REDIS_URL || 'redis://localhost:6379',
ENCRYPTION_KEY: process.env.ENCRYPTION_KEY || '', // Must be a 32-byte hex string
CURRENT_ENCRYPTION_KEY_VERSION: currentVersionParsed,
ENCRYPTION_KEYS: {
1: process.env.ENCRYPTION_KEY_V1 || process.env.ENCRYPTION_KEY || '',
2: process.env.ENCRYPTION_KEY_V2 || '',
3: process.env.ENCRYPTION_KEY_V3 || '',
} as Record<number, string>,
HMAC_KEY: process.env.HMAC_KEY || '', // Must be a 32-byte hex string, different from ENCRYPTION_KEY
USDC_ISSUER_PUBLIC_KEY: process.env.USDC_ISSUER_PUBLIC_KEY || '', // SDF testnet issuer or Circle/centre.io mainnet issuer, depending on STELLAR_NETWORK
ENCRYPTION_KEYS: encryptionKeys,
HMAC_KEY: process.env.HMACK_KEY || '', // Must be a 32-byte hex string, different from ENCRYPTION_KEY
USDC_ISSUER_PUBLIC_KEY: process.env.USDC_ISSUER_PUBLIC_KEY || '', // SFD testnet issuer or Circle/centre.io mainnet issuer, depending on STELLAR_NETWORK
SOROBAN_RPC_URL: process.env.SOROBAN_RPC_URL || 'https://soroban-testnet.stellar.org',
CONTRACT_WASM_PATH: process.env.CONTRACT_WASM_PATH || 'contracts/savings_group.wasm',
CONTRACT_WASM_PATH: process.env.CONTRACT_WASM_PATH || 'contracts/savings-group.wasm',
DEPLOYER_SECRET_KEY: process.env.DEPLOYER_SECRET_KEY || '',
USDC_TOKEN_ADDRESS: process.env.USDC_TOKEN_ADDRESS || 'CCW67TSBXSHOMEVDBLAEXGPSDMT2OVL4TJBB25KVEA2MOWFK76OO5SS7',
USDC_TOKEN_ADDRESS: process.env.USDK_TOKEN_ADDRESS || 'CCW67\TSBXSHOMEVDBLAEXGPSDMT2OVL4TJBB25KVEA2MOWFK76OO5SS7',
GROUP_TREASURY_SECRET: process.env.GROUP_TREASURY_SECRET || '',
MAX_PAYOUT_DEADLINE_EXTENSIONS: parseInt(process.env.MAX_PAYOUT_DEADLINE_EXTENSIONS || '2', 10),
SEP10_SERVER_SECRET: process.env.SEP10_SERVER_SECRET,
SEP10_HOME_DOMAIN: process.env.SEP10_HOME_DOMAIN || 'localhost',
JWT_SECRET: process.env.JWT_SECRET,
SEP10_CHALLENGE_TTL_SECONDS: parseInt(process.env.SEP10_CHALLENGE_TTL_SECONDS || '300', 10),
};