import Button from '@app/components/Common/Button'; import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import SensitiveInput from '@app/components/Common/SensitiveInput'; import SettingsBadge from '@app/components/Settings/SettingsBadge'; import globalMessages from '@app/i18n/globalMessages'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline'; import { Field, Form, Formik } from 'formik'; import { useState } from 'react'; import { useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR, { mutate } from 'swr'; import * as Yup from 'yup'; const messages = defineMessages('components.Settings.Notifications', { validationSmtpHostRequired: 'You must provide a valid hostname or IP address', validationSmtpPortRequired: 'You must provide a valid port number', agentenabled: 'Enable Agent', userEmailRequired: 'Require user email', emailsender: 'Sender Address', smtpHost: 'SMTP Host', smtpPort: 'SMTP Port', encryption: 'Encryption Method', encryptionTip: 'In most cases, Implicit TLS uses port 465 and STARTTLS uses port 587', encryptionNone: 'None', encryptionDefault: 'Use STARTTLS if available', encryptionOpportunisticTls: 'Always use STARTTLS', encryptionImplicitTls: 'Use Implicit TLS', authUser: 'SMTP Username', authPass: 'SMTP Password', emailsettingssaved: 'Email notification settings saved successfully!', emailsettingsfailed: 'Email notification settings failed to save.', toastEmailTestSending: 'Sending email test notification…', toastEmailTestSuccess: 'Email test notification sent!', toastEmailTestFailed: 'Email test notification failed to send.', allowselfsigned: 'Allow Self-Signed Certificates', senderName: 'Sender Name', validationEmail: 'You must provide a valid email address', pgpPrivateKey: 'PGP Private Key', pgpPrivateKeyTip: 'Sign encrypted email messages using OpenPGP', validationPgpPrivateKey: 'You must provide a valid PGP private key', pgpPassword: 'PGP Password', pgpPasswordTip: 'Sign encrypted email messages using OpenPGP', validationPgpPassword: 'You must provide a PGP password', }); export function OpenPgpLink(msg: React.ReactNode) { return ( {msg} ); } const NotificationsEmail = () => { const intl = useIntl(); const { addToast, removeToast } = useToasts(); const [isTesting, setIsTesting] = useState(false); const { data, error, mutate: revalidate, } = useSWR('/api/v1/settings/notifications/email'); const NotificationsEmailSchema = Yup.object().shape( { emailFrom: Yup.string() .when('enabled', { is: true, then: Yup.string() .nullable() .required(intl.formatMessage(messages.validationEmail)), otherwise: Yup.string().nullable(), }) .email(intl.formatMessage(messages.validationEmail)), smtpHost: Yup.string() .when('enabled', { is: true, then: Yup.string() .nullable() .required(intl.formatMessage(messages.validationSmtpHostRequired)), otherwise: Yup.string().nullable(), }) .matches( /^(((([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])):((([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))@)?(([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])$/i, intl.formatMessage(messages.validationSmtpHostRequired) ), smtpPort: Yup.number().when('enabled', { is: true, then: Yup.number() .nullable() .required(intl.formatMessage(messages.validationSmtpPortRequired)), otherwise: Yup.number().nullable(), }), pgpPrivateKey: Yup.string() .when('pgpPassword', { is: (value: unknown) => !!value, then: Yup.string() .nullable() .required(intl.formatMessage(messages.validationPgpPrivateKey)), otherwise: Yup.string().nullable(), }) .matches( /-----BEGIN PGP PRIVATE KEY BLOCK-----.+-----END PGP PRIVATE KEY BLOCK-----/, intl.formatMessage(messages.validationPgpPrivateKey) ), pgpPassword: Yup.string().when('pgpPrivateKey', { is: (value: unknown) => !!value, then: Yup.string() .nullable() .required(intl.formatMessage(messages.validationPgpPassword)), otherwise: Yup.string().nullable(), }), }, [['pgpPrivateKey', 'pgpPassword']] ); if (!data && !error) { return ; } return ( { try { const res = await fetch('/api/v1/settings/notifications/email', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ enabled: values.enabled, options: { userEmailRequired: values.userEmailRequired, emailFrom: values.emailFrom, smtpHost: values.smtpHost, smtpPort: Number(values.smtpPort), secure: values.encryption === 'implicit', ignoreTls: values.encryption === 'none', requireTls: values.encryption === 'opportunistic', authUser: values.authUser, authPass: values.authPass, allowSelfSigned: values.allowSelfSigned, senderName: values.senderName, pgpPrivateKey: values.pgpPrivateKey, pgpPassword: values.pgpPassword, }, }), }); if (!res.ok) throw new Error(); mutate('/api/v1/settings/public'); addToast(intl.formatMessage(messages.emailsettingssaved), { appearance: 'success', autoDismiss: true, }); } catch (e) { addToast(intl.formatMessage(messages.emailsettingsfailed), { appearance: 'error', autoDismiss: true, }); } finally { revalidate(); } }} > {({ errors, touched, isSubmitting, values, isValid }) => { const testSettings = async () => { setIsTesting(true); let toastId: string | undefined; try { addToast( intl.formatMessage(messages.toastEmailTestSending), { autoDismiss: false, appearance: 'info', }, (id) => { toastId = id; } ); const res = await fetch( '/api/v1/settings/notifications/email/test', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ enabled: true, options: { emailFrom: values.emailFrom, smtpHost: values.smtpHost, smtpPort: Number(values.smtpPort), secure: values.encryption === 'implicit', ignoreTls: values.encryption === 'none', requireTls: values.encryption === 'opportunistic', authUser: values.authUser, authPass: values.authPass, senderName: values.senderName, pgpPrivateKey: values.pgpPrivateKey, pgpPassword: values.pgpPassword, }, }), } ); if (!res.ok) throw new Error(); if (toastId) { removeToast(toastId); } addToast(intl.formatMessage(messages.toastEmailTestSuccess), { autoDismiss: true, appearance: 'success', }); } catch (e) { if (toastId) { removeToast(toastId); } addToast(intl.formatMessage(messages.toastEmailTestFailed), { autoDismiss: true, appearance: 'error', }); } finally { setIsTesting(false); } }; return (
{errors.emailFrom && touched.emailFrom && typeof errors.emailFrom === 'string' && (
{errors.emailFrom}
)}
{errors.smtpHost && touched.smtpHost && typeof errors.smtpHost === 'string' && (
{errors.smtpHost}
)}
{errors.smtpPort && touched.smtpPort && typeof errors.smtpPort === 'string' && (
{errors.smtpPort}
)}
{errors.pgpPrivateKey && touched.pgpPrivateKey && typeof errors.pgpPrivateKey === 'string' && (
{errors.pgpPrivateKey}
)}
{errors.pgpPassword && touched.pgpPassword && typeof errors.pgpPassword === 'string' && (
{errors.pgpPassword}
)}
); }}
); }; export default NotificationsEmail;