import { BeakerIcon, SaveIcon } from '@heroicons/react/outline'; import axios from 'axios'; import { Field, Form, Formik } from 'formik'; import React, { useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR, { mutate } from 'swr'; import * as Yup from 'yup'; import globalMessages from '../../../i18n/globalMessages'; import Badge from '../../Common/Badge'; import Button from '../../Common/Button'; import LoadingSpinner from '../../Common/LoadingSpinner'; import SensitiveInput from '../../Common/SensitiveInput'; const messages = defineMessages({ validationSmtpHostRequired: 'You must provide a valid hostname or IP address', validationSmtpPortRequired: 'You must provide a valid port number', agentenabled: 'Enable Agent', 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: string): JSX.Element { return ( {msg} ); } const NotificationsEmail: React.FC = () => { 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-----/s, 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 { await axios.post('/api/v1/settings/notifications/email', { enabled: values.enabled, 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, allowSelfSigned: values.allowSelfSigned, senderName: values.senderName, pgpPrivateKey: values.pgpPrivateKey, pgpPassword: values.pgpPassword, }, }); 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; } ); await axios.post('/api/v1/settings/notifications/email/test', { 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 (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 ( {intl.formatMessage(messages.agentenabled)} * {intl.formatMessage(messages.senderName)} {intl.formatMessage(messages.emailsender)} * {errors.emailFrom && touched.emailFrom && ( {errors.emailFrom} )} {intl.formatMessage(messages.smtpHost)} * {errors.smtpHost && touched.smtpHost && ( {errors.smtpHost} )} {intl.formatMessage(messages.smtpPort)} * {errors.smtpPort && touched.smtpPort && ( {errors.smtpPort} )} {intl.formatMessage(messages.encryption)} * {intl.formatMessage(messages.encryptionTip)} {intl.formatMessage(messages.encryptionNone)} {intl.formatMessage(messages.encryptionDefault)} {intl.formatMessage(messages.encryptionOpportunisticTls)} {intl.formatMessage(messages.encryptionImplicitTls)} {intl.formatMessage(messages.allowselfsigned)} {intl.formatMessage(messages.authUser)} {intl.formatMessage(messages.authPass)} {intl.formatMessage(messages.pgpPrivateKey)} {intl.formatMessage(globalMessages.advanced)} {intl.formatMessage(messages.pgpPrivateKeyTip, { OpenPgpLink: OpenPgpLink, })} {errors.pgpPrivateKey && touched.pgpPrivateKey && ( {errors.pgpPrivateKey} )} {intl.formatMessage(messages.pgpPassword)} {intl.formatMessage(globalMessages.advanced)} {intl.formatMessage(messages.pgpPasswordTip, { OpenPgpLink: OpenPgpLink, })} {errors.pgpPassword && touched.pgpPassword && ( {errors.pgpPassword} )} { e.preventDefault(); testSettings(); }} > {isTesting ? intl.formatMessage(globalMessages.testing) : intl.formatMessage(globalMessages.test)} {isSubmitting ? intl.formatMessage(globalMessages.saving) : intl.formatMessage(globalMessages.save)} ); }} ); }; export default NotificationsEmail;