diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index b08147eb..8c0c65db 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -353,5 +353,13 @@ "blocked_services_global": "Use global blocked services", "blocked_service": "Blocked service", "block_all": "Block all", - "unblock_all": "Unblock all" -} \ No newline at end of file + "unblock_all": "Unblock all", + "encryption_certificate_path": "Certificate path", + "encryption_certificate_path_notice": "To add a path to the certificate, clear the certificate chain textarea.", + "encryption_private_key_path": "Private key path", + "encryption_private_key_path_notice": "To add a path to the private key, clear the private key textarea.", + "encryption_certificates_source_path": "Set a certificates file path", + "encryption_certificates_source_content":"Paste the certificates contents", + "encryption_key_source_path": "Set a private key file", + "encryption_key_source_content": "Paste the private key contents" +} diff --git a/client/src/components/Settings/Encryption/CertificateStatus.js b/client/src/components/Settings/Encryption/CertificateStatus.js new file mode 100644 index 00000000..1ecc2742 --- /dev/null +++ b/client/src/components/Settings/Encryption/CertificateStatus.js @@ -0,0 +1,71 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { withNamespaces, Trans } from 'react-i18next'; +import format from 'date-fns/format'; + +import { EMPTY_DATE } from '../../../helpers/constants'; + +const CertificateStatus = ({ + validChain, + validCert, + subject, + issuer, + notAfter, + dnsNames, +}) => ( + +
+ encryption_status: +
+ +
+); + +CertificateStatus.propTypes = { + validChain: PropTypes.bool.isRequired, + validCert: PropTypes.bool.isRequired, + subject: PropTypes.string, + issuer: PropTypes.string, + notAfter: PropTypes.string, + dnsNames: PropTypes.string, +}; + +export default withNamespaces()(CertificateStatus); diff --git a/client/src/components/Settings/Encryption/Form.js b/client/src/components/Settings/Encryption/Form.js index 94e9923c..058cf918 100644 --- a/client/src/components/Settings/Encryption/Form.js +++ b/client/src/components/Settings/Encryption/Form.js @@ -1,14 +1,22 @@ -import React, { Fragment } from 'react'; +import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { Field, reduxForm, formValueSelector } from 'redux-form'; import { Trans, withNamespaces } from 'react-i18next'; import flow from 'lodash/flow'; -import format from 'date-fns/format'; -import { renderField, renderSelectField, toNumber, port, portTLS, isSafePort } from '../../../helpers/form'; -import { EMPTY_DATE } from '../../../helpers/constants'; +import { + renderField, + renderSelectField, + renderRadioField, + toNumber, + port, + portTLS, + isSafePort, +} from '../../../helpers/form'; import i18n from '../../../i18n'; +import KeyStatus from './KeyStatus'; +import CertificateStatus from './CertificateStatus'; const validate = (values) => { const errors = {}; @@ -27,6 +35,8 @@ const clearFields = (change, setTlsConfig, t) => { const fields = { private_key: '', certificate_chain: '', + private_key_path: '', + certificate_path: '', port_https: 443, port_dns_over_tls: 853, server_name: '', @@ -48,6 +58,8 @@ let Form = (props) => { isEnabled, certificateChain, privateKey, + certificatePath, + privateKeyPath, change, invalid, submitting, @@ -64,6 +76,8 @@ let Form = (props) => { subject, warning_validation, setTlsConfig, + certificateSource, + privateKeySource, } = props; const isSavingDisabled = @@ -71,10 +85,9 @@ let Form = (props) => { submitting || processingConfig || processingValidate || - (isEnabled && (!privateKey || !certificateChain)) || - (privateKey && !valid_key) || - (certificateChain && !valid_cert) || - (privateKey && certificateChain && !valid_pair); + !valid_key || + !valid_cert || + !valid_pair; return (
@@ -182,7 +195,7 @@ let Form = (props) => {
- -
- {certificateChain && ( - -
- encryption_status: -
-
    -
  • - {valid_chain ? ( - encryption_chain_valid - ) : ( - encryption_chain_invalid - )} -
  • - {valid_cert && ( - - {subject && ( -
  • - encryption_subject:  - {subject} -
  • - )} - {issuer && ( -
  • - encryption_issuer:  - {issuer} -
  • - )} - {not_after && not_after !== EMPTY_DATE && ( -
  • - encryption_expire:  - {format(not_after, 'YYYY-MM-DD HH:mm:ss')} -
  • - )} - {dns_names && ( -
  • - encryption_hostnames:  - {dns_names} -
  • - )} -
    - )} -
-
- )} + +
+
+ + +
+ + {certificateSource === 'content' && ( + + )} + {certificateSource === 'path' && ( + + )} +
+
+ {(certificateChain || certificatePath) && ( + + )}
-
+
- -
- {privateKey && ( - -
- encryption_status: -
-
    -
  • - {valid_key ? ( - - encryption_key_valid - - ) : ( - - encryption_key_invalid - - )} -
  • -
-
- )} + +
+
+ + +
+ + {privateKeySource === 'content' && ( + + )} + {privateKeySource === 'path' && ( + + )} +
+
+ {(privateKey || privateKeyPath) && ( + + )}
{warning_validation && ( @@ -334,6 +366,8 @@ Form.propTypes = { isEnabled: PropTypes.bool.isRequired, certificateChain: PropTypes.string.isRequired, privateKey: PropTypes.string.isRequired, + certificatePath: PropTypes.string.isRequired, + privateKeyPath: PropTypes.string.isRequired, change: PropTypes.func.isRequired, submitting: PropTypes.bool.isRequired, invalid: PropTypes.bool.isRequired, @@ -353,6 +387,8 @@ Form.propTypes = { subject: PropTypes.string, t: PropTypes.func.isRequired, setTlsConfig: PropTypes.func.isRequired, + certificateSource: PropTypes.string, + privateKeySource: PropTypes.string, }; const selector = formValueSelector('encryptionForm'); @@ -361,10 +397,18 @@ Form = connect((state) => { const isEnabled = selector(state, 'enabled'); const certificateChain = selector(state, 'certificate_chain'); const privateKey = selector(state, 'private_key'); + const certificatePath = selector(state, 'certificate_path'); + const privateKeyPath = selector(state, 'private_key_path'); + const certificateSource = selector(state, 'certificate_source'); + const privateKeySource = selector(state, 'key_source'); return { isEnabled, certificateChain, privateKey, + certificatePath, + privateKeyPath, + certificateSource, + privateKeySource, }; })(Form); diff --git a/client/src/components/Settings/Encryption/KeyStatus.js b/client/src/components/Settings/Encryption/KeyStatus.js new file mode 100644 index 00000000..08ae1df0 --- /dev/null +++ b/client/src/components/Settings/Encryption/KeyStatus.js @@ -0,0 +1,31 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { withNamespaces, Trans } from 'react-i18next'; + +const KeyStatus = ({ validKey, keyType }) => ( + +
+ encryption_status: +
+
    +
  • + {validKey ? ( + + encryption_key_valid + + ) : ( + + encryption_key_invalid + + )} +
  • +
+
+); + +KeyStatus.propTypes = { + validKey: PropTypes.bool.isRequired, + keyType: PropTypes.string.isRequired, +}; + +export default withNamespaces()(KeyStatus); diff --git a/client/src/components/Settings/Encryption/index.js b/client/src/components/Settings/Encryption/index.js index a8075dc1..06a3b73a 100644 --- a/client/src/components/Settings/Encryption/index.js +++ b/client/src/components/Settings/Encryption/index.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { withNamespaces } from 'react-i18next'; import debounce from 'lodash/debounce'; -import { DEBOUNCE_TIMEOUT } from '../../../helpers/constants'; +import { DEBOUNCE_TIMEOUT, ENCRYPTION_SOURCE } from '../../../helpers/constants'; import Form from './Form'; import Card from '../../ui/Card'; import PageTitle from '../../ui/PageTitle'; @@ -19,13 +19,45 @@ class Encryption extends Component { } handleFormSubmit = (values) => { - this.props.setTlsConfig(values); + const submitValues = this.getSubmitValues(values); + this.props.setTlsConfig(submitValues); }; handleFormChange = debounce((values) => { - this.props.validateTlsConfig(values); + const submitValues = this.getSubmitValues(values); + this.props.validateTlsConfig(submitValues); }, DEBOUNCE_TIMEOUT); + getInitialValues = (data) => { + const { certificate_chain, private_key } = data; + const certificate_source = certificate_chain ? 'content' : 'path'; + const key_source = private_key ? 'content' : 'path'; + + return { + ...data, + certificate_source, + key_source, + }; + }; + + getSubmitValues = (values) => { + const { certificate_source, key_source, ...config } = values; + + if (certificate_source === ENCRYPTION_SOURCE.PATH) { + config.certificate_chain = ''; + } else { + config.certificate_path = ''; + } + + if (values.key_source === ENCRYPTION_SOURCE.PATH) { + config.private_key = ''; + } else { + config.private_key_path = ''; + } + + return config; + }; + render() { const { encryption, t } = this.props; const { @@ -36,8 +68,22 @@ class Encryption extends Component { port_dns_over_tls, certificate_chain, private_key, + certificate_path, + private_key_path, } = encryption; + const initialValues = this.getInitialValues({ + enabled, + server_name, + force_https, + port_https, + port_dns_over_tls, + certificate_chain, + private_key, + certificate_path, + private_key_path, + }); + return (
@@ -49,15 +95,7 @@ class Encryption extends Component { bodyType="card-body box-body--settings" >