passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
352 lines (323 loc) • 14.8 kB
JavaScript
/**
* 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.6.0
*/
import React from "react";
import PropTypes from "prop-types";
import { withAppContext } from "../../../shared/context/AppContext/AppContext";
import { BROWSER_NAMES, detectBrowserName } from "../../../shared/lib/Browser/detectBrowserName";
// The authentication recover workflow states.
export const AuthenticationRecoverWorkflowStates = {
CHECK_ACCOUNT_RECOVERY_EMAIL: "Check account recovery email",
CHOOSE_ACCOUNT_RECOVERY_SECURITY_TOKEN: "Choose account recovery security token",
CHOOSE_SECURITY_TOKEN: "Choose security token",
GENERATE_ACCOUNT_RECOVERY_GPG_KEY: "Generate account recovery gpg key",
HELP_CREDENTIALS_LOST: "Help credentials lost",
IMPORT_GPG_KEY: "Import gpg key",
INITIATE_ACCOUNT_RECOVERY: "Initiate account recovery",
INTRODUCE_EXTENSION: "Introduce extension",
ENSURE_SAFARI_EXTENSION_CONFIGURATION: "Ensure Safari extension configuration",
LOADING: "Loading",
REQUESTING_ACCOUNT_RECOVERY: "Requesting account recovery",
SIGNING_IN: "Signing in",
COMPLETING_RECOVER: "Completing recover",
UNEXPECTED_ERROR: "Unexpected Error",
VALIDATE_PASSPHRASE: "Validate passphrase",
CHECK_MAILBOX: "Check mailbox",
};
/**
* The authentication recover context.
* Handle the business logic of the recover and the manage the workflow.
* @type {React.Context<{}>}
*/
export const AuthenticationRecoverContext = React.createContext({
// Workflow data.
state: null, // The recover workflow current state
error: null, // The current error
// Workflow mutators.
goToImportGpgKey: () => {}, // Whenever the user wants to go to the import key step.
importGpgKey: () => {}, // Whenever the user imports its gpg key.
checkPassphrase: () => {}, // Whenever the user want to check the passphrase of its imported gpg key.
chooseSecurityToken: () => {}, // Whenever the user wants to choose its security token preference.
needHelpCredentialsLost: () => {}, // Whenever the user who lost its credentials needs help.
initiateAccountRecovery: () => {}, // Whenever the user wants to initiate an account recovery.
generateAccountRecoveryGpgKey: () => {}, // Whenever the user wants to create an account recovery gpg key.
chooseAccountRecoverySecurityToken: () => {}, // Whenever the user chose its account recovery security token preferences.
validatePrivateKey: () => {}, // Whenever we need to verify the imported private key
requestHelpCredentialsLost: () => {}, // Whenever the user wants to request help because it lost its credentials.
hasKeyExpirationDate: () => {}, // Whenever the key to check has an expiration date
});
/**
* The authentication recover context provider.
* Handle the business logic of the recover and the associated workflow.
*/
export class AuthenticationRecoverContextProvider extends React.Component {
/**
* Default constructor
* @param props The component props
*/
constructor(props) {
super(props);
this.state = this.defaultState;
}
/**
* Returns the default component state
*/
get defaultState() {
return {
// Workflow data.
state: AuthenticationRecoverWorkflowStates.LOADING, // The recover workflow current state
error: null, // The current error
rememberMe: null, // The user remember me choice
userPassphrasePolicies: null, // the current user passphrase policies to use
// Public workflow mutators.
goToImportGpgKey: this.goToImportGpgKey.bind(this), // Whenever the user wants to go to the import key step.
importGpgKey: this.importGpgKey.bind(this), // Whenever the user imports its gpg key.
checkPassphrase: this.checkPassphrase.bind(this), // Whenever the user want to check the passphrase of its imported gpg key.
chooseSecurityToken: this.chooseSecurityToken.bind(this), // Whenever the user wants to choose its security token preference.
needHelpCredentialsLost: this.needHelpCredentialsLost.bind(this), // Whenever the user who lost its credentials needs help.
requestHelpCredentialsLost: this.requestHelpCredentialsLost.bind(this), // Whenever the user wants to request help because it lost its credentials.
initiateAccountRecovery: this.initiateAccountRecovery.bind(this), // Whenever the user wants to initiate an account recovery.
generateAccountRecoveryGpgKey: this.generateAccountRecoveryGpgKey.bind(this), // Whenever the user wants to create an account recovery gpg key.
chooseAccountRecoverySecurityToken: this.chooseAccountRecoverySecurityToken.bind(this), // Whenever the user chose its account recovery security token preferences.
validatePrivateKey: this.validatePrivateKey.bind(this), // Whenever we need to verify the imported private key
hasKeyExpirationDate: this.hasKeyExpirationDate.bind(this), // Whenever the expiration date needs to be checked
};
}
/**
* Whenever the component is initialized
*/
componentDidMount() {
this.initialize();
}
/**
* Initialize the authentication recover workflow
* @returns {Promise<void>}
*/
async initialize() {
await this.props.context.port.request("passbolt.recover.start");
const userPassphrasePolicies = await this.props.context.port.request(
"passbolt.recover.get-user-passphrase-policies",
);
// The user locale is retrieved by the recover start, update the application locale
await this.props.context.initLocale();
const isLostPassphraseCase = await this.props.context.port.request("passbolt.recover.lost-passphrase-case");
if (isLostPassphraseCase) {
const hasUserEnrolledToAccountRecovery = await this.props.context.port.request(
"passbolt.recover.has-user-enabled-account-recovery",
);
const state = hasUserEnrolledToAccountRecovery
? AuthenticationRecoverWorkflowStates.GENERATE_ACCOUNT_RECOVERY_GPG_KEY
: AuthenticationRecoverWorkflowStates.HELP_CREDENTIALS_LOST;
this.setState({ state, userPassphrasePolicies });
return;
}
const browserName = detectBrowserName();
const isChromeBrowser = browserName === BROWSER_NAMES.CHROME;
const isSafariBrowser = browserName === BROWSER_NAMES.SAFARI;
const isFirstInstall = await this.props.context.port.request("passbolt.recover.first-install");
const isActivatedOnAllWebsite = isSafariBrowser && (await this.isActivatedOnAllWebsite());
let state = AuthenticationRecoverWorkflowStates.IMPORT_GPG_KEY;
if (isFirstInstall && isChromeBrowser) {
state = AuthenticationRecoverWorkflowStates.INTRODUCE_EXTENSION;
} else if (isSafariBrowser && !isActivatedOnAllWebsite) {
state = AuthenticationRecoverWorkflowStates.ENSURE_SAFARI_EXTENSION_CONFIGURATION;
}
this.setState({ state, userPassphrasePolicies });
}
/**
* Whenever the user wants to go to the import gpg key step.
* @returns {void}
*/
goToImportGpgKey() {
this.setState({
state: AuthenticationRecoverWorkflowStates.IMPORT_GPG_KEY,
});
}
/**
* Returns true if the extension is not activated on all website.
* @returns {Promise<boolean>}
*/
async isActivatedOnAllWebsite() {
return await this.props.context.port.request("passbolt.extension.is-allowed-on-every-website");
}
/**
* Whenever the user wants to import its gpg key.
* @param {string} armoredKey The user gpg private key.
* @returns {Promise<void>}
* @throw {Error} If an expected errors is returned by the background page, rethrow it for the caller component.
* Errors of type: GpgKeyError.
*/
async importGpgKey(armoredKey) {
try {
await this.props.context.port.request("passbolt.recover.import-key", armoredKey);
this.setState({ state: AuthenticationRecoverWorkflowStates.VALIDATE_PASSPHRASE });
} catch (error) {
if (error.name === "GpgKeyError") {
throw error;
} else {
this.setState({ state: AuthenticationRecoverWorkflowStates.UNEXPECTED_ERROR, error: error });
}
}
}
/**
* Whenever the user imported a gpg key and wants to validate its passphrase.
* @param {string} passphrase The user passphrase.
* @param {boolean} rememberMe (Optional) Should the passphrase be remembered? Default false.
* @returns {Promise<void>}
* @throw {Error} If an expected errors is returned by the background page, rethrow it for the caller component.
* Errors of type: InvalidMasterPasswordError.
*/
async checkPassphrase(passphrase, rememberMe = false) {
try {
await this.props.context.port.request("passbolt.recover.verify-passphrase", passphrase);
this.setState({
state: AuthenticationRecoverWorkflowStates.CHOOSE_SECURITY_TOKEN,
rememberMe: rememberMe,
});
} catch (error) {
if (error.name === "InvalidMasterPasswordError") {
throw error;
} else {
this.setState({ state: AuthenticationRecoverWorkflowStates.UNEXPECTED_ERROR, error: error });
}
}
}
/**
* Whenever the user chose its security token.
* @param {Object} securityTokenDto The security token dto
* @returns {Promise<void>}
*/
async chooseSecurityToken(securityTokenDto) {
try {
await this.props.context.port.request("passbolt.recover.set-security-token", securityTokenDto);
this.setState({ state: AuthenticationRecoverWorkflowStates.COMPLETING_RECOVER });
await this.props.context.port.request("passbolt.recover.complete");
this.setState({ state: AuthenticationRecoverWorkflowStates.SIGNING_IN });
await this.props.context.port.request("passbolt.recover.sign-in", this.state.rememberMe);
await this.props.context.port.request("passbolt.auth.post-login-redirect");
} catch (error) {
this.setState({ state: AuthenticationRecoverWorkflowStates.UNEXPECTED_ERROR, error: error });
}
}
/**
* Whenever the user who lost its credentials needs help.
* @returns {Promise<void>}
*/
async needHelpCredentialsLost() {
const hasUserAccountRecoveryEnabled = await this.props.context.port.request(
"passbolt.recover.has-user-enabled-account-recovery",
);
if (hasUserAccountRecoveryEnabled) {
this.setState({ state: AuthenticationRecoverWorkflowStates.INITIATE_ACCOUNT_RECOVERY });
} else {
this.setState({ state: AuthenticationRecoverWorkflowStates.HELP_CREDENTIALS_LOST });
}
}
/**
* Whenever the user wants to initiate the account recovery process.
* @returns {void}
*/
initiateAccountRecovery() {
this.setState({ state: AuthenticationRecoverWorkflowStates.GENERATE_ACCOUNT_RECOVERY_GPG_KEY });
}
/**
* Whenever the user wants to request help because it lost its credentials.
* @returns {Promise<void>}
*/
async requestHelpCredentialsLost() {
try {
await this.props.context.port.request("passbolt.recover.request-help-credentials-lost");
this.setState({ state: AuthenticationRecoverWorkflowStates.CHECK_MAILBOX });
} catch (error) {
this.setState({ state: AuthenticationRecoverWorkflowStates.UNEXPECTED_ERROR, error: error });
}
}
/**
* Whenever the generation of an account recovery request gpg key is requested by the user.
* @param {string} passphrase The passphrase used to encrypt the generated gpg key
* @return {Promise<void>}
*/
async generateAccountRecoveryGpgKey(passphrase) {
const generateKeyDto = { passphrase };
try {
await this.props.context.port.request("passbolt.recover.generate-account-recovery-request-key", generateKeyDto);
this.setState({ state: AuthenticationRecoverWorkflowStates.CHOOSE_ACCOUNT_RECOVERY_SECURITY_TOKEN });
} catch (error) {
this.setState({ state: AuthenticationRecoverWorkflowStates.UNEXPECTED_ERROR, error: error });
}
}
/**
* Whenever the user set its account recovery security token.
* @param {Object} securityTokenDto The security token dto.
* @returns {Promise<void>}
*/
async chooseAccountRecoverySecurityToken(securityTokenDto) {
try {
await this.props.context.port.request("passbolt.recover.set-security-token", securityTokenDto);
this.setState({ state: AuthenticationRecoverWorkflowStates.REQUESTING_ACCOUNT_RECOVERY });
await this.props.context.port.request("passbolt.recover.initiate-account-recovery-request");
this.setState({ state: AuthenticationRecoverWorkflowStates.CHECK_ACCOUNT_RECOVERY_EMAIL });
} catch (error) {
this.setState({ state: AuthenticationRecoverWorkflowStates.UNEXPECTED_ERROR, error: error });
}
}
/**
* Whenever we need to verify the imported private key
* @param {string} key the private to check in its armored form
*/
async validatePrivateKey(key) {
await this.props.context.port.request("passbolt.recover.validate-private-key", key);
}
/**
* Returns true if the key has an expiry date
* @param {string} key the private to check in its armored form
* @returns {Promise<boolean>}
*/
async hasKeyExpirationDate(armoredKey) {
const keyInfo = await this.props.context.port.request("passbolt.keyring.get-key-info", armoredKey);
return keyInfo.expires && keyInfo.expires !== "Infinity";
}
/**
* Render the component
* @returns {JSX}
*/
render() {
return (
<AuthenticationRecoverContext.Provider value={this.state}>
{this.props.children}
</AuthenticationRecoverContext.Provider>
);
}
}
AuthenticationRecoverContextProvider.propTypes = {
context: PropTypes.any, // The application context
children: PropTypes.any, // The children components
};
export default withAppContext(AuthenticationRecoverContextProvider);
/**
* Authentication recover context consumer HOC
* @param {React.Component} WrappedComponent The component to wrap
*/
export function withAuthenticationRecoverContext(WrappedComponent) {
return class WithAuthenticationContext extends React.Component {
render() {
return (
<AuthenticationRecoverContext.Consumer>
{(AuthenticationRecoverContext) => (
<WrappedComponent authenticationRecoverContext={AuthenticationRecoverContext} {...this.props} />
)}
</AuthenticationRecoverContext.Consumer>
);
}
};
}