UNPKG

passbolt-styleguide

Version:

Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.

541 lines (486 loc) 17.3 kB
/** * Passbolt ~ Open source password manager for teams * Copyright (c) 2022 Passbolt SA (https://www.passbolt.com) * * Licensed under GNU Affero General Public License version 3 of the or any later version. * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) 2022 Passbolt SA (https://www.passbolt.com) * @license https://opensource.org/licenses/AGPL-3.0 AGPL License * @link https://www.passbolt.com Passbolt(tm) * @since 3.8.0 */ import React from "react"; import PropTypes from "prop-types"; import {withAppContext} from "./AppContext"; import SmtpSettingsModel from "../../shared/models/smtpSettings/SmtpSettingsModel"; import SmtpTestSettingsModel from "../../shared/models/smtpSettings/SmtpTestSettingsModel"; import SmtpProviders from "../components/Administration/ManageSmtpAdministrationSettings/SmtpProviders.data"; import {withDialog} from "./DialogContext"; import NotifyError from "../components/Common/Error/NotifyError/NotifyError"; import {withActionFeedback} from "./ActionFeedbackContext"; import {withTranslation} from "react-i18next"; import DomainUtil from "../lib/Domain/DomainUtil"; import AppEmailValidatorService from "../../shared/services/validator/AppEmailValidatorService"; export const AdminSmtpSettingsContext = React.createContext({ getCurrentSmtpSettings: () => {}, // Returns the current SMTP settings findSmtpSettings: () => {}, // Find the current smtp settings and store it in the state changeProvider: () => {}, // Handles change of provider setData: () => {}, // Set a field of the form isSettingsModified: () => {}, // returns settingsModified state isSettingsValid: () => {}, // returns true if the current form data is valid getErrors: () => {}, // returns all the detected form errors validateData: () => {}, // validates the current data getFieldToFocus: () => {}, // return the field name to be focused on saveSmtpSettings: () => {}, // calls the service to save the settings currently set in the state isProcessing: () => {}, // returns true if a process is running and the UI must be disabled hasProviderChanged: () => {}, // returns true if the last state change is a provider change sendTestMailTo: () => {}, // calls the service to run a "send test email" isDataReady: () => {}, // returns true when the data is loaded for the server clearContext: () => {}, // put the data to its default state value }); /** * The related context provider */ export class AdminSmtpSettingsContextProvider extends React.Component { /** * Default constructor * @param props The component props */ constructor(props) { super(props); this.state = this.defaultState; const apiClientOptions = props.context.getApiClientOptions(); this.smtpSettingsModel = new SmtpSettingsModel(apiClientOptions); this.smtpTestSettingsModel = new SmtpTestSettingsModel(apiClientOptions); ///smtp/email.json this.fieldToFocus = null; this.providerHasChanged = false; } /** * Returns the default component state */ get defaultState() { return { settingsModified: false, currentSmtpSettings: { // The current SMTP settings provider: null, username: "", password: "", host: "", tls: true, port: "", client: "", sender_email: "", sender_name: "Passbolt", }, errors: {}, isLoaded: false, processing: false, hasSumittedForm: false, getCurrentSmtpSettings: this.getCurrentSmtpSettings.bind(this), // returns the SMTP settings findSmtpSettings: this.findSmtpSettings.bind(this), // Find the SMTP settings and store it in the state changeProvider: this.changeProvider.bind(this), // Handles change of provider setData: this.setData.bind(this), // Set a field of the form isSettingsModified: this.isSettingsModified.bind(this), getErrors: this.getErrors.bind(this), validateData: this.validateData.bind(this), getFieldToFocus: this.getFieldToFocus.bind(this), saveSmtpSettings: this.saveSmtpSettings.bind(this), isProcessing: this.isProcessing.bind(this), hasProviderChanged: this.hasProviderChanged.bind(this), sendTestMailTo: this.sendTestMailTo.bind(this), isDataReady: this.isDataReady.bind(this), clearContext: this.clearContext.bind(this), }; } /** * Find the SMTP settings * @return {Promise<void>} */ async findSmtpSettings() { if (!this.props.context.siteSettings.canIUse('smtpSettings')) { return; } let currentSmtpSettings = this.state.currentSmtpSettings; try { currentSmtpSettings = await this.smtpSettingsModel.findSmtpSettings(); this.setState({currentSmtpSettings, isLoaded: true}); } catch (e) { // In case of error, the user should still be able to update the settings. this.handleError(e); } if (!currentSmtpSettings.sender_email) { currentSmtpSettings.sender_email = this.props.context.loggedInUser.username; } if (currentSmtpSettings.host && currentSmtpSettings.port) { currentSmtpSettings.provider = this.detectProvider(currentSmtpSettings); } this.setState({currentSmtpSettings, isLoaded: true}); } /** * Puts the state to its default in order to avoid keeping the data users didn't want to save. */ clearContext() { const {settingsModified, currentSmtpSettings, errors, isLoaded, processing, hasSumittedForm} = this.defaultState; this.setState({ settingsModified, currentSmtpSettings, errors, isLoaded, processing, hasSumittedForm }); } /** * Register the settings against the API * @returns {Promise<void>} */ async saveSmtpSettings() { this._doProcess(async() => { try { const dto = {...this.state.currentSmtpSettings}; delete dto.provider; dto.client = dto.client || null; await this.smtpSettingsModel.saveSmtpSettings(dto); this.props.actionFeedbackContext.displaySuccess(this.props.t("The SMTP settings have been saved successfully")); const newSettings = Object.assign({}, this.state.currentSmtpSettings, {"source": "db"}); this.setState({currentSmtpSettings: newSettings}); } catch (e) { this.handleError(e); } }); } /** * Send a call to the API in order to send a test email * @returns {Promise<object>} */ async sendTestMailTo(recipient) { return await this.smtpTestSettingsModel.sendTestEmail(this.getCurrentSmtpSettings(), recipient); } /** * Run the given callback by ensuring the "processing" state is handled properly. * @returns {Promise<void>} */ _doProcess(callback) { this.setState({processing: true}, async() => { await callback(); this.setState({processing: false}); }); } /** * Returns true if the provider field has been changed (if asked a second time in a row, it returns false). * @returns {boolean} */ hasProviderChanged() { const hasChanged = this.providerHasChanged; this.providerHasChanged = false; return hasChanged; } /** * Handle change of provider. * @param {object} provider */ changeProvider(provider) { if (provider.id !== this.state.currentSmtpSettings.provider?.id) { this.providerHasChanged = true; this.setState({ settingsModified: true, currentSmtpSettings: { ...this.state.currentSmtpSettings, ...provider.defaultConfiguration, provider: provider } }); } } /** * Set a given state field of this context. * @param {object} data Settings data to update as key value object. */ setData(data) { const newSettings = Object.assign({}, this.state.currentSmtpSettings, data); const newState = { currentSmtpSettings: { ...newSettings, provider: this.detectProvider(newSettings) }, settingsModified: true }; this.setState(newState); if (this.state.hasSumittedForm) { this.validateData(newSettings); } } /** * Returns a provider based on the current configuration or the provider "other" if none detected * @param {object} settings the settings from which to detect the provider * @returns {object} */ detectProvider(settings) { for (let i = 0; i < SmtpProviders.length; i++) { const provider = SmtpProviders[i]; const foundConfiguration = provider.availableConfigurations.find(config => config.host === settings.host && config.port === parseInt(settings.port, 10) && config.tls === settings.tls ); if (foundConfiguration) { return provider; } } return SmtpProviders.find(provider => provider.id === "other"); } /** * Returns true when the data is loaded from the server * @returns {boolean} */ isDataReady() { return this.state.isLoaded; } /** * Returns true when the data is under processing * @returns {boolean} */ isProcessing() { return this.state.processing; } /** * Returns true if the current settings has been modified in the state * @returns {boolean} */ isSettingsModified() { return this.state.settingsModified; } /** * Returns all the errors found during the validation step * @returns {object} */ getErrors() { return this.state.errors; } /** * Validates the current data in the state * @param {object} settings (Optional) The settings to validate, if not provided use the settings from the state. * @returns {boolean} true if the data is valid, false otherwise */ validateData(settings) { settings = settings || this.state.currentSmtpSettings; const errors = {}; let isFormValid = true; isFormValid = this.validate_host(settings.host, errors) && isFormValid; isFormValid = this.validate_sender_email(settings.sender_email, errors) && isFormValid; isFormValid = this.validate_sender_name(settings.sender_name, errors) && isFormValid; isFormValid = this.validate_username(settings.username, errors) && isFormValid; isFormValid = this.validate_password(settings.password, errors) && isFormValid; isFormValid = this.validate_port(settings.port, errors) && isFormValid; isFormValid = this.validate_tls(settings.tls, errors) && isFormValid; isFormValid = this.validate_client(settings.client, errors) && isFormValid; if (!isFormValid) { this.fieldToFocus = this.getFirstFieldInError(errors, ["username", "password", "host", "tls", "port", "client", "sender_name", "sender_email"]); } this.setState({errors, hasSumittedForm: true}); return isFormValid; } /** * Returns true if the host value is valid * @param {string} data the data to validate * @param {object} errors a ref object to put the validation onto * @returns {boolean} */ validate_host(data, errors) { if (typeof data !== "string") { errors.host = this.props.t("SMTP Host must be a valid string"); return false; } if (data.length === 0) { errors.host = this.props.t("SMTP Host is required"); return false; } return true; } /** * Returns true if the client value is valid * @param {string|null} data the data to validate * @param {object} errors a ref object to put the validation onto * @returns {boolean} */ validate_client(data, errors) { if (data.length === 0 || (DomainUtil.isValidHostname(data) && data.length <= 2048)) { return true; } errors.client = this.props.t("SMTP client should be a valid domain or IP address"); return false; } /** * Returns true if the sender_email value is valid * @param {string} data the data to validate * @param {object} errors a ref object to put the validation onto * @returns {boolean} */ validate_sender_email(data, errors) { if (typeof data !== "string") { errors.sender_email = this.props.t("Sender email must be a valid email"); return false; } if (data.length === 0) { errors.sender_email = this.props.t("Sender email is required"); return false; } if (!AppEmailValidatorService.validate(data, this.props.context.siteSettings)) { errors.sender_email = this.props.t("Sender email must be a valid email"); return false; } return true; } /** * Returns true if the sender_name value is valid * @param {string} data the data to validate * @param {object} errors a ref object to put the validation onto * @returns {boolean} */ validate_sender_name(data, errors) { if (typeof data !== "string") { errors.sender_name = this.props.t("Sender name must be a valid string"); return false; } if (data.length === 0) { errors.sender_name = this.props.t("Sender name is required"); return false; } return true; } /** * Returns true if the username value is valid * @param {string|null} data the data to validate * @param {object} errors a ref object to put the validation onto * @returns {boolean} */ validate_username(data, errors) { if (data === null) { return true; } if (typeof data !== "string") { errors.username = this.props.t("Username must be a valid string"); return false; } return true; } /** * Returns true if the password value is valid * @param {string|null} data the data to validate * @param {object} errors a ref object to put the validation onto * @returns {boolean} */ validate_password(data, errors) { if (data === null) { return true; } if (typeof data !== "string") { errors.password = this.props.t("Password must be a valid string"); return false; } return true; } /** * Returns true if the tls value is valid * @param {string} data the data to validate * @param {object} errors a ref object to put the validation onto * @returns {boolean} */ validate_tls(data, errors) { if (typeof data !== "boolean") { errors.tls = this.props.t("TLS must be set to 'Yes' or 'No'"); return false; } return true; } /** * Returns true if the port value is valid * @param {string} data the data to validate * @param {object} errors a ref object to put the validation onto * @returns {boolean} */ validate_port(data, errors) { const portNum = parseInt(data, 10); if (isNaN(portNum)) { errors.port = this.props.t("Port must be a valid number"); return false; } if (portNum < 1 || portNum > 65535) { errors.port = this.props.t("Port must be a number between 1 and 65535"); return false; } return true; } /** * Returns the first field with an error (first in the given list) * @param {object} errors a ref object to put the validation onto * @param {Array<string>} fieldPriority the ordered list of field to check * @returns {string|null} */ getFirstFieldInError(errors, fieldPriority) { for (let i = 0; i < fieldPriority.length; i++) { const fieldName = fieldPriority[i]; if (typeof(errors[fieldName]) !== "undefined") { return fieldName; } } return null; } /** * Returns the current SMTP settings that have been fetch previously. * @returns {object} */ getCurrentSmtpSettings() { return this.state.currentSmtpSettings; } /** * Returns the field name to focus on (if called twice in a row, the second time will return null). * @returns {string} */ getFieldToFocus() { const field = this.fieldToFocus; this.fieldToFocus = null; return field; } /** * Handle exception by displaying a pop-up containing the details of the error. * @param {Error} error */ handleError(error) { console.error(error); this.props.dialogContext.open(NotifyError, {error}); } /** * Render the component * @returns {JSX} */ render() { return ( <AdminSmtpSettingsContext.Provider value={this.state}> {this.props.children} </AdminSmtpSettingsContext.Provider> ); } } AdminSmtpSettingsContextProvider.propTypes = { context: PropTypes.any, // The application context dialogContext: PropTypes.object, // The dialog context actionFeedbackContext: PropTypes.object, // The action feedback context children: PropTypes.any, // The children components t: PropTypes.func, // The translation function }; export default withAppContext(withDialog(withActionFeedback(withTranslation("common")(AdminSmtpSettingsContextProvider)))); /** * Resource Workspace Context Consumer HOC * @param WrappedComponent */ export function withAdminSmtpSettings(WrappedComponent) { return class WithAdminSmtpSettings extends React.Component { render() { return ( <AdminSmtpSettingsContext.Consumer> { adminSmtpSettingsContext => <WrappedComponent adminSmtpSettingsContext={adminSmtpSettingsContext} {...this.props} /> } </AdminSmtpSettingsContext.Consumer> ); } }; }