fix: improve smtp email validation and fix enableAndDisableSSO mutation (#3689)

Co-authored-by: Balu Babu <balub997@gmail.com>
This commit is contained in:
Mir Arif Hasan
2023-12-22 21:04:54 +06:00
committed by Andrew Bastin
parent 0df194f9c5
commit 54d8378ccf
5 changed files with 123 additions and 24 deletions

View File

@@ -28,6 +28,13 @@ export const JSON_INVALID = 'json_invalid';
*/ */
export const AUTH_PROVIDER_NOT_SPECIFIED = 'auth/provider_not_specified'; export const AUTH_PROVIDER_NOT_SPECIFIED = 'auth/provider_not_specified';
/**
* Auth Provider not specified
* (Auth)
*/
export const AUTH_PROVIDER_NOT_CONFIGURED =
'auth/provider_not_configured_correctly';
/** /**
* Environment variable "VITE_ALLOWED_AUTH_PROVIDERS" is not present in .env file * Environment variable "VITE_ALLOWED_AUTH_PROVIDERS" is not present in .env file
*/ */

View File

@@ -1,10 +1,33 @@
import { AuthProvider } from 'src/auth/helper';
import { AUTH_PROVIDER_NOT_CONFIGURED } from 'src/errors';
import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaService } from 'src/prisma/prisma.service';
import { InfraConfigEnum } from 'src/types/InfraConfig';
import { throwErr } from 'src/utils';
export enum ServiceStatus { export enum ServiceStatus {
ENABLE = 'ENABLE', ENABLE = 'ENABLE',
DISABLE = 'DISABLE', DISABLE = 'DISABLE',
} }
const AuthProviderConfigurations = {
[AuthProvider.GOOGLE]: [
InfraConfigEnum.GOOGLE_CLIENT_ID,
InfraConfigEnum.GOOGLE_CLIENT_SECRET,
],
[AuthProvider.GITHUB]: [
InfraConfigEnum.GITHUB_CLIENT_ID,
InfraConfigEnum.GITHUB_CLIENT_SECRET,
],
[AuthProvider.MICROSOFT]: [
InfraConfigEnum.MICROSOFT_CLIENT_ID,
InfraConfigEnum.MICROSOFT_CLIENT_SECRET,
],
[AuthProvider.EMAIL]: [
InfraConfigEnum.MAILER_SMTP_URL,
InfraConfigEnum.MAILER_ADDRESS_FROM,
],
};
/** /**
* Load environment variables from the database and set them in the process * Load environment variables from the database and set them in the process
* *
@@ -42,3 +65,42 @@ export function stopApp() {
process.kill(process.pid, 'SIGTERM'); process.kill(process.pid, 'SIGTERM');
}, 5000); }, 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(',');
}

View File

@@ -17,9 +17,9 @@ import {
INFRA_CONFIG_UPDATE_FAILED, INFRA_CONFIG_UPDATE_FAILED,
INFRA_CONFIG_SERVICE_NOT_CONFIGURED, INFRA_CONFIG_SERVICE_NOT_CONFIGURED,
} from 'src/errors'; } from 'src/errors';
import { throwErr, validateEmail, validateSMTPUrl } from 'src/utils'; import { throwErr, validateSMTPEmail, validateSMTPUrl } from 'src/utils';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { ServiceStatus, stopApp } from './helper'; import { ServiceStatus, getConfiguredSSOProviders, stopApp } 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';
@@ -71,7 +71,7 @@ export class InfraConfigService implements OnModuleInit {
}, },
{ {
name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS, name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS,
value: process.env.VITE_ALLOWED_AUTH_PROVIDERS.toLocaleUpperCase(), value: getConfiguredSSOProviders(),
}, },
]; ];
@@ -130,6 +130,19 @@ export class InfraConfigService implements OnModuleInit {
}; };
} }
/**
* Get all the InfraConfigs as map
* @returns InfraConfig map
*/
async getInfraConfigsMap() {
const infraConfigs = await this.prisma.infraConfig.findMany();
const infraConfigMap: Record<string, string> = {};
infraConfigs.forEach((config) => {
infraConfigMap[config.name] = config.value;
});
return infraConfigMap;
}
/** /**
* Update InfraConfig by name * Update InfraConfig by name
* @param name Name of the InfraConfig * @param name Name of the InfraConfig
@@ -187,30 +200,24 @@ export class InfraConfigService implements OnModuleInit {
/** /**
* Check if the service is configured or not * Check if the service is configured or not
* @param service Service can be Auth Provider, Mailer, Audit Log etc. * @param service Service can be Auth Provider, Mailer, Audit Log etc.
* @param configMap Map of all the infra configs
* @returns Either true or false * @returns Either true or false
*/ */
isServiceConfigured(service: AuthProvider) { isServiceConfigured(
service: AuthProvider,
configMap: Record<string, string>,
) {
switch (service) { switch (service) {
case AuthProvider.GOOGLE: case AuthProvider.GOOGLE:
return ( return configMap.GOOGLE_CLIENT_ID && configMap.GOOGLE_CLIENT_SECRET;
this.configService.get<string>('INFRA.GOOGLE_CLIENT_ID') &&
this.configService.get<string>('INFRA.GOOGLE_CLIENT_SECRET')
);
case AuthProvider.GITHUB: case AuthProvider.GITHUB:
return ( return configMap.GITHUB_CLIENT_ID && configMap.GITHUB_CLIENT_SECRET;
this.configService.get<string>('INFRA.GITHUB_CLIENT_ID') &&
!this.configService.get<string>('INFRA.GITHUB_CLIENT_SECRET')
);
case AuthProvider.MICROSOFT: case AuthProvider.MICROSOFT:
return ( return (
this.configService.get<string>('INFRA.MICROSOFT_CLIENT_ID') && configMap.MICROSOFT_CLIENT_ID && configMap.MICROSOFT_CLIENT_SECRET
!this.configService.get<string>('INFRA.MICROSOFT_CLIENT_SECRET')
); );
case AuthProvider.EMAIL: case AuthProvider.EMAIL:
return ( return configMap.MAILER_SMTP_URL && configMap.MAILER_ADDRESS_FROM;
this.configService.get<string>('INFRA.MAILER_SMTP_URL') &&
this.configService.get<string>('INFRA.MAILER_ADDRESS_FROM')
);
default: default:
return false; return false;
} }
@@ -229,11 +236,11 @@ export class InfraConfigService implements OnModuleInit {
let updatedAuthProviders = allowedAuthProviders; let updatedAuthProviders = allowedAuthProviders;
for (let i = 0; i < providerInfo.length; i++) { const infraConfigMap = await this.getInfraConfigsMap();
const { provider, status } = providerInfo[i];
providerInfo.forEach(({ provider, status }) => {
if (status === ServiceStatus.ENABLE) { if (status === ServiceStatus.ENABLE) {
const isConfigured = this.isServiceConfigured(provider); const isConfigured = this.isServiceConfigured(provider, infraConfigMap);
if (!isConfigured) { if (!isConfigured) {
throwErr(INFRA_CONFIG_SERVICE_NOT_CONFIGURED); throwErr(INFRA_CONFIG_SERVICE_NOT_CONFIGURED);
} }
@@ -243,7 +250,7 @@ export class InfraConfigService implements OnModuleInit {
(p) => p !== provider, (p) => p !== provider,
); );
} }
} });
updatedAuthProviders = [...new Set(updatedAuthProviders)]; updatedAuthProviders = [...new Set(updatedAuthProviders)];
@@ -342,7 +349,7 @@ export class InfraConfigService implements OnModuleInit {
if (!isValidUrl) return E.left(INFRA_CONFIG_INVALID_INPUT); if (!isValidUrl) return E.left(INFRA_CONFIG_INVALID_INPUT);
break; break;
case InfraConfigEnumForClient.MAILER_ADDRESS_FROM: case InfraConfigEnumForClient.MAILER_ADDRESS_FROM:
const isValidEmail = validateEmail(infraConfigs[i].value); const isValidEmail = validateSMTPEmail(infraConfigs[i].value);
if (!isValidEmail) return E.left(INFRA_CONFIG_INVALID_INPUT); if (!isValidEmail) return E.left(INFRA_CONFIG_INVALID_INPUT);
break; break;
case InfraConfigEnumForClient.GOOGLE_CLIENT_ID: case InfraConfigEnumForClient.GOOGLE_CLIENT_ID:

View File

@@ -17,6 +17,7 @@ async function bootstrap() {
console.log(`Port: ${configService.get('PORT')}`); console.log(`Port: ${configService.get('PORT')}`);
checkEnvironmentAuthProvider( checkEnvironmentAuthProvider(
configService.get('INFRA.VITE_ALLOWED_AUTH_PROVIDERS') ??
configService.get('VITE_ALLOWED_AUTH_PROVIDERS'), configService.get('VITE_ALLOWED_AUTH_PROVIDERS'),
); );

View File

@@ -131,6 +131,28 @@ export const validateEmail = (email: string) => {
).test(email); ).test(email);
}; };
// Regular expressions for supported address object formats by nodemailer
// check out for more info https://nodemailer.com/message/addresses
const emailRegex1 = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
const emailRegex2 =
/^[\w\s]* <([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})>$/;
const emailRegex3 =
/^"[\w\s]+" <([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})>$/;
/**
* Checks to see if the SMTP email is valid or not
* @param email
* @returns A Boolean depending on the format of the email
*/
export const validateSMTPEmail = (email: string) => {
// Check if the input matches any of the formats
return (
emailRegex1.test(email) ||
emailRegex2.test(email) ||
emailRegex3.test(email)
);
};
/** /**
* Checks to see if the URL is valid or not * Checks to see if the URL is valid or not
* @param url The URL to validate * @param url The URL to validate