0

I've got a basic admin app and I basically want to protect certain routes against the roles sent by the API when a user logs in via the Oauth2 protocol.

I have a route like...

<Route name="app" handler={App}>
   <Route name="admin" path="/admin" roles={["admin", "superadmin"]} />
</Route>

Then I have an authentication component...

import React from 'react';
import SessionStore from '../stores/auth/SessionStore';

export default (ComposedComponent) => {

  return class AuthenticatedComponent extends React.Component {

    static willTransitionTo(transition) {

      // If user isn't logged in, send 'em back to the login page
      if (!SessionStore.isLoggedIn()) {
        transition.redirect('/login', {}, {'nextPath' : transition.path});
      } else if (this.rolesRequired) {

        // Get all current users roles from session store.
        let userRoles = SessionStore.roles;

        // Iterate through user roles, if none match the required roles, send 'em away ta.
        if (!this.rolesRequired.every(role => userRoles.indexOf(role) >= 0)) {
          transition.redirect('/login', {}, { 'nextPath' : transition.path }); 
        }
      }
    }

    constructor(props) {
      super(props);
      this.state = this._getLoginState();
    }

    _getLoginState() {
      return {
        userLoggedIn: SessionStore.isLoggedIn(),
        user: SessionStore.user,
        token: SessionStore.token,
        roles: SessionStore.roles
      };
    }

    componentDidMount() {
      this.changeListener = this._onChange.bind(this);
      SessionStore.addChangeListener(this.changeListener);
    }

    _onChange() {
      this.setState(this._getLoginState());    
    }

    componentsWillUnmount() {
      SessionStore.removeChangeListener(this.changeListener);
    }

    render() {
      return (
        <ComposedComponent
          {...this.props}
          user={this.state.user}
          token={this.state.token}
          roles={this.state.roles}
          userLoggedIn={this.state.userLoggedIn} />
      );    
    }
  }
}

Then any components which need authenticating are passed into an instance of the AuthenticatedComponent, for example...

import React from 'react';
import RoleStore from '../../stores/user/RoleStore';
i
mport AdminActions from '../../actions/admin/AdminActions';
import AuthenticatedComponent from '../../components/AuthenticatedComponent';
import AdminMenu from '../../components/admin/AdminMenu';
import Template from '../template/BaseTemplate.react';
import RoleActions from '../../actions/user/RoleActions';

    /**
 * Admin
 *
 * @author    Ewan Valentine
 * @copyright 65twenty 2015
 */
export default AuthenticatedComponent(class Admin extends React.Component {

  constructor() {
    super();
    this.state = {
      users: [],
      roles: []
    };
    this.onChange = this.onChange.bind(this);
  }



  onChange() {
    this.setState({
      roles: RoleStore.data,
      users: UserListStore.data
    });  
  }

  render() {
    return(
          <Template>
          <main>
            <AdminMenu />
            <h2>Admin Home</h2>
          </main>
          </Template>
        );
      }
    });

I basically can't figure out the best approach for defining the required roles and there doesn't seem to be any way of accessing props on the Route component.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
Ewan Valentine
  • 3,741
  • 7
  • 43
  • 68
  • Are the variables you would want to set (roles) always component specific? So if you were able to receive the props passed on the route (`roles={["admin", "superadmin"]}`) it would work for you? – BradByte Aug 25 '15 at 13:15
  • @BradBumbalough I need to define different roles for different components essentially – Ewan Valentine Aug 25 '15 at 13:18
  • Have you looked at using the statics in addition to `willTransitionTo` to store `requiredRoles`? So set the roles on the component's statics, then have the `willTransitionTo` compare those roles against the logged in user. – BradByte Aug 25 '15 at 13:33
  • @BradBumbalough I tried using `static rolesRequired() { return ["admin"] }` on the component, but the trouble is, the component doesn't extend the AuthenticatedComponent, it's injected into the AuthenticatedComponent, so it doesn't know what `this.rolesRequired()` is – Ewan Valentine Aug 25 '15 at 13:46

1 Answers1

0

I had a similar issue, where I wanted to only show "Billing" link on top navbar if user belonged to 'admin' group.

I also had an Authentication component like you did, then I created an Authorization component, and several authorization policies depending on the required roles. Here is the code:

var Authorized = require('../auth/Authorized');
var AdminComponentPolicy = require('../auth/policies/AdminComponentPolicy');

<Authorized policy={AdminComponentPolicy} action="show" user= {this.props.user}>
  ...protected stuff
</Authorized>

Here is the code for the Authorized component:

//Authorized.jsx
import React from 'react';

var Authorized = React.createClass({
  render: function() {
    //checks if the informed policy returns true for given action and user.
    if (this.props.policy.authorized(this.props.action, this.props.user)) {
      return this.props.children;
    } else {
      return null;
    }
  }
});
module.exports = Authorized;

Here is the code for AdminComponentPolicy

//AdminComponentPolicy.js
class AdminComponentPolicy {
  authorized(action, user) {
    //return user.role === 'admin';
    let _policies = {
      //the 'show' action in this policy returns true for 'admin' users
      show: function(record) {
         return record.role === 'admin';
      },
      destroy: function(record) {
         return this.show(record) || record.role === 'check if owner here';
      },
     };
    return _policies[action](user);
  }
}

export default new AdminComponentPolicy()
Stenio Ferreira
  • 357
  • 2
  • 10