From 05cce8b1073dd4c005157177bdc129cba813a12c Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Mon, 18 Feb 2019 16:06:27 +0300 Subject: [PATCH] Added validation on change and enable encryption checkbox --- client/src/__locales/en.json | 15 +- client/src/actions/encryption.js | 70 ++++++++ client/src/actions/index.js | 41 ----- client/src/api/Api.js | 10 ++ client/src/components/App/index.js | 12 +- client/src/components/Header/index.js | 4 +- .../components/Settings/Encryption/Form.js | 149 ++++++++++++++---- .../components/Settings/Encryption/index.js | 32 +++- client/src/components/Settings/index.js | 1 + client/src/components/ui/EncryptionTopline.js | 41 +++++ client/src/containers/Settings.js | 6 +- client/src/helpers/constants.js | 1 + client/src/reducers/encryption.js | 62 ++++++++ client/src/reducers/index.js | 33 +--- config.go | 2 +- 15 files changed, 356 insertions(+), 123 deletions(-) create mode 100644 client/src/actions/encryption.js create mode 100644 client/src/components/ui/EncryptionTopline.js create mode 100644 client/src/reducers/encryption.js diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 115bd6cb..59fa9e75 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -219,18 +219,27 @@ "encryption_redirect": "Redirect to HTTPS automatically", "encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.", "encryption_https": "HTTPS port", - "encryption_https_desc": "If HTTPS port is configured, AdGuard Home admin interface will be accessible via HTTPS, and it will also provide DNS-over-HTTPS on '\\dns-query' location.", + "encryption_https_desc": "If HTTPS port is configured, AdGuard Home admin interface will be accessible via HTTPS, and it will also provide DNS-over-HTTPS on '\/dns-query' location.", "encryption_dot": "DNS-over-TLS port", "encryption_dot_desc": "If this port is configured, AdGuard Home will run a DNS-over-TLS server on this port.", "encryption_certificates": "Certificates", "encryption_certificates_desc": "In order to use encryption, you need to provide a valid SSL certificates chain for your domain. You can get a free certificate on <0>{{link}} or you can buy it from one of the trusted Certificate Authorities.", "encryption_certificates_input": "Copy/paste your PEM-encoded cerificates here.", "encryption_status": "Status", - "encryption_certificates_for": "Certificates for {{domains}}", - "encryption_expire": "Expire on {{date}}", + "encryption_expire": "Expires", "encryption_key": "Private key", "encryption_key_input": "Copy/paste your PEM-encoded private key for your cerficate here.", + "encryption_enable": "Enable Encryption (HTTPS, DNS-over-HTTPS, and DNS-over-TLS)", + "encryption_enable_desc": "If encryption is enabled, AdGuard Home admin interface will work over HTTPS, and the DNS server will listen for requests over DNS-over-HTTPS and DNS-over-TLS.", + "encryption_chain_valid": "Certificate chain is valid", + "encryption_chain_invalid": "Certificate chain is invalid", + "encryption_key_valid": "This is a valid {{type}} private key", + "encryption_key_invalid": "This is an invalid {{type}} private key", + "encryption_subject": "Subject", + "encryption_issuer": "Issuer", + "encryption_hostnames": "Hostnames", "topline_expiring_certificate": "Your SSL certificate is about to expire. Update <0>Encryption settings.", + "topline_expired_certificate": "Your SSL certificate is expired. Update <0>Encryption settings.", "form_error_port_range": "Enter port value in the range of 80-65535", "form_error_equal": "Shouldn't be equal", "form_error_password": "Password mismatched", diff --git a/client/src/actions/encryption.js b/client/src/actions/encryption.js new file mode 100644 index 00000000..a537f31d --- /dev/null +++ b/client/src/actions/encryption.js @@ -0,0 +1,70 @@ +import { createAction } from 'redux-actions'; +import Api from '../api/Api'; +import { addErrorToast, addSuccessToast } from './index'; + +const apiClient = new Api(); + +export const getTlsStatusRequest = createAction('GET_TLS_STATUS_REQUEST'); +export const getTlsStatusFailure = createAction('GET_TLS_STATUS_FAILURE'); +export const getTlsStatusSuccess = createAction('GET_TLS_STATUS_SUCCESS'); + +export const getTlsStatus = () => async (dispatch) => { + dispatch(getTlsStatusRequest()); + try { + const status = await apiClient.getTlsStatus(); + status.certificate_chain = atob(status.certificate_chain); + status.private_key = atob(status.private_key); + + dispatch(getTlsStatusSuccess(status)); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(getTlsStatusFailure()); + } +}; + +export const setTlsConfigRequest = createAction('SET_TLS_CONFIG_REQUEST'); +export const setTlsConfigFailure = createAction('SET_TLS_CONFIG_FAILURE'); +export const setTlsConfigSuccess = createAction('SET_TLS_CONFIG_SUCCESS'); + +export const setTlsConfig = config => async (dispatch) => { + dispatch(setTlsConfigRequest()); + try { + const values = { ...config }; + values.certificate_chain = btoa(values.certificate_chain); + values.private_key = btoa(values.private_key); + values.port_https = values.port_https || 0; + values.port_dns_over_tls = values.port_dns_over_tls || 0; + + const response = await apiClient.setTlsConfig(values); + response.certificate_chain = atob(response.certificate_chain); + response.private_key = atob(response.private_key); + dispatch(setTlsConfigSuccess(response)); + dispatch(addSuccessToast('encryption_config_saved')); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(setTlsConfigFailure()); + } +}; + +export const validateTlsConfigRequest = createAction('VALIDATE_TLS_CONFIG_REQUEST'); +export const validateTlsConfigFailure = createAction('VALIDATE_TLS_CONFIG_FAILURE'); +export const validateTlsConfigSuccess = createAction('VALIDATE_TLS_CONFIG_SUCCESS'); + +export const validateTlsConfig = config => async (dispatch) => { + dispatch(validateTlsConfigRequest()); + try { + const values = { ...config }; + values.certificate_chain = btoa(values.certificate_chain); + values.private_key = btoa(values.private_key); + values.port_https = values.port_https || 0; + values.port_dns_over_tls = values.port_dns_over_tls || 0; + + const response = await apiClient.validateTlsConfig(values); + response.certificate_chain = atob(response.certificate_chain); + response.private_key = atob(response.private_key); + dispatch(validateTlsConfigSuccess(response)); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(validateTlsConfigFailure()); + } +}; diff --git a/client/src/actions/index.js b/client/src/actions/index.js index 9b06352d..1bb99064 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -650,44 +650,3 @@ export const toggleDhcp = config => async (dispatch) => { } } }; - -export const getTlsStatusRequest = createAction('GET_TLS_STATUS_REQUEST'); -export const getTlsStatusFailure = createAction('GET_TLS_STATUS_FAILURE'); -export const getTlsStatusSuccess = createAction('GET_TLS_STATUS_SUCCESS'); - -export const getTlsStatus = () => async (dispatch) => { - dispatch(getTlsStatusRequest()); - try { - const status = await apiClient.getTlsStatus(); - status.certificate_chain = atob(status.certificate_chain); - status.private_key = atob(status.private_key); - - dispatch(getTlsStatusSuccess(status)); - } catch (error) { - dispatch(addErrorToast({ error })); - dispatch(getTlsStatusFailure()); - } -}; - -export const setTlsConfigRequest = createAction('SET_TLS_CONFIG_REQUEST'); -export const setTlsConfigFailure = createAction('SET_TLS_CONFIG_FAILURE'); -export const setTlsConfigSuccess = createAction('SET_TLS_CONFIG_SUCCESS'); - -export const setTlsConfig = config => async (dispatch) => { - dispatch(setTlsConfigRequest()); - try { - const values = { ...config }; - values.certificate_chain = btoa(values.certificate_chain); - values.private_key = btoa(values.private_key); - values.port_https = values.port_https || 0; - values.port_dns_over_tls = values.port_dns_over_tls || 0; - - await apiClient.setTlsConfig(values); - dispatch(setTlsConfigSuccess(config)); - dispatch(addSuccessToast('encryption_config_saved')); - dispatch(getTlsStatus()); - } catch (error) { - dispatch(addErrorToast({ error })); - dispatch(setTlsConfigFailure()); - } -}; diff --git a/client/src/api/Api.js b/client/src/api/Api.js index 1971fe4a..e77b083c 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -358,6 +358,7 @@ export default class Api { // DNS-over-HTTPS and DNS-over-TLS TLS_STATUS = { path: 'tls/status', method: 'GET' }; TLS_CONFIG = { path: 'tls/configure', method: 'POST' }; + TLS_VALIDATE = { path: 'tls/validate', method: 'POST' }; getTlsStatus() { const { path, method } = this.TLS_STATUS; @@ -372,4 +373,13 @@ export default class Api { }; return this.makeRequest(path, method, parameters); } + + validateTlsConfig(config) { + const { path, method } = this.TLS_VALIDATE; + const parameters = { + data: config, + headers: { 'Content-Type': 'application/json' }, + }; + return this.makeRequest(path, method, parameters); + } } diff --git a/client/src/components/App/index.js b/client/src/components/App/index.js index a756eadc..b483f64a 100644 --- a/client/src/components/App/index.js +++ b/client/src/components/App/index.js @@ -1,7 +1,7 @@ import React, { Component, Fragment } from 'react'; import { HashRouter, Route } from 'react-router-dom'; import PropTypes from 'prop-types'; -import { Trans, withNamespaces } from 'react-i18next'; +import { withNamespaces } from 'react-i18next'; import LoadingBar from 'react-redux-loading-bar'; import 'react-table/react-table.css'; @@ -18,6 +18,7 @@ import Footer from '../ui/Footer'; import Toasts from '../Toasts'; import Status from '../ui/Status'; import Topline from '../ui/Topline'; +import EncryptionTopline from '../ui/EncryptionTopline'; import i18n from '../../i18n'; class App extends Component { @@ -56,7 +57,6 @@ class App extends Component { !dashboard.processingVersions && dashboard.isCoreRunning && dashboard.isUpdateAvailable; - const isExpiringCertificate = !encryption.processing && encryption.warning; return ( @@ -66,12 +66,8 @@ class App extends Component { {dashboard.announcement} Click here for more info. } - {isExpiringCertificate && - - link]}> - topline_expiring_certificate - - + {!encryption.processing && + } diff --git a/client/src/components/Header/index.js b/client/src/components/Header/index.js index 4c21a19a..ecc1a2af 100644 --- a/client/src/components/Header/index.js +++ b/client/src/components/Header/index.js @@ -12,7 +12,6 @@ import './Header.css'; class Header extends Component { state = { isMenuOpen: false, - isDropdownOpen: false, }; toggleMenuOpen = () => { @@ -25,6 +24,7 @@ class Header extends Component { render() { const { dashboard } = this.props; + const { isMenuOpen } = this.state; const badgeClass = classnames({ 'badge dns-status': true, 'badge-success': dashboard.protectionEnabled, @@ -52,7 +52,7 @@ class Header extends Component { diff --git a/client/src/components/Settings/Encryption/Form.js b/client/src/components/Settings/Encryption/Form.js index db0f1b41..e42766ee 100644 --- a/client/src/components/Settings/Encryption/Form.js +++ b/client/src/components/Settings/Encryption/Form.js @@ -1,10 +1,13 @@ import React, { Fragment } from 'react'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { Field, reduxForm } from 'redux-form'; +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 } from '../../../helpers/form'; +import { EMPTY_DATE } from '../../../helpers/constants'; import i18n from '../../../i18n'; const validate = (values) => { @@ -20,21 +23,46 @@ const validate = (values) => { return errors; }; -const Form = (props) => { +let Form = (props) => { const { t, handleSubmit, + handleChange, + isEnabled, + certificateChain, + privateKey, reset, invalid, submitting, processing, - statusCert, - statusKey, + not_after, + valid_chain, + valid_key, + dns_names, + key_type, + issuer, + subject, + warning_validation, } = props; return (
+
+
+ +
+
+ encryption_enable_desc +
+
+
-
+