import { Children, Component, ReactNode, ComponentType } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import compose from 'recompose/compose'; import getContext from 'recompose/getContext'; import { userCheck as userCheckAction } from '../actions/authActions'; import { AUTH_GET_PERMISSIONS } from './types'; import { isLoggedIn as getIsLoggedIn } from '../reducer'; import warning from '../util/warning'; import { AuthProvider } from '../types'; import { UserCheck } from './types'; import { Location } from 'history'; import { match as Match } from 'react-router'; export interface WithPermissionsChildrenParams { authParams?: object; location?: Location; match: Match; permissions: any; } type WithPermissionsChildren = ( params: WithPermissionsChildrenParams ) => ReactNode; interface Props { authParams?: object; children?: WithPermissionsChildren; location: Location; match: Match; render?: WithPermissionsChildren; staticContext?: object; } interface EnhancedProps { authProvider: AuthProvider; isLoggedIn: boolean; userCheck: UserCheck; } const isEmptyChildren = children => Children.count(children) === 0; /** * After checking that the user is authenticated, * retrieves the user's permissions for a specific context. * * Useful for Route components ; used internally by Resource. * Use it to decorate your custom page components to require * a custom role. It will pass the permissions as a prop to your * component. * * Pass the `location` from the `routeParams` as `location` prop. * You can set additional `authParams` at will if your authProvider * requires it. * * @example * import { WithPermissions } from 'react-admin'; * * const Foo = ({ permissions }) => ( * {permissions === 'admin' ?

Sensitive data

: null} *

Not sensitive data

* ); * * const customRoutes = [ * * } * /> * } /> * ]; * const App = () => ( * * ... * * ); */ export class WithPermissions extends Component { cancelled = false; state = { permissions: null }; componentWillMount() { warning( this.props.render && this.props.children && !isEmptyChildren(this.props.children), 'You should not use both and ; will be ignored' ); this.checkAuthentication(this.props); } async componentDidMount() { await this.checkPermissions(this.props); } componentWillUnmount() { this.cancelled = true; } componentWillReceiveProps(nextProps) { if ( nextProps.location !== this.props.location || nextProps.authParams !== this.props.authParams || nextProps.isLoggedIn !== this.props.isLoggedIn ) { this.checkAuthentication(nextProps); this.checkPermissions(this.props); } } checkAuthentication(params: Props & EnhancedProps) { const { userCheck, authParams, location } = params; userCheck(authParams, location && location.pathname); } async checkPermissions(params: Props & EnhancedProps) { const { authProvider, authParams, location, match } = params; try { const permissions = await authProvider(AUTH_GET_PERMISSIONS, { ...authParams, routeParams: match ? match.params : undefined, location: location ? location.pathname : undefined, }); if (!this.cancelled) { this.setState({ permissions }); } } catch (error) { if (!this.cancelled) { this.setState({ permissions: null }); } } } // render even though the AUTH_GET_PERMISSIONS // isn't finished (optimistic rendering) render() { const { authProvider, userCheck, isLoggedIn, render, children, staticContext, ...props } = this.props; const { permissions } = this.state; if (render) { return render({ permissions, ...props }); } if (children) { return children({ permissions, ...props }); } } } const mapStateToProps = state => ({ isLoggedIn: getIsLoggedIn(state), }); const EnhancedWithPermissions = compose( getContext({ authProvider: PropTypes.func, }), connect( mapStateToProps, { userCheck: userCheckAction } ) )(WithPermissions); export default EnhancedWithPermissions as ComponentType;