feat: add MAILER_SMTP_IGNORE_TLS and optional SMTP auth (#5972)
Co-authored-by: nivedin <nivedinp@gmail.com> Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
parent
da3b8c5d37
commit
06bdd7ca6a
12 changed files with 194 additions and 41 deletions
|
|
@ -110,6 +110,10 @@ export class SaveOnboardingConfigRequest {
|
|||
@IsOptional()
|
||||
@IsString()
|
||||
[InfraConfigEnum.MAILER_TLS_REJECT_UNAUTHORIZED]: string;
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
[InfraConfigEnum.MAILER_SMTP_IGNORE_TLS]: string;
|
||||
}
|
||||
|
||||
export class SaveOnboardingConfigResponse {
|
||||
|
|
@ -197,4 +201,7 @@ export class GetOnboardingConfigResponse {
|
|||
@ApiProperty()
|
||||
@Expose()
|
||||
[InfraConfigEnum.MAILER_TLS_REJECT_UNAUTHORIZED]: string;
|
||||
@ApiProperty()
|
||||
@Expose()
|
||||
[InfraConfigEnum.MAILER_SMTP_IGNORE_TLS]: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,8 +71,6 @@ export function getAuthProviderRequiredKeys(
|
|||
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,
|
||||
]
|
||||
|
|
@ -240,6 +238,11 @@ export async function getDefaultInfraConfigs(): Promise<DefaultInfraConfig[]> {
|
|||
value: null,
|
||||
isEncrypted: false,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.MAILER_SMTP_IGNORE_TLS,
|
||||
value: 'false',
|
||||
isEncrypted: false,
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.GOOGLE_CLIENT_ID,
|
||||
value: null,
|
||||
|
|
|
|||
|
|
@ -240,6 +240,10 @@ export class InfraConfigService implements OnModuleInit, OnModuleDestroy {
|
|||
const isValidate = this.validateEnvValues(infraConfigs);
|
||||
if (E.isLeft(isValidate)) return E.left(isValidate.left);
|
||||
|
||||
// Validate SMTP credentials pair against effective post-update state
|
||||
const smtpPairCheck = await this.validateSmtpCredentialPair(infraConfigs);
|
||||
if (E.isLeft(smtpPairCheck)) return E.left(smtpPairCheck.left);
|
||||
|
||||
try {
|
||||
const dbInfraConfig = await this.prisma.infraConfig.findMany({
|
||||
select: { name: true, isEncrypted: true },
|
||||
|
|
@ -310,8 +314,6 @@ export class InfraConfigService implements OnModuleInit, OnModuleDestroy {
|
|||
configMap.MAILER_SMTP_HOST &&
|
||||
configMap.MAILER_SMTP_PORT &&
|
||||
configMap.MAILER_SMTP_SECURE &&
|
||||
configMap.MAILER_SMTP_USER &&
|
||||
configMap.MAILER_SMTP_PASSWORD &&
|
||||
configMap.MAILER_TLS_REJECT_UNAUTHORIZED &&
|
||||
configMap.MAILER_ADDRESS_FROM
|
||||
);
|
||||
|
|
@ -656,6 +658,58 @@ export class InfraConfigService implements OnModuleInit, OnModuleDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that SMTP user and password are both provided or both empty,
|
||||
* checking the effective post-update state (incoming merged with DB).
|
||||
*/
|
||||
private async validateSmtpCredentialPair(
|
||||
infraConfigs: { name: InfraConfigEnum; value: string }[],
|
||||
) {
|
||||
const incoming = new Map(infraConfigs.map((c) => [c.name, c.value]));
|
||||
const smtpKeys = [
|
||||
InfraConfigEnum.MAILER_SMTP_USER,
|
||||
InfraConfigEnum.MAILER_SMTP_PASSWORD,
|
||||
];
|
||||
|
||||
if (!smtpKeys.some((key) => incoming.has(key))) {
|
||||
return E.right(true);
|
||||
}
|
||||
|
||||
const missingKeys = smtpKeys.filter((key) => !incoming.has(key));
|
||||
|
||||
const dbRows =
|
||||
missingKeys.length === 0
|
||||
? []
|
||||
: await this.prisma.infraConfig.findMany({
|
||||
where: { name: { in: missingKeys } },
|
||||
select: { name: true, value: true, isEncrypted: true },
|
||||
});
|
||||
|
||||
const dbValues = new Map(
|
||||
dbRows.map((row) => [
|
||||
row.name,
|
||||
row.value ? (row.isEncrypted ? decrypt(row.value) : row.value) : '',
|
||||
]),
|
||||
);
|
||||
|
||||
const smtpUser =
|
||||
incoming.get(InfraConfigEnum.MAILER_SMTP_USER) ??
|
||||
dbValues.get(InfraConfigEnum.MAILER_SMTP_USER) ??
|
||||
'';
|
||||
|
||||
const smtpPass =
|
||||
incoming.get(InfraConfigEnum.MAILER_SMTP_PASSWORD) ??
|
||||
dbValues.get(InfraConfigEnum.MAILER_SMTP_PASSWORD) ??
|
||||
'';
|
||||
|
||||
const hasUser = smtpUser.trim() !== '';
|
||||
const hasPass = smtpPass.trim() !== '';
|
||||
|
||||
return hasUser !== hasPass
|
||||
? E.left(INFRA_CONFIG_INVALID_INPUT)
|
||||
: E.right(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the values of the InfraConfigs
|
||||
*/
|
||||
|
|
@ -678,6 +732,7 @@ export class InfraConfigService implements OnModuleInit, OnModuleDestroy {
|
|||
case InfraConfigEnum.MAILER_USE_CUSTOM_CONFIGS:
|
||||
case InfraConfigEnum.MAILER_SMTP_SECURE:
|
||||
case InfraConfigEnum.MAILER_TLS_REJECT_UNAUTHORIZED:
|
||||
case InfraConfigEnum.MAILER_SMTP_IGNORE_TLS:
|
||||
if (value !== 'true' && value !== 'false') return fail();
|
||||
break;
|
||||
|
||||
|
|
@ -702,8 +757,6 @@ export class InfraConfigService implements OnModuleInit, OnModuleDestroy {
|
|||
|
||||
case InfraConfigEnum.MAILER_SMTP_HOST:
|
||||
case InfraConfigEnum.MAILER_SMTP_PORT:
|
||||
case InfraConfigEnum.MAILER_SMTP_USER:
|
||||
case InfraConfigEnum.MAILER_SMTP_PASSWORD:
|
||||
case InfraConfigEnum.GOOGLE_CLIENT_ID:
|
||||
case InfraConfigEnum.GOOGLE_CLIENT_SECRET:
|
||||
case InfraConfigEnum.GOOGLE_SCOPE:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
import { TransportType } from '@nestjs-modules/mailer/dist/interfaces/mailer-options.interface';
|
||||
import {
|
||||
MAILER_SMTP_PASSWORD_UNDEFINED,
|
||||
MAILER_SMTP_URL_UNDEFINED,
|
||||
MAILER_SMTP_USER_UNDEFINED,
|
||||
} from 'src/errors';
|
||||
import { MAILER_SMTP_URL_UNDEFINED } from 'src/errors';
|
||||
import { throwErr } from 'src/utils';
|
||||
|
||||
function isSMTPCustomConfigsEnabled(value) {
|
||||
|
|
@ -24,17 +20,28 @@ export function getTransportOption(env): TransportType {
|
|||
return env.INFRA.MAILER_SMTP_URL ?? throwErr(MAILER_SMTP_URL_UNDEFINED);
|
||||
} else {
|
||||
console.log('Using advanced mailer configuration');
|
||||
|
||||
const smtpUser = env.INFRA.MAILER_SMTP_USER?.trim() || undefined;
|
||||
const smtpPass = env.INFRA.MAILER_SMTP_PASSWORD?.trim() || undefined;
|
||||
|
||||
// Both credentials must be provided together or both omitted
|
||||
const hasUser = !!smtpUser;
|
||||
const hasPass = !!smtpPass;
|
||||
if (hasUser !== hasPass) {
|
||||
throw new Error(
|
||||
'SMTP auth requires both MAILER_SMTP_USER and MAILER_SMTP_PASSWORD. Provide both or leave both empty for unauthenticated relay.',
|
||||
);
|
||||
}
|
||||
|
||||
const auth =
|
||||
smtpUser && smtpPass ? { user: smtpUser, pass: smtpPass } : undefined;
|
||||
|
||||
return {
|
||||
host: env.INFRA.MAILER_SMTP_HOST,
|
||||
port: +env.INFRA.MAILER_SMTP_PORT,
|
||||
secure: env.INFRA.MAILER_SMTP_SECURE === 'true',
|
||||
auth: {
|
||||
user:
|
||||
env.INFRA.MAILER_SMTP_USER ?? throwErr(MAILER_SMTP_USER_UNDEFINED),
|
||||
pass:
|
||||
env.INFRA.MAILER_SMTP_PASSWORD ??
|
||||
throwErr(MAILER_SMTP_PASSWORD_UNDEFINED),
|
||||
},
|
||||
...(auth && { auth }),
|
||||
ignoreTLS: env.INFRA.MAILER_SMTP_IGNORE_TLS === 'true',
|
||||
tls: {
|
||||
rejectUnauthorized: env.INFRA.MAILER_TLS_REJECT_UNAUTHORIZED === 'true',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export enum InfraConfigEnum {
|
|||
MAILER_SMTP_USER = 'MAILER_SMTP_USER',
|
||||
MAILER_SMTP_PASSWORD = 'MAILER_SMTP_PASSWORD',
|
||||
MAILER_TLS_REJECT_UNAUTHORIZED = 'MAILER_TLS_REJECT_UNAUTHORIZED',
|
||||
MAILER_SMTP_IGNORE_TLS = 'MAILER_SMTP_IGNORE_TLS',
|
||||
|
||||
GOOGLE_CLIENT_ID = 'GOOGLE_CLIENT_ID',
|
||||
GOOGLE_CLIENT_SECRET = 'GOOGLE_CLIENT_SECRET',
|
||||
|
|
|
|||
|
|
@ -74,22 +74,24 @@
|
|||
},
|
||||
"load_error": "Unable to load server configurations",
|
||||
"mail_configs": {
|
||||
"address_from": "MAILER FROM ADDRESS",
|
||||
"address_from": "Mailer From Address",
|
||||
"custom_smtp_configs": "Use Custom SMTP Configurations",
|
||||
"description": " Configure the smtp configurations",
|
||||
"enable_email_auth": "Enable Email based authentication",
|
||||
"enable_smtp": "Enable SMTP",
|
||||
"host": "MAILER HOST",
|
||||
"host": "SMTP Host",
|
||||
"input_validation": "SMTP URL should start with smtp(s)://",
|
||||
"password": "MAILER PASSWORD",
|
||||
"port": "MAILER PORT",
|
||||
"secure": "MAILER SECURE",
|
||||
"smtp_url": "MAILER SMTP URL",
|
||||
"tls_reject_unauthorized": "TLS REJECT UNAUTHORIZED",
|
||||
"password": "SMTP Password",
|
||||
"port": "SMTP Port",
|
||||
"secure": "SMTP Secure",
|
||||
"smtp_url": "SMTP URL",
|
||||
"ignore_tls": "SMTP Ignore TLS",
|
||||
"tls_reject_unauthorized": "TLS Reject Unauthorized",
|
||||
"title": "SMTP Configurations",
|
||||
"smtp_auth_incomplete": "SMTP username and password must both be provided or both left empty",
|
||||
"toggle_failure": "Failed to toggle smtp!!",
|
||||
"update_failure": "Failed to update smtp configurations!!",
|
||||
"user": "MAILER USER"
|
||||
"user": "SMTP User"
|
||||
},
|
||||
"history_configs": {
|
||||
"description": "Disable or enable tracking history of sent requests from the Hoppscotch app",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,13 @@
|
|||
{{ smtp.SMTP_SECURE.text }}
|
||||
</HoppSmartCheckbox>
|
||||
|
||||
<HoppSmartCheckbox
|
||||
:on="smtp.SMTP_IGNORE_TLS.enabled"
|
||||
@change="toggleConfig('SMTP_IGNORE_TLS')"
|
||||
>
|
||||
{{ smtp.SMTP_IGNORE_TLS.text }}
|
||||
</HoppSmartCheckbox>
|
||||
|
||||
<HoppSmartCheckbox
|
||||
:on="smtp.TLS_REJECT_UNAUTHORIZED.enabled"
|
||||
@change="toggleConfig('TLS_REJECT_UNAUTHORIZED')"
|
||||
|
|
@ -101,55 +108,61 @@ const smtp = computed<MailerConfig>(() => {
|
|||
return {
|
||||
SMTP_URL: {
|
||||
id: 'MAILER_SMTP_URL',
|
||||
text: 'SMTP URL',
|
||||
text: t('configs.mail_configs.smtp_url'),
|
||||
value: cfg.MAILER_SMTP_URL,
|
||||
enabled: !isCustom,
|
||||
},
|
||||
ADDRESS_FROM: {
|
||||
id: 'MAILER_ADDRESS_FROM',
|
||||
text: 'Address From',
|
||||
text: t('configs.mail_configs.address_from'),
|
||||
value: cfg.MAILER_ADDRESS_FROM,
|
||||
enabled: true,
|
||||
},
|
||||
USE_CUSTOM_CONFIGS: {
|
||||
id: 'MAILER_USE_CUSTOM_CONFIGS',
|
||||
text: 'Use Custom Configs',
|
||||
text: t('configs.mail_configs.custom_smtp_configs'),
|
||||
value: cfg.MAILER_USE_CUSTOM_CONFIGS,
|
||||
enabled: isCustom,
|
||||
},
|
||||
SMTP_SECURE: {
|
||||
id: 'MAILER_SMTP_SECURE',
|
||||
text: 'SMTP Secure',
|
||||
text: t('configs.mail_configs.secure'),
|
||||
value: cfg.MAILER_SMTP_SECURE,
|
||||
enabled: isCustom && cfg.MAILER_SMTP_SECURE === 'true',
|
||||
},
|
||||
SMTP_IGNORE_TLS: {
|
||||
id: 'MAILER_SMTP_IGNORE_TLS',
|
||||
text: t('configs.mail_configs.ignore_tls'),
|
||||
value: cfg.MAILER_SMTP_IGNORE_TLS,
|
||||
enabled: isCustom && cfg.MAILER_SMTP_IGNORE_TLS === 'true',
|
||||
},
|
||||
TLS_REJECT_UNAUTHORIZED: {
|
||||
id: 'MAILER_TLS_REJECT_UNAUTHORIZED',
|
||||
text: 'TLS Reject Unauthorized',
|
||||
text: t('configs.mail_configs.tls_reject_unauthorized'),
|
||||
value: cfg.MAILER_TLS_REJECT_UNAUTHORIZED,
|
||||
enabled: isCustom && cfg.MAILER_TLS_REJECT_UNAUTHORIZED === 'true',
|
||||
},
|
||||
SMTP_USER: {
|
||||
id: 'MAILER_SMTP_USER',
|
||||
text: 'SMTP User',
|
||||
text: t('configs.mail_configs.user'),
|
||||
value: cfg.MAILER_SMTP_USER,
|
||||
enabled: isCustom,
|
||||
},
|
||||
SMTP_PASSWORD: {
|
||||
id: 'MAILER_SMTP_PASSWORD',
|
||||
text: 'SMTP Password',
|
||||
text: t('configs.mail_configs.password'),
|
||||
value: cfg.MAILER_SMTP_PASSWORD,
|
||||
enabled: isCustom,
|
||||
},
|
||||
SMTP_HOST: {
|
||||
id: 'MAILER_SMTP_HOST',
|
||||
text: 'SMTP Host',
|
||||
text: t('configs.mail_configs.host'),
|
||||
value: cfg.MAILER_SMTP_HOST,
|
||||
enabled: isCustom,
|
||||
},
|
||||
SMTP_PORT: {
|
||||
id: 'MAILER_SMTP_PORT',
|
||||
text: 'SMTP Port',
|
||||
text: t('configs.mail_configs.port'),
|
||||
value: cfg.MAILER_SMTP_PORT,
|
||||
enabled: isCustom,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -171,6 +171,10 @@ const smtpConfigFields = reactive<Field[]>([
|
|||
name: t('configs.mail_configs.secure'),
|
||||
key: 'mailer_smtp_secure',
|
||||
},
|
||||
{
|
||||
name: t('configs.mail_configs.ignore_tls'),
|
||||
key: 'mailer_smtp_ignore_tls',
|
||||
},
|
||||
{
|
||||
name: t('configs.mail_configs.tls_reject_unauthorized'),
|
||||
key: 'mailer_tls_reject_unauthorized',
|
||||
|
|
@ -200,6 +204,7 @@ const fieldCondition = (field: Field) => {
|
|||
'mailer_smtp_user',
|
||||
'mailer_smtp_password',
|
||||
'mailer_smtp_secure',
|
||||
'mailer_smtp_ignore_tls',
|
||||
'mailer_tls_reject_unauthorized',
|
||||
];
|
||||
const basicFields = ['mailer_smtp_url'];
|
||||
|
|
@ -216,7 +221,11 @@ const fieldCondition = (field: Field) => {
|
|||
};
|
||||
|
||||
const isCheckboxField = (field: Field) => {
|
||||
const checkboxKeys = ['mailer_smtp_secure', 'mailer_tls_reject_unauthorized'];
|
||||
const checkboxKeys = [
|
||||
'mailer_smtp_secure',
|
||||
'mailer_smtp_ignore_tls',
|
||||
'mailer_tls_reject_unauthorized',
|
||||
];
|
||||
return checkboxKeys.includes(field.key);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -137,6 +137,8 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) {
|
|||
),
|
||||
mailer_smtp_secure:
|
||||
getFieldValue(InfraConfigEnum.MailerSmtpSecure) === 'true',
|
||||
mailer_smtp_ignore_tls:
|
||||
getFieldValue(InfraConfigEnum.MailerSmtpIgnoreTls) === 'true',
|
||||
mailer_tls_reject_unauthorized:
|
||||
getFieldValue(InfraConfigEnum.MailerTlsRejectUnauthorized) ===
|
||||
'true',
|
||||
|
|
@ -273,8 +275,10 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) {
|
|||
if (section.name === 'email') {
|
||||
const { mailer_use_custom_configs, ...otherFields } = section.fields;
|
||||
|
||||
// SMTP user and password are optional as a pair (both or neither)
|
||||
const optionalMailerKeys = ['mailer_smtp_user', 'mailer_smtp_password'];
|
||||
const excludeKeys = mailer_use_custom_configs
|
||||
? ['mailer_smtp_url']
|
||||
? ['mailer_smtp_url', ...optionalMailerKeys]
|
||||
: [
|
||||
'mailer_smtp_host',
|
||||
'mailer_smtp_port',
|
||||
|
|
@ -285,7 +289,8 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) {
|
|||
return (
|
||||
section.enabled &&
|
||||
Object.entries(otherFields).some(
|
||||
([key, value]) => isFieldEmpty(value) && !excludeKeys.includes(key)
|
||||
([key, value]) =>
|
||||
isFieldEmpty(value) && !excludeKeys.includes(key)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
@ -312,6 +317,18 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) {
|
|||
return hasSectionWithEmptyFields;
|
||||
};
|
||||
|
||||
const hasPartialSmtpCredentials = (config: ServerConfigs): boolean => {
|
||||
if (!config.mailConfigs.enabled) return false;
|
||||
|
||||
const fields = config.mailConfigs.fields;
|
||||
if (!fields.mailer_use_custom_configs) return false;
|
||||
|
||||
const hasUser = fields.mailer_smtp_user.trim() !== '';
|
||||
const hasPass = fields.mailer_smtp_password.trim() !== '';
|
||||
|
||||
return hasUser !== hasPass;
|
||||
};
|
||||
|
||||
// Extract the mail config fields (excluding the custom mail config fields)
|
||||
const mailConfigFields = Object.fromEntries(
|
||||
Object.entries(updatedConfigs?.mailConfigs.fields ?? {}).filter(([key]) => {
|
||||
|
|
@ -659,5 +676,6 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) {
|
|||
infraConfigsError,
|
||||
allowedAuthProvidersError,
|
||||
AreAnyConfigFieldsEmpty,
|
||||
hasPartialSmtpCredentials,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export type MailerConfigKeys =
|
|||
| 'SMTP_SECURE'
|
||||
| 'SMTP_USER'
|
||||
| 'SMTP_PASSWORD'
|
||||
| 'SMTP_IGNORE_TLS'
|
||||
| 'TLS_REJECT_UNAUTHORIZED';
|
||||
|
||||
export type Configs = {
|
||||
|
|
@ -89,6 +90,7 @@ function mapMailerConfigs(
|
|||
MAILER_SMTP_SECURE: configs.MAILER_SMTP_SECURE || 'false',
|
||||
MAILER_SMTP_USER: configs.MAILER_SMTP_USER ?? '',
|
||||
MAILER_SMTP_PASSWORD: configs.MAILER_SMTP_PASSWORD ?? '',
|
||||
MAILER_SMTP_IGNORE_TLS: configs.MAILER_SMTP_IGNORE_TLS || 'false',
|
||||
MAILER_TLS_REJECT_UNAUTHORIZED:
|
||||
configs.MAILER_TLS_REJECT_UNAUTHORIZED || 'false',
|
||||
};
|
||||
|
|
@ -229,6 +231,7 @@ export function useOnboardingConfigHandler() {
|
|||
'MAILER_ADDRESS_FROM',
|
||||
'MAILER_USE_CUSTOM_CONFIGS',
|
||||
'MAILER_SMTP_SECURE',
|
||||
'MAILER_SMTP_IGNORE_TLS',
|
||||
'MAILER_TLS_REJECT_UNAUTHORIZED',
|
||||
'MAILER_SMTP_ENABLE',
|
||||
].includes(key);
|
||||
|
|
@ -253,11 +256,21 @@ export function useOnboardingConfigHandler() {
|
|||
);
|
||||
|
||||
const neededKeys = filterNeededConfigs(relevantKeys);
|
||||
const allFilled = neededKeys.every((key) => configs[key]);
|
||||
// SMTP auth is optional (both blank = no-auth SMTP server).
|
||||
// These keys are excluded from mandatory "filled" checks, but are still
|
||||
// included in the returned payload even when empty so the backend can
|
||||
// explicitly clear previously stored credentials on re-visits.
|
||||
const optionalSmtpKeys = new Set([
|
||||
'MAILER_SMTP_USER',
|
||||
'MAILER_SMTP_PASSWORD',
|
||||
]);
|
||||
const allFilled = neededKeys.every(
|
||||
(key) => configs[key] || optionalSmtpKeys.has(key)
|
||||
);
|
||||
|
||||
if (!allFilled) {
|
||||
neededKeys.forEach((key) => {
|
||||
if (!configs[key])
|
||||
if (!configs[key] && !optionalSmtpKeys.has(key))
|
||||
toast.error(
|
||||
t('onboarding.please_fill_configurations', {
|
||||
fieldName: makeReadableKey(key),
|
||||
|
|
@ -267,11 +280,28 @@ export function useOnboardingConfigHandler() {
|
|||
return;
|
||||
}
|
||||
|
||||
// SMTP credentials must be provided together or both left empty.
|
||||
// Only enforce when custom SMTP mode is active (not simple URL mode).
|
||||
if (
|
||||
enabledConfigs.value.includes('MAILER') &&
|
||||
configs['MAILER_USE_CUSTOM_CONFIGS'] === 'true'
|
||||
) {
|
||||
const smtpUser = configs['MAILER_SMTP_USER']?.trim();
|
||||
const smtpPass = configs['MAILER_SMTP_PASSWORD']?.trim();
|
||||
|
||||
if (!!smtpUser !== !!smtpPass) {
|
||||
toast.error(t('configs.mail_configs.smtp_auth_incomplete'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow empty strings through for optional SMTP keys so the backend
|
||||
// receives an explicit clear rather than silently retaining old values
|
||||
return Object.fromEntries(
|
||||
Object.entries(configs).filter(
|
||||
([key, val]) =>
|
||||
enabledConfigs.value.includes(key.split('_')[0] as EnabledConfig) &&
|
||||
val
|
||||
(val || optionalSmtpKeys.has(key))
|
||||
)
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ export type ServerConfigs = {
|
|||
mailer_smtp_user: string;
|
||||
mailer_smtp_password: string;
|
||||
mailer_smtp_secure: boolean;
|
||||
mailer_smtp_ignore_tls: boolean;
|
||||
mailer_tls_reject_unauthorized: boolean;
|
||||
mailer_use_custom_configs: boolean;
|
||||
};
|
||||
|
|
@ -221,6 +222,10 @@ export const CUSTOM_MAIL_CONFIGS: Config[] = [
|
|||
name: InfraConfigEnum.MailerSmtpSecure,
|
||||
key: 'mailer_smtp_secure',
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.MailerSmtpIgnoreTls,
|
||||
key: 'mailer_smtp_ignore_tls',
|
||||
},
|
||||
{
|
||||
name: InfraConfigEnum.MailerTlsRejectUnauthorized,
|
||||
key: 'mailer_tls_reject_unauthorized',
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ const {
|
|||
fetchingAllowedAuthProviders,
|
||||
allowedAuthProvidersError,
|
||||
AreAnyConfigFieldsEmpty,
|
||||
hasPartialSmtpCredentials,
|
||||
} = useConfigHandler();
|
||||
|
||||
// Check if the configs have been updated
|
||||
|
|
@ -114,6 +115,10 @@ const triggerSaveChangesModal = () => {
|
|||
return toast.error(t('configs.input_empty'));
|
||||
}
|
||||
|
||||
if (workingConfigs.value && hasPartialSmtpCredentials(workingConfigs.value)) {
|
||||
return toast.error(t('configs.mail_configs.smtp_auth_incomplete'));
|
||||
}
|
||||
|
||||
if (hasInputValidationFailed.value) {
|
||||
return toast.error(t('configs.input_validation_error'));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue