UNPKG

passbolt-styleguide

Version:

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

433 lines (403 loc) 12.6 kB
/** * Passbolt ~ Open source password manager for teams * Copyright (c) 2020 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) 2020 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.1.0 */ import React, { Component } from "react"; import { CirclePicker } from "react-color"; import NotifyError from "../../Common/Error/NotifyError/NotifyError"; import { withDialog } from "../../../contexts/DialogContext"; import PropTypes from "prop-types"; import { withAppContext } from "../../../../shared/context/AppContext/AppContext"; import { withUserSettings } from "../../../contexts/UserSettingsContext"; import { withActionFeedback } from "../../../contexts/ActionFeedbackContext"; import SecretComplexity from "../../../../shared/lib/Secret/SecretComplexity"; import { Trans, withTranslation } from "react-i18next"; import { isValidSecurityToken } from "../../../../shared/utils/assertions"; import Tooltip from "../../Common/Tooltip/Tooltip"; import InfoSVG from "../../../../img/svg/info.svg"; /** * This component displays the user choose security token information */ class ChangeUserSecurityToken extends Component { /** * Default constructor * @param props Component props */ constructor(props) { super(props); this.state = this.defaultState; this.bindEventHandlers(); this.createReferences(); } /** * Whenever the component is mounted */ componentDidMount() { // get token and color from the logged user this.getSecurityTokenFromLoggedInUser(); } /** * Returns the default state */ get defaultState() { return { background: "", // The token color code: "", // The token code processing: false, // True if one's processing passphrase hasBeenValidated: false, // true if the form has already validated once errors: { emptyCode: false, // True if the token code is empty lengthCode: false, // True if the token code length is > 3 invalidRegex: false, // True if the regex is not valid }, }; } /** * Get the security token from the logged in user */ getSecurityTokenFromLoggedInUser() { const background = this.props.context.userSettings.getSecurityToken().backgroundColor; const code = this.props.context.userSettings.getSecurityToken().code; this.setState({ background, code }); } /** * Returns the default set of colors */ get defaultColors() { return [ "#f44336", "#9c27b0", "#3f51b5", "#03a9f4", "#009688", "#8bc34a", "#ffeb3b", "#ff9800", "#795548", "#607d8b", "#000000", "#f6f6f6", ]; } /** * Returns the default set of colors text */ get textColor() { const c = this.state.background.substr(1).match(/(\S{2})/g); const r = parseInt(c[0], 16); const g = parseInt(c[1], 16); const b = parseInt(c[2], 16); const l = (r * 299 + g * 587 + b * 114) / 1000; return l > 125 ? "#000000" : "#ffffff"; } /** * Returns the security token CSS style * @returns {undefined|{backgroundColor: *, color: (string)}} */ get securityTokenStyle() { if (this.state.background) { return { backgroundColor: this.state.background, color: this.textColor, }; } else { return undefined; } } /** * Returns true if the user can perform actions on the component */ get areActionsAllowed() { return !this.state.processing; } /** * Returns true if there is at least one error property is true * @param {object} errors The errors * @return boolean */ isValid(errors) { return Object.values(errors).every((value) => !value); } /** * Returns true if the component must be in a processing mode */ get isProcessing() { return this.state.processing; } /** * Handle component event handlers */ bindEventHandlers() { this.handleSubmit = this.handleSubmit.bind(this); this.handleSelectColor = this.handleSelectColor.bind(this); this.handleRandomize = this.handleRandomize.bind(this); this.handleChangeCode = this.handleChangeCode.bind(this); } /** * Creates the references */ createReferences() { this.tokenCodeInputRef = React.createRef(); } /** * Whenever the users submits his security token choice * @param event Dom event */ async handleSubmit(event) { event.preventDefault(); // Prevent submission while processing if (this.isProcessing) { return; } this.setState({ hasBeenValidated: true, processing: true }); const errors = this.validate(); if (this.isValid(errors)) { await this.save(); } this.setState({ processing: false }); } /** * Whenever a new color has been selected */ async handleSelectColor(color) { if (this.areActionsAllowed) { this.selectColor(color); if (this.state.hasBeenValidated) { this.validate(); } } } /** * Whenever the user wants to randomize the token code and color */ handleRandomize() { this.randomizeCode(); this.randomizeColor(); } /** * Whenever the user changes the token code * @param event An input event */ async handleChangeCode(event) { const code = event.target.value; this.selectCode(code); if (this.state.hasBeenValidated) { this.validate(); } } /** * Saves the security token */ async save() { const securityTokenDto = { color: this.state.background, textcolor: this.textColor, code: this.state.code, }; try { await this.props.userSettingsContext.onUpdateSecurityTokenRequested(securityTokenDto); await this.props.actionFeedbackContext.displaySuccess( this.props.t("The security token has been updated successfully"), ); } catch (error) { await this.onSaveFailure(error); } finally { this.setState({ processing: false }); } } /** * Whenever the gpg key generation failed * @param error The error */ async onSaveFailure(error) { const ErrorDialogProps = { error: error }; this.props.dialogContext.open(NotifyError, ErrorDialogProps); } /** * Select a token color * @param color A color */ selectColor(color) { if (color.hex !== this.state.background) { this.setState({ background: color.hex }); } } /** * Select a token code * @param code A code */ selectCode(code) { this.setState({ code }); } /** * Randomize a token code */ randomizeCode() { const code = SecretComplexity.generate(3, ["uppercase"]); this.selectCode(code); if (this.state.hasBeenValidated) { this.validate(); } } /** * Randomize a color */ randomizeColor() { let color; do { const number = parseInt(SecretComplexity.generate(3, ["digit"])) % this.defaultColors.length; color = { hex: this.defaultColors[number], }; } while (color.hex === this.state.background); this.selectColor(color); } /** * Validate the security token data * @return {object} errors */ validate() { const { code } = this.state; const errors = {}; const emptyCode = code.trim() === ""; if (emptyCode) { errors.emptyCode = true; } else { const lengthCode = code.trim().length !== 3; if (lengthCode) { errors.lengthCode = true; } else { const invalidRegex = !isValidSecurityToken(code.trim()); if (invalidRegex) { errors.invalidRegex = true; } } } this.setState({ errors }); return errors; } /** * Check if there are errors * @return {bool} */ get hasErrors() { return ( this.state.errors && (this.state.errors.emptyCode || this.state.errors.lengthCode || this.state.errors.invalidRegex) ); } /** * Render the component */ render() { const processingClassName = this.isProcessing ? "processing" : ""; return ( <> <div className="main-column profile-choose-security-token"> <div className="main-content"> <form onSubmit={this.handleSubmit}> <h3> <Trans>Update the Security Token</Trans> </h3> <div className={`input-security-token input required ${this.hasErrors ? "error" : ""} ${!this.areActionsAllowed ? "disabled" : ""}`} > <div className="label-required-inline"> <label htmlFor="security-token-text"> <Trans>Security token</Trans> </label> <Tooltip message={this.props.t("Only alphanumeric, dash and underscore characters are accepted.")}> <InfoSVG className="baseline svg-icon" /> </Tooltip> </div> <input id="security-token-text" ref={this.tokenCodeInputRef} type="text" aria-required={true} className="input text required" name="text" maxLength="3" style={this.securityTokenStyle} value={this.state.code} onChange={this.handleChangeCode} disabled={!this.areActionsAllowed} /> <input type="hidden" id="security-token-background-color" name="security-token-background-color" /> <input type="hidden" id="security-token-text-color" name="security-token-text-color" /> <CirclePicker color={this.state.background} onChange={this.handleSelectColor} width={240} circleSize={24} circleSpacing={16} colors={this.defaultColors} /> <div className="randomize-button-wrapper"> <button type="button" className={`randomize-button link ${this.isProcessing ? "disabled" : ""}`} disabled={this.isProcessing} onClick={this.handleRandomize} > <Trans>Randomize</Trans> </button> </div> </div> {this.state.hasBeenValidated && ( <div className="input text"> {this.state.errors.emptyCode && ( <div className="empty-code error-message"> <Trans>The security token code should not be empty.</Trans> </div> )} {this.state.errors.lengthCode && ( <div className="not-good-length-code error-message"> <Trans>The security token code should be 3 characters long.</Trans> </div> )} {this.state.errors.invalidRegex && ( <div className="not-good-regex-code error-message"> <Trans> The security token code should contains only alphanumeric, dash and underscore characters. </Trans> </div> )} </div> )} </form> </div> </div> <div className="actions-wrapper"> <button className={`button primary form ${processingClassName}`} type="submit" disabled={this.isProcessing} onClick={this.handleSubmit} > <Trans>Save</Trans> </button> </div> </> ); } } ChangeUserSecurityToken.propTypes = { context: PropTypes.object, // The application context userSettingsContext: PropTypes.object, // The user settings context dialogContext: PropTypes.any, // The dialog context actionFeedbackContext: PropTypes.object, // The action feedback context t: PropTypes.func, // The translation function }; export default withAppContext( withDialog(withActionFeedback(withUserSettings(withTranslation("common")(ChangeUserSecurityToken)))), );