HSB-473 feat: encrypt sensitive data before storing in db (#4212)

* feat: encryption added on onMuduleInit

* feat: encryption changes added on sh admin mutations and query

* chore: fetch minimum column from DB

* feat: data encryption added on account table

* test: infra config test case update

* chore: env example modified

* chore: update variable name

* chore: refactor the code

* feat: client-ids made encrypted

* chore: encrypted auth client id's

---------

Co-authored-by: Balu Babu <balub997@gmail.com>
This commit is contained in:
Mir Arif Hasan
2024-08-14 13:34:12 +06:00
committed by GitHub
parent 3f78bf1062
commit 67f0324521
8 changed files with 213 additions and 26 deletions

View File

@@ -5,7 +5,7 @@ import {
} from 'src/errors';
import { PrismaService } from 'src/prisma/prisma.service';
import { InfraConfigEnum } from 'src/types/InfraConfig';
import { throwErr } from 'src/utils';
import { decrypt, encrypt, throwErr } from 'src/utils';
import { randomBytes } from 'crypto';
export enum ServiceStatus {
@@ -60,7 +60,11 @@ export async function loadInfraConfiguration() {
let environmentObject: Record<string, any> = {};
infraConfigs.forEach((infraConfig) => {
environmentObject[infraConfig.name] = infraConfig.value;
if (infraConfig.isEncrypted) {
environmentObject[infraConfig.name] = decrypt(infraConfig.value);
} else {
environmentObject[infraConfig.name] = infraConfig.value;
}
});
return { INFRA: environmentObject };
@@ -76,119 +80,150 @@ export async function loadInfraConfiguration() {
* @returns Array of default infra configs
*/
export async function getDefaultInfraConfigs(): Promise<
{ name: InfraConfigEnum; value: string }[]
{ 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 }[] = [
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: process.env.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: process.env.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: process.env.GOOGLE_CLIENT_ID,
value: encrypt(process.env.GOOGLE_CLIENT_ID),
isEncrypted: true,
},
{
name: InfraConfigEnum.GOOGLE_CLIENT_SECRET,
value: process.env.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: process.env.GITHUB_CLIENT_ID,
value: encrypt(process.env.GITHUB_CLIENT_ID),
isEncrypted: true,
},
{
name: InfraConfigEnum.GITHUB_CLIENT_SECRET,
value: process.env.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: process.env.MICROSOFT_CLIENT_ID,
value: encrypt(process.env.MICROSOFT_CLIENT_ID),
isEncrypted: true,
},
{
name: InfraConfigEnum.MICROSOFT_CLIENT_SECRET,
value: process.env.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,
},
];
@@ -214,12 +249,33 @@ export async function getMissingInfraConfigEntries() {
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<boolean> {
const prisma = new PrismaService();
try {
const propsRemainingToInsert = await getMissingInfraConfigEntries();