passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
613 lines (571 loc) • 26.2 kB
JavaScript
import React from "react";
import FilterResourcesByFavoritePage from "./components/FilterResourcesByFavoritePage/FilterResourcesByFavoritePage";
import FilterResourcesByItemsIOwnPage from "./components/FilterResourcesByItemsIOwnPage/FilterResourcesByItemsIOwnPage";
import FilterResourcesByGroupPage from "./components/FilterResourcesByGroupPage/FilterResourcesByGroupPage";
import FilterResourcesByRecentlyModifiedPage from "./components/FilterResourcesByRecentlyModifiedPage/FilterResourcesByRecentlyModifiedPage";
import FilterResourcesBySharedWithMePage from "./components/FilterResourcesBySharedWithMePage/FilterResourcesBySharedWithMePage";
import FilterResourcesByTagPage from "./components/FilterResourcesByTagPage/FilterResourcesByTagPage";
import Header from "./components/Header/Header";
import HomePage from "./components/HomePage/HomePage";
import LoginPage from "./components/LoginPage/LoginPage";
import MoreFiltersPage from "./components/MoreFiltersPage/MoreFiltersPage";
import ResourceCreatePage from "./components/ResourceCreatePage/ResourceCreatePage";
import ResourceViewPage from "./components/ResourceViewPage/ResourceViewPage";
import Search from "./components/Search/Search";
import { MemoryRouter as Router, Redirect, Route, Switch } from "react-router-dom";
import AnimatedSwitch from "./components/AnimatedSwitch/AnimatedSwitch";
import PassphraseDialog from "./components/PassphraseDialog/PassphraseDialog";
import PropTypes from "prop-types";
import SiteSettings from "../shared/lib/Settings/SiteSettings";
import UserSettings from "../shared/lib/Settings/UserSettings";
import TranslationProvider from "../shared/components/Internationalisation/TranslationProvider";
import SetupExtensionInProgress from "./components/ExtensionSetup/SetupExtensionInProgress/SetupExtensionInProgress";
import ManageQuickAccessMode from "./components/ManageQuickAccessMode/ManageQuickAccessMode";
import PrivateRoute from "./components/PrivateRoute/PrivateRoute";
import SaveResource from "./components/ResourceAutoSave/SaveResource";
import GeneratePasswordPage from "./components/GeneratePasswordPage/GeneratePasswordPage";
import PrepareResourceContextProvider from "./contexts/PrepareResourceContext";
import SpinnerSVG from "../img/svg/spinner.svg";
import SsoContextProvider from "./contexts/SsoContext";
import RbacsCollection from "../shared/models/entity/rbac/rbacsCollection";
import AppContext from "../shared/context/AppContext/AppContext";
import PasswordPoliciesContext from "../shared/context/PasswordPoliciesContext/PasswordPoliciesContext";
import PasswordExpirySettingsContextProvider from "../react-extension/contexts/PasswordExpirySettingsContext";
import ConfirmCreatePage from "./components/ConfirmCreatePage/ConfirmCreatePage";
import ResourceLocalStorageProvider from "./contexts/ResourceLocalStorageContext";
import ResourceTypesLocalStorageContextProvider from "../shared/context/ResourceTypesLocalStorageContext/ResourceTypesLocalStorageContext";
import MetadataTypesSettingsLocalStorageContextProvider from "../shared/context/MetadataTypesSettingsLocalStorageContext/MetadataTypesSettingsLocalStorageContext";
import AccountEntity from "../shared/models/entity/account/accountEntity";
import ConfirmMetadataKeyDialog from "./components/ConfirmMetadataKeyPage/ConfirmMetadataKeyPage";
import MetadataKeyEntity from "../shared/models/entity/metadata/metadataKeyEntity";
import MetadataTrustedKeyEntity from "../shared/models/entity/metadata/metadataTrustedKeyEntity";
import MetadataKeysSettingsLocalStorageContextProvider from "../shared/context/MetadataKeysSettingsLocalStorageContext/MetadataKeysSettingsLocalStorageContext";
import ActionAbortedMissingMetadataKeysPage from "./components/ActionAbortedMissingMetadataKeysPage/ActionAbortedMissingMetadataKeysPage";
import RbacServiceWorkerService from "../shared/services/serviceWorker/rbac/rbacServiceWorkerService";
const SEARCH_VISIBLE_ROUTES = [
"/webAccessibleResources/quickaccess/home",
"/webAccessibleResources/quickaccess/resources/favorite",
"/webAccessibleResources/quickaccess/resources/group",
"/webAccessibleResources/quickaccess/resources/owned-by-me",
"/webAccessibleResources/quickaccess/resources/recently-modified",
"/webAccessibleResources/quickaccess/resources/shared-with-me",
"/webAccessibleResources/quickaccess/resources/tag",
];
// Supported bootstrap features.
export const BOOTSTRAP_FEATURE = {
LOGIN: "login",
CREATE_NEW_CREDENTIALS: "create-new-credentials",
SAVE_CREDENTIALS: "save-credentials",
AUTOSAVE_CREDENTIALS: "autosave-credentials",
REQUEST_PASSPHRASE: "request-passphrase",
};
class ExtQuickAccess extends React.Component {
constructor(props) {
super(props);
this.createRefs();
this.bindCallbacks();
this.state = this.getDefaultState(props);
this.rbacServiceWorkerService = new RbacServiceWorkerService(props.port);
this.getAccount();
}
/**
* Create DOM nodes or React elements references in order to be able to access them programmatically.
*/
createRefs() {
this.searchRef = React.createRef();
}
/**
* Can the user use the remember until I logout option
* @return {boolean}
*/
get canRememberMe() {
const options = this.state.siteSettings.getRememberMeOptions();
return options !== null && typeof options[-1] !== "undefined";
}
/**
* Bind callbacks methods.
* @return {void}
*/
bindCallbacks() {
this.focusSearch = this.focusSearch.bind(this);
this.updateSearch = this.updateSearch.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleBackgroundPageRequiresPassphraseEvent = this.handleBackgroundPageRequiresPassphraseEvent.bind(this);
this.handlePassphraseDialogCompleted = this.handlePassphraseDialogCompleted.bind(this);
this.loginSuccessCallback = this.loginSuccessCallback.bind(this);
this.logoutSuccessCallback = this.logoutSuccessCallback.bind(this);
this.mfaRequiredCallback = this.mfaRequiredCallback.bind(this);
this.setWindowBlurBehaviour = this.setWindowBlurBehaviour.bind(this);
this.getOpenerTabId = this.getOpenerTabId.bind(this);
this.getBootstrapFeature = this.getBootstrapFeature.bind(this);
this.getDetached = this.getDetached.bind(this);
this.handleBackgroundPageConfirmMetadataKeyEvent = this.handleBackgroundPageConfirmMetadataKeyEvent.bind(this);
this.handleConfirmMetadataKeyDialogCompleted = this.handleConfirmMetadataKeyDialogCompleted.bind(this);
this.closeWindow = this.closeWindow.bind(this);
}
/**
* ComponentDidMount
* Invoked immediately after component is inserted into the tree
* @return {void}
*/
async componentDidMount() {
try {
this.state.port.on("passbolt.passphrase.request", this.handleBackgroundPageRequiresPassphraseEvent);
this.state.port.on("passbolt.metadata-key.trust-confirm", this.handleBackgroundPageConfirmMetadataKeyEvent);
this.handlePassphraseRequest();
await this.checkPluginIsConfigured();
await this.getUser();
const isAuthenticated = await this.checkAuthStatus();
const siteSettings = await this.getSiteSettings();
if (isAuthenticated) {
this.getLoggedInUser(siteSettings);
}
await this.getLocale();
} catch (e) {
this.setState({
hasError: true,
errorMessage: e.message,
});
}
}
/**
* Get the default state value.
* @param {object} props The component props.
* @returns {object}
*/
getDefaultState(props) {
return {
storage: props.storage,
port: props.port,
isAuthenticated: null,
userSettings: null,
siteSettings: null,
loggedInUser: null,
account: null, // The account
rbacs: null, // The role based access control
hasError: false,
errorMessage: "",
locale: "en-UK", // To avoid any weird blink, launch the quickaccess with a default english locale
// Search
search: "",
searchHistory: {},
updateSearch: this.updateSearch,
focusSearch: this.focusSearch,
// Passphrase
passphraseRequired: false,
passphraseRequestId: "",
// Manage popup blur
shouldCloseAtWindowBlur: true, // when true the quickaccess in detached mode should close when losing focus
setWindowBlurBehaviour: this.setWindowBlurBehaviour, // set the detached mode blur behaviour
closeWindow: this.closeWindow,
// Quickaccess properties getters.
getOpenerTabId: this.getOpenerTabId, // Get the opener tab id, useful when used in detached mode to get info of the opener tab.
getBootstrapFeature: this.getBootstrapFeature, // The bootstrap feature.
getDetached: this.getDetached, // The detached mode
// Confirm metadata key
confirmMetadataKeyRequired: false,
confirmMetadataKeyRequestId: null,
confirmMetadataKeyMetadataKey: null,
confirmMetadataKeyMetadataTrustedKey: null,
};
}
/**
* Get the opener tab identifier.
* @returns {string}
*/
getOpenerTabId() {
return this.props.openerTabId;
}
/**
* Get the bootstrap feature.
* @returns {string}
*/
getBootstrapFeature() {
return this.props.bootstrapFeature;
}
/**
* Get the detached mode
* @return {boolean}
*/
getDetached() {
return this.props.detached;
}
updateSearch(search) {
this.setState({ search });
}
focusSearch() {
if (this.searchRef.current) {
this.searchRef.current.focus();
}
}
/**
* When set to true the quickaccess in detached mode should close when losing focus
* @param {boolean} shouldCloseAtWindowBlur
*/
setWindowBlurBehaviour(shouldCloseAtWindowBlur) {
this.setState({ shouldCloseAtWindowBlur });
}
/**
* Closes the current window.
* @returns {Promise<void>}
*/
async closeWindow() {
if (this.getDetached()) {
await this.props.port.request("passbolt.active-tab.close");
} else {
window.close();
}
}
async checkPluginIsConfigured() {
const isConfigured = await this.state.port.request("passbolt.addon.is-configured");
if (!isConfigured) {
await this.props.port.request("passbolt.tabs.open-website-getting-started-page");
await this.closeWindow();
}
}
async getUser() {
const storageData = await this.props.storage.local.get(["_passbolt_data"]);
const userSettings = new UserSettings(storageData._passbolt_data.config);
this.setState({ userSettings });
}
async getSiteSettings() {
const siteSettingsDto = await this.state.port.request("passbolt.organization-settings.get");
const siteSettings = new SiteSettings(siteSettingsDto);
this.setState({ siteSettings });
return siteSettings;
}
/**
* Get the current user info from background page and set it in the state
*/
async getLoggedInUser(siteSettings) {
const canIUseRbac = siteSettings.canIUse("rbacs");
const loggedInUser = await this.props.port.request("passbolt.users.find-logged-in-user");
const rbacsDto = canIUseRbac ? await this.rbacServiceWorkerService.findMe() : [];
const rbacs = new RbacsCollection(rbacsDto);
this.setState({ loggedInUser, rbacs });
}
async getLocale() {
const { locale } = await this.state.port.request("passbolt.locale.get");
this.setState({ locale });
}
/**
* Get the account
* @returns {Promise<void>}
*/
async getAccount() {
const accountDto = await this.state.port.request("passbolt.account.get");
const account = new AccountEntity(accountDto);
this.setState({ account });
}
/**
* Retrieve the authentication status.
*
* If the user is authenticated but the MFA challenge is required, close the quickaccess and redirect the user to
* the passbolt application.
*
* This function requires the user settings to be present in the component state.
* @returns {Promise<void>}
*/
async checkAuthStatus() {
const { isAuthenticated, isMfaRequired } = await this.state.port.request("passbolt.auth.check-status");
if (isMfaRequired) {
await this.redirectToMfaAuthentication();
return;
}
this.setState({ isAuthenticated });
return isAuthenticated;
}
/**
* Redirect to MFA authentication.
*/
async redirectToMfaAuthentication() {
await this.props.port.request("passbolt.tabs.open-trusted-domain");
await this.closeWindow();
}
async loginSuccessCallback() {
if (this.props.bootstrapFeature === BOOTSTRAP_FEATURE.LOGIN) {
await this.closeWindow();
return;
}
const siteSettings = await this.getSiteSettings();
this.setState({ isAuthenticated: true });
this.getLoggedInUser(siteSettings);
}
logoutSuccessCallback() {
this.setState({ isAuthenticated: false });
}
async mfaRequiredCallback() {
await this.props.port.request("passbolt.tabs.open-trusted-domain");
await this.closeWindow();
}
async handleKeyDown(event) {
// Close the quickaccess popup when the user presses the "ESC" key.
if (event.keyCode === 27) {
await this.closeWindow();
}
}
handleBackgroundPageRequiresPassphraseEvent(requestId) {
this.setState({ passphraseRequired: true, passphraseRequestId: requestId });
}
/**
* Handle background page confirm metadata key event
* @param requestId
* @param confirmMetadataKey
*/
handleBackgroundPageConfirmMetadataKeyEvent(requestId, data) {
try {
// Set validation to false as data is required for the entity used by the service worker but should not be sent to the content code.
const metadataKey = new MetadataKeyEntity(data.metadata_key, { validate: false });
const metadataTrustedKey = new MetadataTrustedKeyEntity(data.metadata_trusted_key);
this.setState({
confirmMetadataKeyRequired: true,
confirmMetadataKeyRequestId: requestId,
confirmMetadataKeyMetadataKey: metadataKey,
confirmMetadataKeyMetadataTrustedKey: metadataTrustedKey,
});
} catch (error) {
console.log(error);
this.setState({
hasError: true,
errorMessage: error.message,
});
}
}
async handlePassphraseDialogCompleted() {
if (this.props.bootstrapFeature === BOOTSTRAP_FEATURE.REQUEST_PASSPHRASE) {
await this.closeWindow();
} else {
this.setState({ passphraseRequired: false, passphraseRequestId: null });
}
}
handlePassphraseRequest() {
if (this.props.bootstrapFeature === BOOTSTRAP_FEATURE.REQUEST_PASSPHRASE) {
this.handleBackgroundPageRequiresPassphraseEvent(this.props.bootstrapRequestId);
}
}
/**
* Handle confirm metadata key dialog completed
*/
handleConfirmMetadataKeyDialogCompleted() {
this.setState({
confirmMetadataKeyRequired: false,
confirmMetadataKeyRequestId: null,
confirmMetadataKeyMetadataKey: null,
confirmMetadataKeyMetadataTrustedKey: null,
});
}
isReady() {
return (
this.state.isAuthenticated !== null &&
this.state.userSettings !== null &&
this.state.siteSettings != null &&
this.state.locale !== null
);
}
/**
* Get the route to quickaccess should bootstrap on.
* @returns {string}
*/
getBootstrapRoute() {
if (!this.state.isAuthenticated) {
return "/webAccessibleResources/quickaccess/login";
}
switch (this.props.bootstrapFeature) {
case BOOTSTRAP_FEATURE.CREATE_NEW_CREDENTIALS:
case BOOTSTRAP_FEATURE.SAVE_CREDENTIALS:
return "/webAccessibleResources/quickaccess/resources/create";
case BOOTSTRAP_FEATURE.AUTOSAVE_CREDENTIALS:
return "/webAccessibleResources/quickaccess/resources/autosave";
}
return "/webAccessibleResources/quickaccess/home";
}
/**
* Renders the component
* @returns {JSX.Element}
*/
render() {
const isReady = this.isReady();
return (
<AppContext.Provider value={this.state}>
<TranslationProvider
loadingPath="/webAccessibleResources/locales/{{lng}}/{{ns}}.json"
locale={this.state?.locale}
>
<Router initialEntries={[`/webAccessibleResources/quickaccess.html`]}>
<div className="container quickaccess" onKeyDown={this.handleKeyDown}>
<Header logoutSuccessCallback={this.logoutSuccessCallback} />
{!isReady && !this.state.hasError && (
<div className="processing-wrapper">
<SpinnerSVG />
<p className="processing-text">Connecting your account</p>
</div>
)}
{this.state.hasError && (
<div className="processing-wrapper">
<p className="processing-text">{this.state.errorMessage}</p>
</div>
)}
{isReady && (
<>
<ManageQuickAccessMode />
<Switch>
{/* The initial route the quickaccess panel is loaded on is a triage url. */}
<Route
exact
path={"/webAccessibleResources/quickaccess.html"}
render={() => <Redirect to={this.getBootstrapRoute()} />}
/>
{/* The route when the user is not authenticated */}
<Route
exact
path="/webAccessibleResources/quickaccess/login"
render={() => (
<SsoContextProvider>
<LoginPage
loginSuccessCallback={this.loginSuccessCallback}
mfaRequiredCallback={this.mfaRequiredCallback}
canRememberMe={this.canRememberMe}
/>
</SsoContextProvider>
)}
/>
{/* Any other authenticated routes. */}
<Route path="/">
{this.state.passphraseRequired && (
<PassphraseDialog
requestId={this.state.passphraseRequestId}
onComplete={this.handlePassphraseDialogCompleted}
canRememberMe={this.canRememberMe}
/>
)}
{this.state.confirmMetadataKeyRequired && (
<ConfirmMetadataKeyDialog
requestId={this.state.confirmMetadataKeyRequestId}
metadataKey={this.state.confirmMetadataKeyMetadataKey}
metadataTrustedKey={this.state.confirmMetadataKeyMetadataTrustedKey}
onComplete={this.handleConfirmMetadataKeyDialogCompleted}
/>
)}
<div
className={`${this.state.passphraseRequired || this.state.confirmMetadataKeyRequired ? "visually-hidden" : ""}`}
>
<Route
path={SEARCH_VISIBLE_ROUTES}
render={() => <Search ref={(el) => (this.searchRef = el)} />}
/>
<ResourceTypesLocalStorageContextProvider>
<ResourceLocalStorageProvider>
<MetadataTypesSettingsLocalStorageContextProvider>
<MetadataKeysSettingsLocalStorageContextProvider>
<PasswordPoliciesContext>
<PrepareResourceContextProvider>
<PasswordExpirySettingsContextProvider>
<AnimatedSwitch>
<PrivateRoute
exact
path="/webAccessibleResources/quickaccess/resources/group"
component={FilterResourcesByGroupPage}
/>
<PrivateRoute
path="/webAccessibleResources/quickaccess/resources/group/:id"
component={FilterResourcesByGroupPage}
/>
<PrivateRoute
exact
path="/webAccessibleResources/quickaccess/resources/tag"
component={FilterResourcesByTagPage}
/>
<PrivateRoute
path="/webAccessibleResources/quickaccess/resources/tag/:id"
component={FilterResourcesByTagPage}
/>
<PrivateRoute
exact
path="/webAccessibleResources/quickaccess/resources/favorite"
component={FilterResourcesByFavoritePage}
/>
<PrivateRoute
exact
path="/webAccessibleResources/quickaccess/resources/owned-by-me"
component={FilterResourcesByItemsIOwnPage}
/>
<PrivateRoute
exact
path="/webAccessibleResources/quickaccess/resources/recently-modified"
component={FilterResourcesByRecentlyModifiedPage}
/>
<PrivateRoute
exact
path="/webAccessibleResources/quickaccess/resources/shared-with-me"
component={FilterResourcesBySharedWithMePage}
/>
<PrivateRoute
path="/webAccessibleResources/quickaccess/resources/create"
component={ResourceCreatePage}
/>
<PrivateRoute
exact
path="/webAccessibleResources/quickaccess/resources/confirm-create"
component={ConfirmCreatePage}
/>
<PrivateRoute
exact
path="/webAccessibleResources/quickaccess/resources/autosave"
component={SaveResource}
/>
<PrivateRoute
path="/webAccessibleResources/quickaccess/resources/view/:id"
component={ResourceViewPage}
/>
<PrivateRoute
exact
path="/webAccessibleResources/quickaccess/more-filters"
component={MoreFiltersPage}
/>
<PrivateRoute
exact
path="/webAccessibleResources/quickaccess/setup-extension-in-progress"
component={SetupExtensionInProgress}
/>
<PrivateRoute
path="/webAccessibleResources/quickaccess/resources/generate-password"
component={GeneratePasswordPage}
/>
<PrivateRoute
path="/webAccessibleResources/quickaccess/resources/action-aborted-missing-metadata-keys"
component={ActionAbortedMissingMetadataKeysPage}
/>
<PrivateRoute
exact
path="/webAccessibleResources/quickaccess/home"
component={HomePage}
/>
</AnimatedSwitch>
</PasswordExpirySettingsContextProvider>
</PrepareResourceContextProvider>
</PasswordPoliciesContext>
</MetadataKeysSettingsLocalStorageContextProvider>
</MetadataTypesSettingsLocalStorageContextProvider>
</ResourceLocalStorageProvider>
</ResourceTypesLocalStorageContextProvider>
</div>
</Route>
</Switch>
</>
)}
</div>
</Router>
</TranslationProvider>
</AppContext.Provider>
);
}
}
ExtQuickAccess.propTypes = {
port: PropTypes.object,
storage: PropTypes.object,
bootstrapFeature: PropTypes.string,
bootstrapRequestId: PropTypes.string,
openerTabId: PropTypes.string,
detached: PropTypes.bool,
};
export default ExtQuickAccess;