passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
293 lines (270 loc) • 9.02 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.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 { withAppContext } from "../../../../shared/context/AppContext/AppContext";
/**
* The component display variations.
* @type {Object}
*/
export const CheckPassphraseVariations = {
SETUP: "Setup",
RECOVER: "Recover",
};
/**
* This component checks the passphrase of an user gpg key
*/
class CheckPassphrase extends Component {
/**
* Default constructor
* @param props Component props
*/
constructor(props) {
super(props);
this.state = this.defaultState;
this.isPwndProcessingPromise = null;
this.bindEventHandlers();
this.createReferences();
}
/**
* Returns the default state
*/
get defaultState() {
return {
passphrase: "", // The passphrase
rememberMe: false, // The remember passphrase flag
isObfuscated: true, // True if the passphrase should not be visible
processing: false, // True if one's processing passphrase
hasBeenValidated: false, // true if the form has already validated once
errors: {
emptyPassphrase: false, // True if the passphrase is empty
invalidPassphrase: false, // True if the passphrase is invalid
},
};
}
/**
* Returns true if the user can perform actions on the component
*/
get areActionsAllowed() {
return !this.state.processing;
}
/**
* Returns true if the component must be in a processing mode
*/
get isProcessing() {
return this.state.processing;
}
/**
* Return true if there are errors
*/
get hasErrors() {
return this.state.errors.emptyPassphrase || this.state.errors.invalidPassphrase;
}
/**
* Whenever the component is mounted
*/
async componentDidMount() {
this.focusOnPassphrase();
}
/**
* Handle component event handlers
*/
bindEventHandlers() {
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChangePassphrase = this.handleChangePassphrase.bind(this);
this.handleToggleRememberMe = this.handleToggleRememberMe.bind(this);
}
/**
* Creates the references
*/
createReferences() {
this.passphraseInputRef = React.createRef();
}
/**
* Whenever the users submits his passphrase
* @param event Dom event
*/
async handleSubmit(event) {
event.preventDefault();
// Prevent submission while processing
if (this.isProcessing) {
return;
}
const errors = this.validate();
if (!errors.emptyPassphrase) {
this.setState({ processing: true });
await this.check();
} else {
this.focusOnPassphrase();
}
}
/**
* Whenever the user changes the private key
* @param event An input event
*/
handleChangePassphrase(event) {
const newState = {
passphrase: event.target.value,
errors: {
emptyPassphrase: false,
invalidPassphrase: false,
},
};
this.setState(newState);
}
/**
* Whenever the user toggles the remember me flag
*/
handleToggleRememberMe() {
this.toggleRemmemberMe();
}
/**
* Check the private gpg key passphrase
*/
async check() {
await this.props.onComplete(this.state.passphrase, this.state.rememberMe).catch(this.onCheckFailure.bind(this));
}
/**
* Whenever the gpg key import failed
* @param {Error} error The error
* @throw {Error} If an unexpected errors hits the component. Errors not of type: InvalidMasterPasswordError.
*/
onCheckFailure(error) {
// Whenever the passphrase is invalid.
this.setState({ processing: false });
if (error.name === "InvalidMasterPasswordError") {
this.setState({ errors: { ...this.state.errors, invalidPassphrase: true } });
this.focusOnPassphrase();
} else {
throw error;
}
}
/**
* Toggle the remember me flag value
*/
toggleRemmemberMe() {
this.setState({ rememberMe: !this.state.rememberMe });
}
/**
* Validate the security token data
*/
validate() {
const { passphrase } = this.state;
const errors = { ...this.state.errors, emptyPassphrase: passphrase.trim() === "" };
this.setState({ hasBeenValidated: true, errors });
return errors;
}
/**
* Put the focus on the passphrase input
*/
focusOnPassphrase() {
this.passphraseInputRef.current.focus();
}
/**
* Render the component
*/
render() {
const processingClassName = this.isProcessing ? "processing" : "";
return (
<div className="check-passphrase">
<h1>
<Trans>Please enter your passphrase to continue.</Trans>
</h1>
<form acceptCharset="utf-8" onSubmit={this.handleSubmit} className="enter-passphrase">
<div className="form-content">
<div
className={`input-password-wrapper input required ${this.hasErrors ? "error" : ""} ${!this.areActionsAllowed ? "disabled" : ""}`}
>
<label htmlFor="passphrase">
<Trans>Passphrase</Trans>
</label>
<Password
id="passphrase"
autoComplete="off"
inputRef={this.passphraseInputRef}
name="passphrase"
value={this.state.passphrase}
preview={true}
onChange={this.handleChangePassphrase}
disabled={!this.areActionsAllowed}
/>
{this.state.hasBeenValidated && (
<>
{this.state.errors.emptyPassphrase && (
<div className="empty-passphrase error-message">
<Trans>The passphrase should not be empty.</Trans>
</div>
)}
{!this.state.errors.emptyPassphrase && this.state.errors.invalidPassphrase && (
<div className="invalid-passphrase error-message">
<Trans>The passphrase is invalid.</Trans>
</div>
)}
</>
)}
</div>
{this.props.canRememberMe && (
<div className="input checkbox">
<input
id="remember-me"
type="checkbox"
name="remember-me"
value={this.state.rememberMe}
onChange={this.handleToggleRememberMe}
disabled={!this.areActionsAllowed}
/>
<label htmlFor="remember-me">
<Trans>Remember until signed out.</Trans>
</label>
</div>
)}
</div>
<div className="form-actions">
<button
type="submit"
className={`button primary big full-width ${processingClassName}`}
disabled={this.isProcessing}
>
<Trans>Verify</Trans>
</button>
{this.props.onSecondaryActionClick && (
<button type="button" className="link" onClick={this.props.onSecondaryActionClick}>
{
{
[CheckPassphraseVariations.SETUP]: <Trans>I lost my passphrase, generate a new private key.</Trans>,
[CheckPassphraseVariations.RECOVER]: <Trans>Help, I lost my passphrase.</Trans>,
}[this.props.displayAs]
}
</button>
)}
</div>
</form>
</div>
);
}
}
CheckPassphrase.defaultProps = {
displayAs: CheckPassphraseVariations.SETUP,
};
CheckPassphrase.propTypes = {
context: PropTypes.any, // The application context
userPassphrasePolicies: PropTypes.object.isRequired, // the user passphrase policies
onComplete: PropTypes.func.isRequired, // The callback to trigger when the user wants to verify its passphrase
displayAs: PropTypes.PropTypes.oneOf([CheckPassphraseVariations.SETUP, CheckPassphraseVariations.RECOVER]), // Defines how the form should be displayed and behaves
canRememberMe: PropTypes.bool, // True if the remember me flag must be displayed
onSecondaryActionClick: PropTypes.func, // Callback to trigger when the user clicks on the secondary action link.
};
export default withAppContext(withTranslation("common")(CheckPassphrase));