ReactとRedux(Node.jsバックエンドを使用)でシングルページアプリケーションを作成しています。
ロールベースのアクセス制御を実装し、アプリの特定の部分(またはサブ部分)の表示を制御したいと思います。
Node.jsからパーミッションリストを取得します。これは、次のような構造のオブジェクトです。
{
users: 'read',
models: 'write',
...
dictionaries: 'none',
}
keyは保護されたリソースであり、
valueは、このリソースに対するユーザー権限です(none
、read
、write
のいずれか) 。
Redux状態で保存しています。簡単そうです。
none
権限は、react-router
ルートonEnter/onChange
フックまたはredux-auth-wrapper
によってチェックされます。それも簡単そうです。
ただし、コンポーネントビューにread/write
権限を適用するための最良の方法は何ですか(たとえば、ユーザーが{ models: 'read' }
権限を持っている場合はModelsコンポーネントの編集ボタンを非表示にします)。
私は この解決策 を見つけ、私のタスクのために少し変更します:
class Check extends React.Component {
static propTypes = {
resource: React.PropTypes.string.isRequired,
permission: React.PropTypes.oneOf(['read', 'write']),
userPermissions: React.PropTypes.object,
};
// Checks that user permission for resource is the same or greater than required
allowed() {
const permissions = ['read', 'write'];
const { permission, userPermissions } = this.props;
const userPermission = userPermissions[resource] || 'none';
return permissions.indexOf(userPermission) >= permissions.indexOf(permission)
}
render() {
if (this.allowed()) return { this.props.children };
}
}
export default connect(userPermissionsSelector)(Check)
ここで、userPermissionsSelector
は次のようになります:(store) => store.userPermisisons
そしてユーザー権限オブジェクトを返します。
次に、保護された要素をCheck
でラップします。
<Check resource="models" permission="write">
<Button>Edit model</Button>
</Check>
したがって、ユーザーがwrite
に対するmodels
権限を持っていない場合、ボタンは表示されません。
誰かがこのようなことをしましたか?これよりも「エレガントな」ソリューションはありますか?
ありがとう!
P.S。もちろん、ユーザー権限もサーバー側でチェックされます。
さて、私はあなたが欲しいものを理解したと思います。私は自分に合った何かをしました、そして私はそれを持っている方法が好きです、しかし私は他の実行可能な解決策がそこにあることを理解しています。
私が書いたのはHOCのreact-routerスタイルでした。
基本的に、ユーザーのアクセス許可を初期化するPermissionsProviderがあります。以前に提供した権限をコンポーネントに注入する別のwithPermissionsHOCがあります。
したがって、その特定のコンポーネントのアクセス許可を確認する必要がある場合は、簡単にアクセスできます。
// PermissionsProvider.js
import React, { Component } from "react";
import PropTypes from "prop-types";
import hoistStatics from "hoist-non-react-statics";
class PermissionsProvider extends React.Component {
static propTypes = {
permissions: PropTypes.array.isRequired,
};
static contextTypes = {
permissions: PropTypes.array,
};
static childContextTypes = {
permissions: PropTypes.array.isRequired,
};
getChildContext() {
// maybe you want to transform the permissions somehow
// maybe run them through some helpers. situational stuff
// otherwise just return the object with the props.permissions
// const permissions = doSomething(this.props.permissions);
// maybe add some validation methods
return { permissions: this.props.permissions };
}
render() {
return React.Children.only(this.props.children);
}
}
const withPermissions = Component => {
const C = (props, context) => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
<Component permissions={context.permissions} {...remainingProps} ref={wrappedComponentRef} />
);
};
C.displayName = `withPermissions(${Component.displayName || Component.name})`;
C.WrappedComponent = Component;
C.propTypes = {
wrappedComponentRef: PropTypes.func
};
C.contextTypes = {
permissions: PropTypes.array.isRequired
};
return hoistStatics(C, Component);
};
export { PermissionsProvider as default, withPermissions };
わかりました、これはたくさんのコードです。しかし、これらはHOCです(詳細は ここ を学ぶことができます)。
高階コンポーネント(HOC)は、コンポーネントロジックを再利用するためのReactの高度な手法です。HOC自体はReact APIの一部ではありません。は、Reactの構成的性質から生まれるパターンです。具体的には、高階コンポーネントは、コンポーネントを受け取り、新しいコンポーネントを返す関数です。
基本的に私がこれをしたのは、react-routerが行ったことに触発されたからです。ルーティングに関する情報が必要な場合はいつでも、デコレータ@ withRouterを追加するだけで、コンポーネントに小道具を挿入できます。 では、同じことをしてみませんか?
//App render
return (
<PermissionsProvider permissions={permissions}>
<SomeStuff />
</PermissionsProvider>
);
SomeStuffのどこかに、権限をチェックする広く普及したツールバーがありますか?
@withPermissions
export default class Toolbar extends React.Component {
render() {
const { permissions } = this.props;
return permissions.canDoStuff ? <RenderStuff /> : <HeCantDoStuff />;
}
}
デコレータを使用できない場合は、次のようにツールバーをエクスポートします
export default withPermissions(Toolbar);
これが私が実際にそれを示したコードサンドボックスです:
https://codesandbox.io/s/lxor8v3pkz
ノート:
@lokuztによって提案されたアプローチは素晴らしいです。
また、コードを簡素化するためにさらに先に進むことができます。
まず第一に、すべての保護されたコンポーネントにはいくつかがあります 要件 レンダリングに満足する。レンダリングにrequirementを取り、credentialsをとる関数を定義する必要があります。パラメータとしての現在のユーザー。 trueまたはfalseを返す必要があります。
function isSatisfied(requirement, credentials) {
if (...) {
return false;
}
return true;
}
さらに、ReactJSの 新しいコンテキストAPI を使用して HOC(高階コンポーネント) を定義する必要があります。
const { Provider, Consumer } = React.createContext();
function protect(requirement, WrappedComponent) {
return class extends Component {
render() {
return (
<Consumer>
{ credentials => isSatisfied(requirement, credentials)
? <WrappedComponent {...this.props}>
{this.props.children}
</WrappedComponent>
: null
}
</Consumer>
);
}
};
}
これで、コンポーネントを装飾できます。
const requireAdmin = {...}; // <- this is your requirement
class AdminPanel extends Component {
...
}
export default protect(requireAdmin, AdminPanel);
またはサードパーティのコンポーネント:
import {Button} from 'react-bootstrap';
const AdminButton = protect(requireAdmin, Button);
資格情報は、ReactJSコンテキストAPIによって渡される必要があります。
class MyApp extends Component {
render() {
const {credentials} = this.props;
<Provider value={credentials}>
...
<AdminPanel/>
<AdminButton>
Drop Database
</AdminButton>
...
</Provider>
}
}
これが私の拡張された 実装 githubです。
デモ もご利用いただけます。