passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
433 lines (403 loc) • 12.6 kB
JavaScript
/**
* 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)))),
);