import { AuthProvider } from 'src/auth/helper'; import { AUTH_PROVIDER_NOT_CONFIGURED, DATABASE_TABLE_NOT_EXIST, } from 'src/errors'; import { PrismaService } from 'src/prisma/prisma.service'; import { InfraConfigEnum } from 'src/types/InfraConfig'; import { decrypt, encrypt, throwErr } from 'src/utils'; import { randomBytes } from 'crypto'; export enum ServiceStatus { ENABLE = 'ENABLE', DISABLE = 'DISABLE', } const AuthProviderConfigurations = { [AuthProvider.GOOGLE]: [ InfraConfigEnum.GOOGLE_CLIENT_ID, InfraConfigEnum.GOOGLE_CLIENT_SECRET, InfraConfigEnum.GOOGLE_CALLBACK_URL, InfraConfigEnum.GOOGLE_SCOPE, ], [AuthProvider.GITHUB]: [ InfraConfigEnum.GITHUB_CLIENT_ID, InfraConfigEnum.GITHUB_CLIENT_SECRET, InfraConfigEnum.GITHUB_CALLBACK_URL, InfraConfigEnum.GITHUB_SCOPE, ], [AuthProvider.MICROSOFT]: [ InfraConfigEnum.MICROSOFT_CLIENT_ID, InfraConfigEnum.MICROSOFT_CLIENT_SECRET, InfraConfigEnum.MICROSOFT_CALLBACK_URL, InfraConfigEnum.MICROSOFT_SCOPE, InfraConfigEnum.MICROSOFT_TENANT, ], [AuthProvider.EMAIL]: !!process.env.MAILER_USE_CUSTOM_CONFIGS ? [ InfraConfigEnum.MAILER_SMTP_HOST, InfraConfigEnum.MAILER_SMTP_PORT, InfraConfigEnum.MAILER_SMTP_SECURE, InfraConfigEnum.MAILER_SMTP_USER, InfraConfigEnum.MAILER_SMTP_PASSWORD, InfraConfigEnum.MAILER_TLS_REJECT_UNAUTHORIZED, InfraConfigEnum.MAILER_ADDRESS_FROM, ] : [InfraConfigEnum.MAILER_SMTP_URL, InfraConfigEnum.MAILER_ADDRESS_FROM], }; /** * Load environment variables from the database and set them in the process * * @Description Fetch the 'infra_config' table from the database and return it as an object * (ConfigModule will set the environment variables in the process) */ export async function loadInfraConfiguration() { try { const prisma = new PrismaService(); const infraConfigs = await prisma.infraConfig.findMany(); let environmentObject: Record = {}; infraConfigs.forEach((infraConfig) => { if (infraConfig.isEncrypted) { environmentObject[infraConfig.name] = decrypt(infraConfig.value); } else { environmentObject[infraConfig.name] = infraConfig.value; } }); return { INFRA: environmentObject }; } catch (error) { // Prisma throw error if 'Can't reach at database server' OR 'Table does not exist' // Reason for not throwing error is, we want successful build during 'postinstall' and generate dist files return { INFRA: {} }; } } /** * Read the default values from .env file and return them as an array * @returns Array of default infra configs */ export async function getDefaultInfraConfigs(): Promise< { name: InfraConfigEnum; value: string; isEncrypted: boolean }[] > { const prisma = new PrismaService(); // Prepare rows for 'infra_config' table with default values (from .env) for each 'name' const infraConfigDefaultObjs: { name: InfraConfigEnum; value: string; isEncrypted: boolean; }[] = [ { name: InfraConfigEnum.MAILER_SMTP_ENABLE, value: process.env.MAILER_SMTP_ENABLE ?? 'true', isEncrypted: false, }, { name: InfraConfigEnum.MAILER_USE_CUSTOM_CONFIGS, value: process.env.MAILER_USE_CUSTOM_CONFIGS ?? 'false', isEncrypted: false, }, { name: InfraConfigEnum.MAILER_SMTP_URL, value: encrypt(process.env.MAILER_SMTP_URL), isEncrypted: true, }, { name: InfraConfigEnum.MAILER_ADDRESS_FROM, value: process.env.MAILER_ADDRESS_FROM, isEncrypted: false, }, { name: InfraConfigEnum.MAILER_SMTP_HOST, value: process.env.MAILER_SMTP_HOST, isEncrypted: false, }, { name: InfraConfigEnum.MAILER_SMTP_PORT, value: process.env.MAILER_SMTP_PORT, isEncrypted: false, }, { name: InfraConfigEnum.MAILER_SMTP_SECURE, value: process.env.MAILER_SMTP_SECURE, isEncrypted: false, }, { name: InfraConfigEnum.MAILER_SMTP_USER, value: process.env.MAILER_SMTP_USER, isEncrypted: false, }, { name: InfraConfigEnum.MAILER_SMTP_PASSWORD, value: encrypt(process.env.MAILER_SMTP_PASSWORD), isEncrypted: true, }, { name: InfraConfigEnum.MAILER_TLS_REJECT_UNAUTHORIZED, value: process.env.MAILER_TLS_REJECT_UNAUTHORIZED, isEncrypted: false, }, { name: InfraConfigEnum.GOOGLE_CLIENT_ID, value: encrypt(process.env.GOOGLE_CLIENT_ID), isEncrypted: true, }, { name: InfraConfigEnum.GOOGLE_CLIENT_SECRET, value: encrypt(process.env.GOOGLE_CLIENT_SECRET), isEncrypted: true, }, { name: InfraConfigEnum.GOOGLE_CALLBACK_URL, value: process.env.GOOGLE_CALLBACK_URL, isEncrypted: false, }, { name: InfraConfigEnum.GOOGLE_SCOPE, value: process.env.GOOGLE_SCOPE, isEncrypted: false, }, { name: InfraConfigEnum.GITHUB_CLIENT_ID, value: encrypt(process.env.GITHUB_CLIENT_ID), isEncrypted: true, }, { name: InfraConfigEnum.GITHUB_CLIENT_SECRET, value: encrypt(process.env.GITHUB_CLIENT_SECRET), isEncrypted: true, }, { name: InfraConfigEnum.GITHUB_CALLBACK_URL, value: process.env.GITHUB_CALLBACK_URL, isEncrypted: false, }, { name: InfraConfigEnum.GITHUB_SCOPE, value: process.env.GITHUB_SCOPE, isEncrypted: false, }, { name: InfraConfigEnum.MICROSOFT_CLIENT_ID, value: encrypt(process.env.MICROSOFT_CLIENT_ID), isEncrypted: true, }, { name: InfraConfigEnum.MICROSOFT_CLIENT_SECRET, value: encrypt(process.env.MICROSOFT_CLIENT_SECRET), isEncrypted: true, }, { name: InfraConfigEnum.MICROSOFT_CALLBACK_URL, value: process.env.MICROSOFT_CALLBACK_URL, isEncrypted: false, }, { name: InfraConfigEnum.MICROSOFT_SCOPE, value: process.env.MICROSOFT_SCOPE, isEncrypted: false, }, { name: InfraConfigEnum.MICROSOFT_TENANT, value: process.env.MICROSOFT_TENANT, isEncrypted: false, }, { name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS, value: getConfiguredSSOProviders(), isEncrypted: false, }, { name: InfraConfigEnum.ALLOW_ANALYTICS_COLLECTION, value: false.toString(), isEncrypted: false, }, { name: InfraConfigEnum.ANALYTICS_USER_ID, value: generateAnalyticsUserId(), isEncrypted: false, }, { name: InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP, value: (await prisma.infraConfig.count()) === 0 ? 'true' : 'false', isEncrypted: false, }, ]; return infraConfigDefaultObjs; } /** * Get the missing entries in the 'infra_config' table * @returns Array of InfraConfig */ export async function getMissingInfraConfigEntries() { const prisma = new PrismaService(); const [dbInfraConfigs, infraConfigDefaultObjs] = await Promise.all([ prisma.infraConfig.findMany(), getDefaultInfraConfigs(), ]); const missingEntries = infraConfigDefaultObjs.filter( (config) => !dbInfraConfigs.some((dbConfig) => dbConfig.name === config.name), ); return missingEntries; } /** * Get the encryption required entries in the 'infra_config' table * @returns Array of InfraConfig */ export async function getEncryptionRequiredInfraConfigEntries() { const prisma = new PrismaService(); const [dbInfraConfigs, infraConfigDefaultObjs] = await Promise.all([ prisma.infraConfig.findMany(), getDefaultInfraConfigs(), ]); const requiredEncryption = dbInfraConfigs.filter((dbConfig) => { const defaultConfig = infraConfigDefaultObjs.find( (config) => config.name === dbConfig.name, ); if (!defaultConfig) return false; return defaultConfig.isEncrypted !== dbConfig.isEncrypted; }); return requiredEncryption; } /** * Verify if 'infra_config' table is loaded with all entries * @returns boolean */ export async function isInfraConfigTablePopulated(): Promise { try { const propsRemainingToInsert = await getMissingInfraConfigEntries(); if (propsRemainingToInsert.length > 0) { console.log( 'Infra Config table is not populated with all entries. Populating now...', ); return false; } return true; } catch (error) { return false; } } /** * Stop the app after 5 seconds * (Docker will re-start the app) */ export function stopApp() { console.log('Stopping app in 5 seconds...'); setTimeout(() => { console.log('Stopping app now...'); process.kill(process.pid, 'SIGTERM'); }, 5000); } /** * Get the configured SSO providers * @returns Array of configured SSO providers */ export function getConfiguredSSOProviders() { const allowedAuthProviders: string[] = process.env.VITE_ALLOWED_AUTH_PROVIDERS.split(','); let configuredAuthProviders: string[] = []; const addProviderIfConfigured = (provider) => { const configParameters: string[] = AuthProviderConfigurations[provider]; const isConfigured = configParameters.every((configParameter) => { return process.env[configParameter]; }); if (isConfigured) configuredAuthProviders.push(provider); }; allowedAuthProviders.forEach((provider) => addProviderIfConfigured(provider)); if (configuredAuthProviders.length === 0) { throwErr(AUTH_PROVIDER_NOT_CONFIGURED); } else if (allowedAuthProviders.length !== configuredAuthProviders.length) { const unConfiguredAuthProviders = allowedAuthProviders.filter( (provider) => { return !configuredAuthProviders.includes(provider); }, ); console.log( `${unConfiguredAuthProviders.join( ',', )} SSO auth provider(s) are not configured properly. Do configure them from Admin Dashboard.`, ); } return configuredAuthProviders.join(','); } /** * Generate a hashed valued for analytics * @returns Generated hashed value */ export function generateAnalyticsUserId() { const hashedUserID = randomBytes(20).toString('hex'); return hashedUserID; }