passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
403 lines (380 loc) • 15.8 kB
JavaScript
/**
* Passbolt ~ Open source password manager for teams
* Copyright (c) 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) Passbolt SA (https://www.passbolt.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.passbolt.com Passbolt(tm)
* @since 5.0.0
*/
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Trans, withTranslation } from "react-i18next";
import Password from "../../../../shared/components/Password/Password";
import DiceSVG from "../../../../img/svg/dice.svg";
import PasswordComplexity from "../../../../shared/components/PasswordComplexity/PasswordComplexity";
import CaretDownSVG from "../../../../img/svg/caret_down.svg";
import CaretRightSVG from "../../../../img/svg/caret_right.svg";
import Tabs from "../../Common/Tab/Tabs";
import Tab from "../../Common/Tab/Tab";
import ConfigurePasswordGenerator from "../../../../shared/components/GeneratePassword/ConfigurePasswordGenerator";
import ConfigurePassphraseGenerator from "../../../../shared/components/GeneratePassword/ConfigurePassphraseGenerator";
import { withResourcePasswordGeneratorContext } from "../../../contexts/ResourcePasswordGeneratorContext";
import { SecretGenerator } from "../../../../shared/lib/SecretGenerator/SecretGenerator";
import { withAppContext } from "../../../../shared/context/AppContext/AppContext";
import AttentionSVG from "../../../../img/svg/attention.svg";
class AddResourcePassword extends Component {
constructor(props) {
super(props);
this.state = this.defaultState;
this.bindCallbacks();
this.createReferences();
}
get defaultState() {
return {
displayPasswordGenerator: false,
generatorSettings: null,
};
}
/**
* Component did mount
*/
componentDidMount() {
this.setState({
generatorSettings: this.props.resourcePasswordGeneratorContext.getSettings(),
});
}
/**
* Bind callbacks methods
* @return {void}
*/
bindCallbacks() {
this.handleDisplayPasswordGeneratorClick = this.handleDisplayPasswordGeneratorClick.bind(this);
this.handlePassphraseGeneratorConfigurationChanged = this.handlePassphraseGeneratorConfigurationChanged.bind(this);
this.handleGeneratorTypeChanged = this.handleGeneratorTypeChanged.bind(this);
this.handlePasswordGeneratorConfigurationChanged = this.handlePasswordGeneratorConfigurationChanged.bind(this);
this.handleGeneratePasswordClick = this.handleGeneratePasswordClick.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
/**
* Handle when the generator configuration has changed
* @param {Object} generatorSettings The generator configuration
*/
handleGeneratorConfigurationChanged(generatorSettings) {
const generatedPassword = this.generatePassword(generatorSettings);
this.setState({ generatorSettings });
this.handleInputChange({
target: {
name: "secret.password",
value: generatedPassword,
},
});
}
/**
* Create elements references
*/
createReferences() {
this.passwordInputRef = React.createRef();
}
/**
* Generate the password
* @param {object} generatorConfiguration the configuration to be sued to generate a new password
*/
generatePassword(generatorConfiguration) {
return SecretGenerator.generate(generatorConfiguration);
}
/**
* Component did update
* @param prevProps
* @param prevState
*/
async componentDidUpdate() {
const hasEntropyError = this.props.consumePasswordEntropyError();
if (hasEntropyError) {
this.passwordInputRef.current.focus();
}
}
/**
* Handles the click on the display secrets button.
*/
handleDisplayPasswordGeneratorClick() {
this.setState({ displayPasswordGenerator: !this.state.displayPasswordGenerator });
}
/**
* Handle when the secret generator type is changed.
* @param {string} generatorType
*/
handleGeneratorTypeChanged(generatorType) {
const generatorSettings = JSON.parse(JSON.stringify(this.state.generatorSettings));
generatorSettings.default_generator = generatorType;
this.handleGeneratorConfigurationChanged(generatorSettings);
}
/**
* Handle when the passphrase generator configuration has changed
* @param generator The generator configuration
*/
handlePassphraseGeneratorConfigurationChanged(generator) {
const settings = JSON.parse(JSON.stringify(this.state.generatorSettings));
settings.passphrase_generator_settings = generator;
this.handleGeneratorConfigurationChanged(settings);
}
/**
* Handle when the password generator configuration has changed
* @param generator The generator configuration
*/
handlePasswordGeneratorConfigurationChanged(generator) {
const settings = JSON.parse(JSON.stringify(this.state.generatorSettings));
settings.password_generator_settings = generator;
this.handleGeneratorConfigurationChanged(settings);
}
/**
* Handle when the generate password has been clicked
*/
handleGeneratePasswordClick() {
const generatedPassword = this.generatePassword(this.state.generatorSettings);
this.handleInputChange({
target: {
name: "secret.password",
value: generatedPassword,
},
});
}
/**
* Returns true if the logged in user can use the password generator capability.
* @returns {boolean}
*/
get canUsePasswordGenerator() {
return this.props.context.siteSettings.canIUse("passwordGenerator");
}
/**
* Get the translation function
* @returns {function(...[*]=)}
*/
get translate() {
return this.props.t;
}
/**
* Handle form input change.
* @params {ReactEvent} The react event.
*/
handleInputChange(event) {
if (this.props.onChange) {
this.props.onChange(event);
}
}
/**
* Checks if there is a max length warning for a specific property.
*
* @param {string} propName - The name of the property to check for max length warnings.
* @param {string} association - The association name.
* @returns {boolean} - Returns true if there is a max length warning for the property, false otherwise.
*/
isMaxLengthWarnings(propName, association) {
return (
!this.isMaxLengthError(propName, association) &&
this.props.warnings?.hasError(`${association}.${propName}`, "maxLength")
);
}
/**
* Checks if there is a max length error for a specific property.
*
* @param {string} propName - The name of the property to check for max length errors.
* @param {string} association - The association name.
* @returns {boolean} - Returns true if there is a max length error for the property, false otherwise.
*/
isMaxLengthError(propName, association) {
if (propName.includes(".")) {
const segments = propName.split(".");
const propArrayName = segments[0];
const propsArrayIndex = segments[1];
return this.props.errors?.details?.[association]?.details?.[propArrayName]?.[propsArrayIndex]?.maxLength;
} else {
return this.props.errors?.details?.[association]?.hasError(propName, "maxLength");
}
}
/*
* =============================================================
* Render view
* =============================================================
*/
render() {
return (
<>
<div className="title">
<h2>
<Trans>Password</Trans>
</h2>
</div>
<div className="content">
<div className="password-fields">
<div className={`input text ${this.props.disabled ? "disabled" : ""}`}>
<label htmlFor="resource-uri">
<Trans>URI</Trans>
{this.isMaxLengthWarnings("uris.0", "metadata") && <AttentionSVG className="attention-required" />}
</label>
<input
id="resource-uri"
disabled={this.props.disabled}
name="metadata.uris.0"
maxLength="1024"
type="text"
autoComplete="off"
placeholder={this.translate("URI")}
value={this.props.resource?.metadata?.uris?.[0]}
onChange={this.handleInputChange}
/>
{this.isMaxLengthError("uris.0", "metadata") && (
<div className="uri error-message">
<Trans>This is the maximum size for this field, make sure your data was not truncated.</Trans>
</div>
)}
{this.isMaxLengthWarnings("uris.0", "metadata") && (
<div className="uri warning-message">
<strong>
<Trans>Warning:</Trans>
</strong>{" "}
<Trans>this is the maximum size for this field, make sure your data was not truncated.</Trans>
</div>
)}
</div>
<div className={`input text ${this.props.disabled ? "disabled" : ""}`}>
<label htmlFor="resource-username">
<Trans>Username</Trans>
{this.isMaxLengthWarnings("username", "metadata") && <AttentionSVG className="attention-required" />}
</label>
<input
id="resource-username"
disabled={this.props.disabled}
name="metadata.username"
type="text"
className="fluid"
maxLength="255"
autoComplete="off"
placeholder={this.translate("Username")}
value={this.props.resource?.metadata?.username}
onChange={this.handleInputChange}
/>
{this.isMaxLengthError("username", "metadata") && (
<div className="username error-message">
<Trans>This is the maximum size for this field, make sure your data was not truncated.</Trans>
</div>
)}
{this.isMaxLengthWarnings("username", "metadata") && (
<div className="username warning-message">
<strong>
<Trans>Warning:</Trans>
</strong>{" "}
<Trans>this is the maximum size for this field, make sure your data was not truncated.</Trans>
</div>
)}
</div>
<div className={`input-password-wrapper input ${this.props.disabled ? "disabled" : ""}`}>
<label htmlFor="resource-password">
<Trans>Password</Trans>
{this.isMaxLengthWarnings("password", "secret") && <AttentionSVG className="attention-required" />}
</label>
<div className="password-button-inline">
<Password
id="resource-password"
name="secret.password"
autoComplete="new-password"
placeholder={this.translate("Password")}
preview={true}
value={this.props.resource?.secret?.password}
onChange={this.handleInputChange}
inputRef={this.passwordInputRef}
disabled={this.props.disabled}
/>
<button
type="button"
disabled={this.props.disabled}
className="password-generate button-icon"
onClick={this.handleGeneratePasswordClick}
>
<DiceSVG />
</button>
</div>
{this.isMaxLengthError("password", "secret") && (
<div className="password error-message">
<Trans>This is the maximum size for this field, make sure your data was not truncated.</Trans>
</div>
)}
{this.isMaxLengthWarnings("password", "secret") && (
<div className="password warning-message">
<strong>
<Trans>Warning:</Trans>
</strong>{" "}
<Trans>this is the maximum size for this field, make sure your data was not truncated.</Trans>
</div>
)}
<PasswordComplexity entropy={this.props.passwordEntropy} />
</div>
</div>
{this.canUsePasswordGenerator && (
<div className="additional-information">
<button
type="button"
className="section-header no-border"
onClick={this.handleDisplayPasswordGeneratorClick}
>
<h4>
<Trans>Advanced password generation</Trans>
</h4>
{this.state.displayPasswordGenerator ? <CaretDownSVG /> : <CaretRightSVG />}
</button>
{this.state.displayPasswordGenerator && this.state.generatorSettings?.default_generator && (
<Tabs activeTabName={this.state.generatorSettings.default_generator}>
<Tab
key={"password"}
name={this.props.t("password")}
type={"password"}
onClick={() => this.handleGeneratorTypeChanged("password")}
>
{this.state.generatorSettings.default_generator === "password" && (
<ConfigurePasswordGenerator
disabled={this.props.disabled}
configuration={this.state.generatorSettings.password_generator_settings}
onConfigurationChanged={this.handlePasswordGeneratorConfigurationChanged}
/>
)}
</Tab>
<Tab
key={"passphrase"}
name={this.props.t("passphrase")}
type={"passphrase"}
onClick={() => this.handleGeneratorTypeChanged("passphrase")}
>
{this.state.generatorSettings.default_generator === "passphrase" && (
<ConfigurePassphraseGenerator
disabled={this.props.disabled}
configuration={this.state.generatorSettings.passphrase_generator_settings}
onConfigurationChanged={this.handlePassphraseGeneratorConfigurationChanged}
/>
)}
</Tab>
</Tabs>
)}
</div>
)}
</div>
</>
);
}
}
AddResourcePassword.propTypes = {
context: PropTypes.any, // The app context
passwordEntropy: PropTypes.number, // a callback for when the entropy of the current password changed
consumePasswordEntropyError: PropTypes.func, // the password entropy error state consumer to know if the password field must be focused or nto
resourcePasswordGeneratorContext: PropTypes.any, // The resource password generator context
resource: PropTypes.object, // The resource to edit or create
onChange: PropTypes.func, // The on change function
t: PropTypes.func, // The translation function
warnings: PropTypes.object, //The warnings validation
errors: PropTypes.object, // The errors entity error validation
disabled: PropTypes.bool, // The disabled property
};
export default withAppContext(withResourcePasswordGeneratorContext(withTranslation("common")(AddResourcePassword)));