feat: env file sync with infra config (#4545)

This commit is contained in:
Mir Arif Hasan
2024-11-26 20:22:51 +06:00
committed by GitHub
parent 80d7dd046d
commit 5dccce39b4
8 changed files with 210 additions and 59 deletions

View File

@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "InfraConfig" DROP COLUMN "active",
ADD COLUMN "lastSyncedEnvFileValue" TEXT;

View File

@@ -214,13 +214,13 @@ enum TeamMemberRole {
} }
model InfraConfig { model InfraConfig {
id String @id @default(cuid()) id String @id @default(cuid())
name String @unique name String @unique
value String? value String?
isEncrypted Boolean @default(false) // Use case: Let's say, Admin wants to store a Secret Key, but doesn't want to store it in plain text in `value` column lastSyncedEnvFileValue String?
active Boolean @default(true) // Use case: Let's say, Admin wants to disable Google SSO, but doesn't want to delete the config isEncrypted Boolean @default(false) // Use case: Let's say, Admin wants to store a Secret Key, but doesn't want to store it in plain text in `value` column
createdOn DateTime @default(now()) @db.Timestamp(3) createdOn DateTime @default(now()) @db.Timestamp(3)
updatedOn DateTime @updatedAt @db.Timestamp(3) updatedOn DateTime @updatedAt @db.Timestamp(3)
} }
model PersonalAccessToken { model PersonalAccessToken {

View File

@@ -13,8 +13,8 @@ import { MicrosoftStrategy } from './strategies/microsoft.strategy';
import { AuthProvider, authProviderCheck } from './helper'; import { AuthProvider, authProviderCheck } from './helper';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
import { import {
getConfiguredSSOProvidersFromInfraConfig,
isInfraConfigTablePopulated, isInfraConfigTablePopulated,
loadInfraConfiguration,
} from 'src/infra-config/helper'; } from 'src/infra-config/helper';
import { InfraConfigModule } from 'src/infra-config/infra-config.module'; import { InfraConfigModule } from 'src/infra-config/infra-config.module';
@@ -42,8 +42,8 @@ export class AuthModule {
return { module: AuthModule }; return { module: AuthModule };
} }
const env = await loadInfraConfiguration(); const allowedAuthProviders =
const allowedAuthProviders = env.INFRA.VITE_ALLOWED_AUTH_PROVIDERS; await getConfiguredSSOProvidersFromInfraConfig();
const providers = [ const providers = [
...(authProviderCheck(AuthProvider.GOOGLE, allowedAuthProviders) ...(authProviderCheck(AuthProvider.GOOGLE, allowedAuthProviders)

View File

@@ -55,6 +55,12 @@ export const ENV_NOT_FOUND_KEY_AUTH_PROVIDERS =
export const ENV_NOT_FOUND_KEY_DATA_ENCRYPTION_KEY = export const ENV_NOT_FOUND_KEY_DATA_ENCRYPTION_KEY =
'"DATA_ENCRYPTION_KEY" is not present in .env file'; '"DATA_ENCRYPTION_KEY" is not present in .env file';
/**
* Environment variable "DATA_ENCRYPTION_KEY" is changed in .env file
*/
export const ENV_INVALID_DATA_ENCRYPTION_KEY =
'"DATA_ENCRYPTION_KEY" value changed in .env file. Please undo the changes and restart the server';
/** /**
* Environment variable "VITE_ALLOWED_AUTH_PROVIDERS" is empty in .env file * Environment variable "VITE_ALLOWED_AUTH_PROVIDERS" is empty in .env file
*/ */

View File

@@ -2,17 +2,26 @@ import { AuthProvider } from 'src/auth/helper';
import { import {
AUTH_PROVIDER_NOT_CONFIGURED, AUTH_PROVIDER_NOT_CONFIGURED,
DATABASE_TABLE_NOT_EXIST, DATABASE_TABLE_NOT_EXIST,
ENV_INVALID_DATA_ENCRYPTION_KEY,
} from 'src/errors'; } from 'src/errors';
import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaService } from 'src/prisma/prisma.service';
import { InfraConfigEnum } from 'src/types/InfraConfig'; import { InfraConfigEnum } from 'src/types/InfraConfig';
import { decrypt, encrypt, throwErr } from 'src/utils'; import { decrypt, encrypt, throwErr } from 'src/utils';
import { randomBytes } from 'crypto'; import { randomBytes } from 'crypto';
import { InfraConfig } from '@prisma/client';
export enum ServiceStatus { export enum ServiceStatus {
ENABLE = 'ENABLE', ENABLE = 'ENABLE',
DISABLE = 'DISABLE', DISABLE = 'DISABLE',
} }
type DefaultInfraConfig = {
name: InfraConfigEnum;
value: string;
lastSyncedEnvFileValue: string;
isEncrypted: boolean;
};
const AuthProviderConfigurations = { const AuthProviderConfigurations = {
[AuthProvider.GOOGLE]: [ [AuthProvider.GOOGLE]: [
InfraConfigEnum.GOOGLE_CLIENT_ID, InfraConfigEnum.GOOGLE_CLIENT_ID,
@@ -33,17 +42,18 @@ const AuthProviderConfigurations = {
InfraConfigEnum.MICROSOFT_SCOPE, InfraConfigEnum.MICROSOFT_SCOPE,
InfraConfigEnum.MICROSOFT_TENANT, InfraConfigEnum.MICROSOFT_TENANT,
], ],
[AuthProvider.EMAIL]: !!process.env.MAILER_USE_CUSTOM_CONFIGS [AuthProvider.EMAIL]:
? [ process.env.MAILER_USE_CUSTOM_CONFIGS === 'true'
InfraConfigEnum.MAILER_SMTP_HOST, ? [
InfraConfigEnum.MAILER_SMTP_PORT, InfraConfigEnum.MAILER_SMTP_HOST,
InfraConfigEnum.MAILER_SMTP_SECURE, InfraConfigEnum.MAILER_SMTP_PORT,
InfraConfigEnum.MAILER_SMTP_USER, InfraConfigEnum.MAILER_SMTP_SECURE,
InfraConfigEnum.MAILER_SMTP_PASSWORD, InfraConfigEnum.MAILER_SMTP_USER,
InfraConfigEnum.MAILER_TLS_REJECT_UNAUTHORIZED, InfraConfigEnum.MAILER_SMTP_PASSWORD,
InfraConfigEnum.MAILER_ADDRESS_FROM, InfraConfigEnum.MAILER_TLS_REJECT_UNAUTHORIZED,
] InfraConfigEnum.MAILER_ADDRESS_FROM,
: [InfraConfigEnum.MAILER_SMTP_URL, InfraConfigEnum.MAILER_ADDRESS_FROM], ]
: [InfraConfigEnum.MAILER_SMTP_URL, InfraConfigEnum.MAILER_ADDRESS_FROM],
}; };
/** /**
@@ -69,6 +79,9 @@ export async function loadInfraConfiguration() {
return { INFRA: environmentObject }; return { INFRA: environmentObject };
} catch (error) { } catch (error) {
if (error.code === 'ERR_OSSL_BAD_DECRYPT')
throw new Error(ENV_INVALID_DATA_ENCRYPTION_KEY);
// Prisma throw error if 'Can't reach at database server' OR 'Table does not exist' // 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 // Reason for not throwing error is, we want successful build during 'postinstall' and generate dist files
return { INFRA: {} }; return { INFRA: {} };
@@ -79,150 +92,174 @@ export async function loadInfraConfiguration() {
* Read the default values from .env file and return them as an array * Read the default values from .env file and return them as an array
* @returns Array of default infra configs * @returns Array of default infra configs
*/ */
export async function getDefaultInfraConfigs(): Promise< export async function getDefaultInfraConfigs(): Promise<DefaultInfraConfig[]> {
{ name: InfraConfigEnum; value: string; isEncrypted: boolean }[]
> {
const prisma = new PrismaService(); const prisma = new PrismaService();
// Prepare rows for 'infra_config' table with default values (from .env) for each 'name' // Prepare rows for 'infra_config' table with default values (from .env) for each 'name'
const infraConfigDefaultObjs: { const configuredSSOProviders = getConfiguredSSOProvidersFromEnvFile();
name: InfraConfigEnum; const generatedAnalyticsUserId = generateAnalyticsUserId();
value: string;
isEncrypted: boolean; const infraConfigDefaultObjs: DefaultInfraConfig[] = [
}[] = [
{ {
name: InfraConfigEnum.MAILER_SMTP_ENABLE, name: InfraConfigEnum.MAILER_SMTP_ENABLE,
value: process.env.MAILER_SMTP_ENABLE ?? 'true', value: process.env.MAILER_SMTP_ENABLE ?? 'true',
lastSyncedEnvFileValue: process.env.MAILER_SMTP_ENABLE ?? 'true',
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.MAILER_USE_CUSTOM_CONFIGS, name: InfraConfigEnum.MAILER_USE_CUSTOM_CONFIGS,
value: process.env.MAILER_USE_CUSTOM_CONFIGS ?? 'false', value: process.env.MAILER_USE_CUSTOM_CONFIGS ?? 'false',
lastSyncedEnvFileValue: process.env.MAILER_USE_CUSTOM_CONFIGS ?? 'false',
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.MAILER_SMTP_URL, name: InfraConfigEnum.MAILER_SMTP_URL,
value: encrypt(process.env.MAILER_SMTP_URL), value: encrypt(process.env.MAILER_SMTP_URL),
lastSyncedEnvFileValue: encrypt(process.env.MAILER_SMTP_URL),
isEncrypted: true, isEncrypted: true,
}, },
{ {
name: InfraConfigEnum.MAILER_ADDRESS_FROM, name: InfraConfigEnum.MAILER_ADDRESS_FROM,
value: process.env.MAILER_ADDRESS_FROM, value: process.env.MAILER_ADDRESS_FROM,
lastSyncedEnvFileValue: process.env.MAILER_ADDRESS_FROM,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.MAILER_SMTP_HOST, name: InfraConfigEnum.MAILER_SMTP_HOST,
value: process.env.MAILER_SMTP_HOST, value: process.env.MAILER_SMTP_HOST,
lastSyncedEnvFileValue: process.env.MAILER_SMTP_HOST,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.MAILER_SMTP_PORT, name: InfraConfigEnum.MAILER_SMTP_PORT,
value: process.env.MAILER_SMTP_PORT, value: process.env.MAILER_SMTP_PORT,
lastSyncedEnvFileValue: process.env.MAILER_SMTP_PORT,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.MAILER_SMTP_SECURE, name: InfraConfigEnum.MAILER_SMTP_SECURE,
value: process.env.MAILER_SMTP_SECURE, value: process.env.MAILER_SMTP_SECURE,
lastSyncedEnvFileValue: process.env.MAILER_SMTP_SECURE,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.MAILER_SMTP_USER, name: InfraConfigEnum.MAILER_SMTP_USER,
value: process.env.MAILER_SMTP_USER, value: process.env.MAILER_SMTP_USER,
lastSyncedEnvFileValue: process.env.MAILER_SMTP_USER,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.MAILER_SMTP_PASSWORD, name: InfraConfigEnum.MAILER_SMTP_PASSWORD,
value: encrypt(process.env.MAILER_SMTP_PASSWORD), value: encrypt(process.env.MAILER_SMTP_PASSWORD),
lastSyncedEnvFileValue: encrypt(process.env.MAILER_SMTP_PASSWORD),
isEncrypted: true, isEncrypted: true,
}, },
{ {
name: InfraConfigEnum.MAILER_TLS_REJECT_UNAUTHORIZED, name: InfraConfigEnum.MAILER_TLS_REJECT_UNAUTHORIZED,
value: process.env.MAILER_TLS_REJECT_UNAUTHORIZED, value: process.env.MAILER_TLS_REJECT_UNAUTHORIZED,
lastSyncedEnvFileValue: process.env.MAILER_TLS_REJECT_UNAUTHORIZED,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.GOOGLE_CLIENT_ID, name: InfraConfigEnum.GOOGLE_CLIENT_ID,
value: encrypt(process.env.GOOGLE_CLIENT_ID), value: encrypt(process.env.GOOGLE_CLIENT_ID),
lastSyncedEnvFileValue: encrypt(process.env.GOOGLE_CLIENT_ID),
isEncrypted: true, isEncrypted: true,
}, },
{ {
name: InfraConfigEnum.GOOGLE_CLIENT_SECRET, name: InfraConfigEnum.GOOGLE_CLIENT_SECRET,
value: encrypt(process.env.GOOGLE_CLIENT_SECRET), value: encrypt(process.env.GOOGLE_CLIENT_SECRET),
lastSyncedEnvFileValue: encrypt(process.env.GOOGLE_CLIENT_SECRET),
isEncrypted: true, isEncrypted: true,
}, },
{ {
name: InfraConfigEnum.GOOGLE_CALLBACK_URL, name: InfraConfigEnum.GOOGLE_CALLBACK_URL,
value: process.env.GOOGLE_CALLBACK_URL, value: process.env.GOOGLE_CALLBACK_URL,
lastSyncedEnvFileValue: process.env.GOOGLE_CALLBACK_URL,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.GOOGLE_SCOPE, name: InfraConfigEnum.GOOGLE_SCOPE,
value: process.env.GOOGLE_SCOPE, value: process.env.GOOGLE_SCOPE,
lastSyncedEnvFileValue: process.env.GOOGLE_SCOPE,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.GITHUB_CLIENT_ID, name: InfraConfigEnum.GITHUB_CLIENT_ID,
value: encrypt(process.env.GITHUB_CLIENT_ID), value: encrypt(process.env.GITHUB_CLIENT_ID),
lastSyncedEnvFileValue: encrypt(process.env.GITHUB_CLIENT_ID),
isEncrypted: true, isEncrypted: true,
}, },
{ {
name: InfraConfigEnum.GITHUB_CLIENT_SECRET, name: InfraConfigEnum.GITHUB_CLIENT_SECRET,
value: encrypt(process.env.GITHUB_CLIENT_SECRET), value: encrypt(process.env.GITHUB_CLIENT_SECRET),
lastSyncedEnvFileValue: encrypt(process.env.GITHUB_CLIENT_SECRET),
isEncrypted: true, isEncrypted: true,
}, },
{ {
name: InfraConfigEnum.GITHUB_CALLBACK_URL, name: InfraConfigEnum.GITHUB_CALLBACK_URL,
value: process.env.GITHUB_CALLBACK_URL, value: process.env.GITHUB_CALLBACK_URL,
lastSyncedEnvFileValue: process.env.GITHUB_CALLBACK_URL,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.GITHUB_SCOPE, name: InfraConfigEnum.GITHUB_SCOPE,
value: process.env.GITHUB_SCOPE, value: process.env.GITHUB_SCOPE,
lastSyncedEnvFileValue: process.env.GITHUB_SCOPE,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.MICROSOFT_CLIENT_ID, name: InfraConfigEnum.MICROSOFT_CLIENT_ID,
value: encrypt(process.env.MICROSOFT_CLIENT_ID), value: encrypt(process.env.MICROSOFT_CLIENT_ID),
lastSyncedEnvFileValue: encrypt(process.env.MICROSOFT_CLIENT_ID),
isEncrypted: true, isEncrypted: true,
}, },
{ {
name: InfraConfigEnum.MICROSOFT_CLIENT_SECRET, name: InfraConfigEnum.MICROSOFT_CLIENT_SECRET,
value: encrypt(process.env.MICROSOFT_CLIENT_SECRET), value: encrypt(process.env.MICROSOFT_CLIENT_SECRET),
lastSyncedEnvFileValue: encrypt(process.env.MICROSOFT_CLIENT_SECRET),
isEncrypted: true, isEncrypted: true,
}, },
{ {
name: InfraConfigEnum.MICROSOFT_CALLBACK_URL, name: InfraConfigEnum.MICROSOFT_CALLBACK_URL,
value: process.env.MICROSOFT_CALLBACK_URL, value: process.env.MICROSOFT_CALLBACK_URL,
lastSyncedEnvFileValue: process.env.MICROSOFT_CALLBACK_URL,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.MICROSOFT_SCOPE, name: InfraConfigEnum.MICROSOFT_SCOPE,
value: process.env.MICROSOFT_SCOPE, value: process.env.MICROSOFT_SCOPE,
lastSyncedEnvFileValue: process.env.MICROSOFT_SCOPE,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.MICROSOFT_TENANT, name: InfraConfigEnum.MICROSOFT_TENANT,
value: process.env.MICROSOFT_TENANT, value: process.env.MICROSOFT_TENANT,
lastSyncedEnvFileValue: process.env.MICROSOFT_TENANT,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS, name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS,
value: getConfiguredSSOProviders(), value: configuredSSOProviders,
lastSyncedEnvFileValue: configuredSSOProviders,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.ALLOW_ANALYTICS_COLLECTION, name: InfraConfigEnum.ALLOW_ANALYTICS_COLLECTION,
value: false.toString(), value: false.toString(),
lastSyncedEnvFileValue: null,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.ANALYTICS_USER_ID, name: InfraConfigEnum.ANALYTICS_USER_ID,
value: generateAnalyticsUserId(), value: generatedAnalyticsUserId,
lastSyncedEnvFileValue: null,
isEncrypted: false, isEncrypted: false,
}, },
{ {
name: InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP, name: InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP,
value: (await prisma.infraConfig.count()) === 0 ? 'true' : 'false', value: (await prisma.infraConfig.count()) === 0 ? 'true' : 'false',
lastSyncedEnvFileValue: null,
isEncrypted: false, isEncrypted: false,
}, },
]; ];
@@ -234,12 +271,11 @@ export async function getDefaultInfraConfigs(): Promise<
* Get the missing entries in the 'infra_config' table * Get the missing entries in the 'infra_config' table
* @returns Array of InfraConfig * @returns Array of InfraConfig
*/ */
export async function getMissingInfraConfigEntries() { export async function getMissingInfraConfigEntries(
infraConfigDefaultObjs: DefaultInfraConfig[],
) {
const prisma = new PrismaService(); const prisma = new PrismaService();
const [dbInfraConfigs, infraConfigDefaultObjs] = await Promise.all([ const dbInfraConfigs = await prisma.infraConfig.findMany();
prisma.infraConfig.findMany(),
getDefaultInfraConfigs(),
]);
const missingEntries = infraConfigDefaultObjs.filter( const missingEntries = infraConfigDefaultObjs.filter(
(config) => (config) =>
@@ -253,12 +289,11 @@ export async function getMissingInfraConfigEntries() {
* Get the encryption required entries in the 'infra_config' table * Get the encryption required entries in the 'infra_config' table
* @returns Array of InfraConfig * @returns Array of InfraConfig
*/ */
export async function getEncryptionRequiredInfraConfigEntries() { export async function getEncryptionRequiredInfraConfigEntries(
infraConfigDefaultObjs: DefaultInfraConfig[],
) {
const prisma = new PrismaService(); const prisma = new PrismaService();
const [dbInfraConfigs, infraConfigDefaultObjs] = await Promise.all([ const dbInfraConfigs = await prisma.infraConfig.findMany();
prisma.infraConfig.findMany(),
getDefaultInfraConfigs(),
]);
const requiredEncryption = dbInfraConfigs.filter((dbConfig) => { const requiredEncryption = dbInfraConfigs.filter((dbConfig) => {
const defaultConfig = infraConfigDefaultObjs.find( const defaultConfig = infraConfigDefaultObjs.find(
@@ -271,13 +306,57 @@ export async function getEncryptionRequiredInfraConfigEntries() {
return requiredEncryption; return requiredEncryption;
} }
/**
* Sync the 'infra_config' table with .env file
* @returns Array of InfraConfig
*/
export async function syncInfraConfigWithEnvFile() {
const prisma = new PrismaService();
const dbInfraConfigs = await prisma.infraConfig.findMany();
const updateRequiredObjs: (Partial<InfraConfig> & { id: string })[] = [];
for (const dbConfig of dbInfraConfigs) {
let envValue = process.env[dbConfig.name];
// lastSyncedEnvFileValue null check for backward compatibility from 2024.10.2 and below
if (!dbConfig.lastSyncedEnvFileValue && envValue) {
const configValue = dbConfig.isEncrypted ? encrypt(envValue) : envValue;
updateRequiredObjs.push({
id: dbConfig.id,
value: dbConfig.value === null ? configValue : undefined,
lastSyncedEnvFileValue: configValue,
});
continue;
}
// If the value in the database is different from the value in the .env file, means the value in the .env file has been updated
const rawLastSyncedEnvFileValue = dbConfig.isEncrypted
? decrypt(dbConfig.lastSyncedEnvFileValue)
: dbConfig.lastSyncedEnvFileValue;
if (rawLastSyncedEnvFileValue != envValue) {
const configValue = dbConfig.isEncrypted ? encrypt(envValue) : envValue;
updateRequiredObjs.push({
id: dbConfig.id,
value: configValue ?? null,
lastSyncedEnvFileValue: configValue ?? null,
});
}
}
return updateRequiredObjs;
}
/** /**
* Verify if 'infra_config' table is loaded with all entries * Verify if 'infra_config' table is loaded with all entries
* @returns boolean * @returns boolean
*/ */
export async function isInfraConfigTablePopulated(): Promise<boolean> { export async function isInfraConfigTablePopulated(): Promise<boolean> {
try { try {
const propsRemainingToInsert = await getMissingInfraConfigEntries(); const defaultInfraConfigs = await getDefaultInfraConfigs();
const propsRemainingToInsert =
await getMissingInfraConfigEntries(defaultInfraConfigs);
if (propsRemainingToInsert.length > 0) { if (propsRemainingToInsert.length > 0) {
console.log( console.log(
@@ -306,10 +385,11 @@ export function stopApp() {
} }
/** /**
* Get the configured SSO providers * Get the configured SSO providers from .env file
* @description This function verify if the required parameters for each SSO provider are configured in .env file. Usage on first time setup and reset.
* @returns Array of configured SSO providers * @returns Array of configured SSO providers
*/ */
export function getConfiguredSSOProviders() { export function getConfiguredSSOProvidersFromEnvFile() {
const allowedAuthProviders: string[] = const allowedAuthProviders: string[] =
process.env.VITE_ALLOWED_AUTH_PROVIDERS.split(','); process.env.VITE_ALLOWED_AUTH_PROVIDERS.split(',');
let configuredAuthProviders: string[] = []; let configuredAuthProviders: string[] = [];
@@ -320,7 +400,6 @@ export function getConfiguredSSOProviders() {
const isConfigured = configParameters.every((configParameter) => { const isConfigured = configParameters.every((configParameter) => {
return process.env[configParameter]; return process.env[configParameter];
}); });
if (isConfigured) configuredAuthProviders.push(provider); if (isConfigured) configuredAuthProviders.push(provider);
}; };
@@ -337,7 +416,47 @@ export function getConfiguredSSOProviders() {
console.log( console.log(
`${unConfiguredAuthProviders.join( `${unConfiguredAuthProviders.join(
',', ',',
)} SSO auth provider(s) are not configured properly. Do configure them from Admin Dashboard.`, )} SSO auth provider(s) are not configured properly in .env file. Do configure them from Admin Dashboard.`,
);
}
return configuredAuthProviders.join(',');
}
/**
* Get the configured SSO providers from 'infra_config' table.
* @description Usage every time the app starts by AuthModule to initiate Strategies.
* @returns Array of configured SSO providers
*/
export async function getConfiguredSSOProvidersFromInfraConfig() {
const env = await loadInfraConfiguration();
const allowedAuthProviders: string[] =
env['INFRA'].VITE_ALLOWED_AUTH_PROVIDERS.split(',');
let configuredAuthProviders: string[] = [];
const addProviderIfConfigured = (provider) => {
const configParameters: string[] = AuthProviderConfigurations[provider];
const isConfigured = configParameters.every((configParameter) => {
return env['INFRA'][configParameter];
});
if (isConfigured) configuredAuthProviders.push(provider);
};
allowedAuthProviders.forEach((provider) => addProviderIfConfigured(provider));
if (configuredAuthProviders.length === 0) {
return '';
} else if (allowedAuthProviders.length !== configuredAuthProviders.length) {
const prisma = new PrismaService();
await prisma.infraConfig.update({
where: { name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS },
data: { value: configuredAuthProviders.join(',') },
});
stopApp();
console.log(
`${configuredAuthProviders.join(',')} SSO auth provider(s) are configured properly. To enable other SSO providers, configure them from Admin Dashboard.`,
); );
} }

View File

@@ -28,8 +28,8 @@ const dbInfraConfigs: dbInfraConfig[] = [
id: '3', id: '3',
name: InfraConfigEnum.GOOGLE_CLIENT_ID, name: InfraConfigEnum.GOOGLE_CLIENT_ID,
value: 'abcdefghijkl', value: 'abcdefghijkl',
lastSyncedEnvFileValue: 'abcdefghijkl',
isEncrypted: false, isEncrypted: false,
active: true,
createdOn: INITIALIZED_DATE_CONST, createdOn: INITIALIZED_DATE_CONST,
updatedOn: INITIALIZED_DATE_CONST, updatedOn: INITIALIZED_DATE_CONST,
}, },
@@ -37,8 +37,8 @@ const dbInfraConfigs: dbInfraConfig[] = [
id: '4', id: '4',
name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS, name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS,
value: 'google', value: 'google',
lastSyncedEnvFileValue: 'google',
isEncrypted: false, isEncrypted: false,
active: true,
createdOn: INITIALIZED_DATE_CONST, createdOn: INITIALIZED_DATE_CONST,
updatedOn: INITIALIZED_DATE_CONST, updatedOn: INITIALIZED_DATE_CONST,
}, },
@@ -72,8 +72,8 @@ describe('InfraConfigService', () => {
id: '', id: '',
name, name,
value, value,
lastSyncedEnvFileValue: value,
isEncrypted: false, isEncrypted: false,
active: true,
createdOn: new Date(), createdOn: new Date(),
updatedOn: new Date(), updatedOn: new Date(),
}); });
@@ -97,8 +97,8 @@ describe('InfraConfigService', () => {
id: '', id: '',
name, name,
value, value,
lastSyncedEnvFileValue: value,
isEncrypted: false, isEncrypted: false,
active: true,
createdOn: new Date(), createdOn: new Date(),
updatedOn: new Date(), updatedOn: new Date(),
}); });
@@ -122,8 +122,8 @@ describe('InfraConfigService', () => {
id: '', id: '',
name, name,
value, value,
lastSyncedEnvFileValue: value,
isEncrypted: false, isEncrypted: false,
active: true,
createdOn: new Date(), createdOn: new Date(),
updatedOn: new Date(), updatedOn: new Date(),
}); });
@@ -173,8 +173,8 @@ describe('InfraConfigService', () => {
id: '', id: '',
name, name,
value, value,
lastSyncedEnvFileValue: value,
isEncrypted: false, isEncrypted: false,
active: true,
createdOn: new Date(), createdOn: new Date(),
updatedOn: new Date(), updatedOn: new Date(),
}); });

View File

@@ -29,6 +29,7 @@ import {
getEncryptionRequiredInfraConfigEntries, getEncryptionRequiredInfraConfigEntries,
getMissingInfraConfigEntries, getMissingInfraConfigEntries,
stopApp, stopApp,
syncInfraConfigWithEnvFile,
} from './helper'; } from './helper';
import { EnableAndDisableSSOArgs, InfraConfigArgs } from './input-args'; import { EnableAndDisableSSOArgs, InfraConfigArgs } from './input-args';
import { AuthProvider } from 'src/auth/helper'; import { AuthProvider } from 'src/auth/helper';
@@ -65,8 +66,11 @@ export class InfraConfigService implements OnModuleInit {
*/ */
async initializeInfraConfigTable() { async initializeInfraConfigTable() {
try { try {
const defaultInfraConfigs = await getDefaultInfraConfigs();
// Adding missing InfraConfigs to the database (with encrypted values) // Adding missing InfraConfigs to the database (with encrypted values)
const propsToInsert = await getMissingInfraConfigEntries(); const propsToInsert =
await getMissingInfraConfigEntries(defaultInfraConfigs);
if (propsToInsert.length > 0) { if (propsToInsert.length > 0) {
await this.prisma.infraConfig.createMany({ data: propsToInsert }); await this.prisma.infraConfig.createMany({ data: propsToInsert });
@@ -74,7 +78,7 @@ export class InfraConfigService implements OnModuleInit {
// Encrypting previous InfraConfigs that are required to be encrypted // Encrypting previous InfraConfigs that are required to be encrypted
const encryptionRequiredEntries = const encryptionRequiredEntries =
await getEncryptionRequiredInfraConfigEntries(); await getEncryptionRequiredInfraConfigEntries(defaultInfraConfigs);
if (encryptionRequiredEntries.length > 0) { if (encryptionRequiredEntries.length > 0) {
const dbOperations = encryptionRequiredEntries.map((dbConfig) => { const dbOperations = encryptionRequiredEntries.map((dbConfig) => {
@@ -87,8 +91,25 @@ export class InfraConfigService implements OnModuleInit {
await Promise.allSettled(dbOperations); await Promise.allSettled(dbOperations);
} }
// Sync the InfraConfigs with the .env file, if .env file updates later on
const envFileChangesRequired = await syncInfraConfigWithEnvFile();
if (envFileChangesRequired.length > 0) {
const dbOperations = envFileChangesRequired.map((dbConfig) => {
const { id, ...dataObj } = dbConfig;
return this.prisma.infraConfig.update({
where: { id: dbConfig.id },
data: dataObj,
});
});
await Promise.allSettled(dbOperations);
}
// Restart the app if needed // Restart the app if needed
if (propsToInsert.length > 0 || encryptionRequiredEntries.length > 0) { if (
propsToInsert.length > 0 ||
encryptionRequiredEntries.length > 0 ||
envFileChangesRequired.length > 0
) {
stopApp(); stopApp();
} }
} catch (error) { } catch (error) {

View File

@@ -546,10 +546,12 @@ describe('ShortcodeService', () => {
); );
expect(result).toEqual(<ShortcodeWithUserEmail[]>[ expect(result).toEqual(<ShortcodeWithUserEmail[]>[
{ {
id: shortcodes[1].id, id: shortcodesWithUserEmail[1].id,
request: JSON.stringify(shortcodes[1].request), request: JSON.stringify(shortcodesWithUserEmail[1].request),
properties: JSON.stringify(shortcodes[1].embedProperties), properties: JSON.stringify(
createdOn: shortcodes[1].createdOn, shortcodesWithUserEmail[1].embedProperties,
),
createdOn: shortcodesWithUserEmail[1].createdOn,
creator: { creator: {
uid: user.uid, uid: user.uid,
email: user.email, email: user.email,