account/src/config-manager.ts
2025-01-18 17:52:58 +00:00

273 lines
No EOL
10 KiB
TypeScript

import fs from 'fs-extra';
import mongoose from 'mongoose';
import dotenv from 'dotenv';
import isValidHostname from 'is-valid-hostname';
import { LOG_INFO, LOG_WARN, LOG_ERROR, formatHostnames } from '@/logger';
import { type Config, domainServices, optionalDomainServices } from '@/types/common/config';
dotenv.config();
export const disabledFeatures = {
redis: false,
email: false,
captcha: false,
s3: false,
datastore: false
};
const hexadecimalStringRegex = /^[0-9a-f]+$/i;
LOG_INFO('Loading config');
let mongooseConnectOptions: mongoose.ConnectOptions = {};
if (process.env.PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH) {
mongooseConnectOptions = fs.readJSONSync(process.env.PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH);
}
if (process.env.PN_ACT_CONFIG_EMAIL_SECURE) {
if (process.env.PN_ACT_CONFIG_EMAIL_SECURE !== 'true' && process.env.PN_ACT_CONFIG_EMAIL_SECURE !== 'false') {
LOG_ERROR(`PN_ACT_CONFIG_EMAIL_SECURE must be either true or false, got ${process.env.PN_ACT_CONFIG_EMAIL_SECURE}`);
process.exit(0);
}
}
export const config: Config = {
http: {
port: Number(process.env.PN_ACT_CONFIG_HTTP_PORT || '')
},
mongoose: {
connection_string: process.env.PN_ACT_CONFIG_MONGO_CONNECTION_STRING || '',
options: mongooseConnectOptions
},
redis: {
client: {
url: process.env.PN_ACT_CONFIG_REDIS_URL || ''
}
},
email: {
ses: {
region: process.env.PN_ACT_CONFIG_EMAIL_SES_REGION || '',
key: process.env.PN_ACT_CONFIG_EMAIL_SES_ACCESS_KEY || '',
secret: process.env.PN_ACT_CONFIG_EMAIL_SES_SECRET_KEY || ''
},
from: process.env.PN_ACT_CONFIG_EMAIL_FROM || ''
},
s3: {
bucket: process.env.PN_ACT_CONFIG_S3_BUCKET || '',
endpoint: process.env.PN_ACT_CONFIG_S3_ENDPOINT || '',
key: process.env.PN_ACT_CONFIG_S3_ACCESS_KEY || '',
secret: process.env.PN_ACT_CONFIG_S3_ACCESS_SECRET || '',
region: process.env.PN_ACT_CONFIG_S3_REGION || '',
forcePathStyle: process.env.PN_ACT_CONFIG_S3_FORCE_PATH_STYLE === 'true'
},
hcaptcha: {
secret: process.env.PN_ACT_CONFIG_HCAPTCHA_SECRET || ''
},
cdn: {
subdomain: process.env.PN_ACT_CONFIG_CDN_SUBDOMAIN,
disk_path: process.env.PN_ACT_CONFIG_CDN_DISK_PATH || '',
base_url: process.env.PN_ACT_CONFIG_CDN_BASE_URL || ''
},
website_base: process.env.PN_ACT_CONFIG_WEBSITE_BASE || '',
aes_key: process.env.PN_ACT_CONFIG_AES_KEY || '',
grpc: {
master_api_keys: {
account: process.env.PN_ACT_CONFIG_GRPC_MASTER_API_KEY_ACCOUNT || '',
api: process.env.PN_ACT_CONFIG_GRPC_MASTER_API_KEY_API || '',
},
port: Number(process.env.PN_ACT_CONFIG_GRPC_PORT || ''),
},
server_environment: process.env.PN_ACT_CONFIG_SERVER_ENVIRONMENT || '',
datastore: {
signature_secret: process.env.PN_ACT_CONFIG_DATASTORE_SIGNATURE_SECRET || ''
},
domains: {
api: (process.env.PN_ACT_CONFIG_DOMAINS_API || 'api.pretendo.cc').split(','),
assets: (process.env.PN_ACT_CONFIG_DOMAINS_ASSETS || 'assets.pretendo.cc').split(','),
cbvc: (process.env.PN_ACT_CONFIG_DOMAINS_CBVC || 'cbvc.cdn.pretendo.cc').split(','),
conntest: (process.env.PN_ACT_CONFIG_DOMAINS_CONNTEST || 'conntest.pretendo.cc').split(','),
datastore: (process.env.PN_ACT_CONFIG_DOMAINS_DATASTORE || 'datastore.pretendo.cc').split(','),
local_cdn: (process.env.PN_ACT_CONFIG_DOMAINS_LOCAL_CDN || '').split(','),
nasc: (process.env.PN_ACT_CONFIG_DOMAINS_NASC || 'nasc.pretendo.cc').split(','),
nnas: (process.env.PN_ACT_CONFIG_DOMAINS_NNAS || 'c.account.pretendo.cc,account.pretendo.cc').split(','),
}
};
if (process.env.PN_ACT_CONFIG_STRIPE_SECRET_KEY) {
config.stripe = {
secret_key: process.env.PN_ACT_CONFIG_STRIPE_SECRET_KEY
};
}
// * Add the old config option for backwards compatibility
if (config.cdn.subdomain) {
config.domains.local_cdn.push(config.cdn.subdomain);
}
let configValid = true;
LOG_INFO('Config loaded, checking integrity');
for (const service of domainServices) {
const validDomains: string[] = [];
const invalidDomains: string[] = [];
const uniqueDomains = [...new Set(config.domains[service])];
for (const domain of uniqueDomains) {
isValidHostname(domain) ? validDomains.push(domain) : invalidDomains.push(domain);
}
if (validDomains.length === 0 && !optionalDomainServices.includes(service)) {
LOG_ERROR(`No valid domains found for ${service}. Set the PN_ACT_CONFIG_DOMAINS_${service.toUpperCase()} environment variable to a valid domain`);
configValid = false;
}
if (invalidDomains.length) {
LOG_WARN(`Invalid domain(s) skipped for ${service}: ${formatHostnames(invalidDomains)}`);
}
config.domains[service] = validDomains;
}
if (!config.http.port) {
LOG_ERROR('Failed to find HTTP port. Set the PN_ACT_CONFIG_HTTP_PORT environment variable');
configValid = false;
}
if (!config.mongoose.connection_string) {
LOG_ERROR('Failed to find MongoDB connection string. Set the PN_ACT_CONFIG_MONGO_CONNECTION_STRING environment variable');
configValid = false;
}
if (!config.cdn.base_url) {
LOG_ERROR('Failed to find asset CDN base URL. Set the PN_ACT_CONFIG_CDN_BASE_URL environment variable');
configValid = false;
}
if (!config.redis.client.url) {
LOG_WARN('Failed to find Redis connection url. Disabling feature and using in-memory cache. To enable feature set the PN_ACT_CONFIG_REDIS_URL environment variable');
disabledFeatures.redis = true;
}
if (!config.email.ses.region) {
LOG_WARN('Failed to find AWS SES region. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_SES_REGION environment variable');
disabledFeatures.email = true;
}
if (!config.email.ses.key) {
LOG_WARN('Failed to find AWS SES access key. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_SES_ACCESS_KEY environment variable');
disabledFeatures.email = true;
}
if (!config.email.ses.secret) {
LOG_WARN('Failed to find AWS SES secret key. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_SES_SECRET_KEY environment variable');
disabledFeatures.email = true;
}
if (!config.email.from) {
LOG_WARN('Failed to find email from config. Disabling feature. To enable feature set the PN_ACT_CONFIG_EMAIL_FROM environment variable');
disabledFeatures.email = true;
}
if (!disabledFeatures.email) {
if (!config.website_base) {
LOG_ERROR('Email sending is enabled and no website base was configured. Set the PN_ACT_CONFIG_WEBSITE_BASE environment variable');
configValid = false;
}
}
if (!config.hcaptcha.secret) {
LOG_WARN('Failed to find captcha secret config. Disabling feature. To enable feature set the PN_ACT_CONFIG_HCAPTCHA_SECRET environment variable');
disabledFeatures.captcha = true;
}
if (!config.s3.bucket) {
LOG_WARN('Failed to find S3 bucket config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_BUCKET environment variable');
disabledFeatures.s3 = true;
}
if (!config.s3.endpoint) {
LOG_WARN('Failed to find S3 endpoint config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ENDPOINT environment variable');
disabledFeatures.s3 = true;
}
if (!config.s3.key) {
LOG_WARN('Failed to find S3 access key config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ACCESS_KEY environment variable');
disabledFeatures.s3 = true;
}
if (!config.s3.secret) {
LOG_WARN('Failed to find S3 secret key config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_ACCESS_SECRET environment variable');
disabledFeatures.s3 = true;
}
if (!config.s3.region) {
LOG_WARN('Failed to find S3 region config. Disabling feature. To enable feature set the PN_ACT_CONFIG_S3_REGION environment variable');
disabledFeatures.s3 = true;
}
if (!config.server_environment) {
LOG_WARN('Failed to find server environment. To change the environment, set the PN_ACT_CONFIG_SERVER_ENVIRONMENT environment variable. Defaulting to prod');
config.server_environment = 'prod';
}
if (disabledFeatures.s3) {
if (config.domains.local_cdn.length === 0) {
LOG_ERROR('S3 file storage is disabled and no CDN domain was set. Set the PN_ACT_CONFIG_DOMAINS_LOCAL_CDN environment variable');
configValid = false;
}
if (!config.cdn.disk_path) {
LOG_ERROR('S3 file storage is disabled and no CDN disk path was set. Set the PN_ACT_CONFIG_CDN_DISK_PATH environment variable');
configValid = false;
}
if (configValid) {
LOG_WARN(`S3 file storage disabled. Using disk-based file storage. Please ensure cdn.base_url config or PN_ACT_CONFIG_CDN_BASE env variable is set to point to this server with the domain being one of ${formatHostnames(config.domains.local_cdn)}`);
if (disabledFeatures.redis) {
LOG_WARN('Both S3 and Redis are disabled. Large CDN files will use the in-memory cache, which may result in high memory use. Please enable S3 if you\'re running a production server.');
}
}
}
if (!config.aes_key) {
LOG_ERROR('Token AES key is not set. Set the PN_ACT_CONFIG_AES_KEY environment variable to your AES-256-CBC key');
configValid = false;
}
if (!config.grpc.master_api_keys.account) {
LOG_ERROR('Master gRPC API key for the account service is not set. Set the PN_ACT_CONFIG_GRPC_MASTER_API_KEY_ACCOUNT environment variable');
configValid = false;
}
if (!config.grpc.master_api_keys.api) {
LOG_ERROR('Master gRPC API key for the api service is not set. Set the PN_ACT_CONFIG_GRPC_MASTER_API_KEY_API environment variable');
configValid = false;
}
if (!config.grpc.port) {
LOG_ERROR('Failed to find gRPC port. Set the PN_ACT_CONFIG_GRPC_PORT environment variable');
configValid = false;
}
if (!config.stripe?.secret_key) {
LOG_WARN('Failed to find Stripe api key! If a PNID is deleted with an active subscription, the subscription will *NOT* be canceled! Set the PN_ACT_CONFIG_STRIPE_SECRET_KEY environment variable to enable');
}
if (!config.datastore.signature_secret) {
LOG_WARN('Datastore signature secret key is not set. Disabling feature. To enable feature set the PN_ACT_CONFIG_DATASTORE_SIGNATURE_SECRET environment variable');
disabledFeatures.datastore = true;
} else {
if (config.datastore.signature_secret.length !== 32 || !hexadecimalStringRegex.test(config.datastore.signature_secret)) {
LOG_ERROR('Datastore signature secret key must be a 32-character hexadecimal string.');
configValid = false;
}
}
if (!configValid) {
LOG_ERROR('Config is invalid. Exiting');
process.exit(0);
}