Aaron Pennington
Aaron Pennington

Reputation: 566

How do I replicate the admin dashboard in meanjs to create a user directory

I am new to meanjs but I am currently trying to create a simple app where users can connect to each other.

Right now I just want to implement a function where users can look each other up by search and nothing else but I keep running into a 403 error. I tried to fix it by replicating the documents that make up the admin 'manage users' view.

I didn't want to give everyone the 'admin' role so I added a new policy for users where they are given only 'get' permissions. I also added a new route, view and controller for regular user that is modified from the admin files.

It has worked pretty well but I still can't get the system to allow access to the new userDirectory view when logged in under a non-admin role. I think it has something to do with the auth.interceptor.client.service.js file and the modules/core/client/app/init.js file and the $state being provided by the new files I made but I have tried to modify them and it still is throwing up the /forbidden view in the body of my app.

I am at a loss for what else I can do. Any help would be greatly appreciated.

The app starts with home.client.view.html view:

<section ng-controller="HomeController">

  </div>
  <div class="jumbotron text-center" ng-hide="authentication.user">
    <div class="row">
      <div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3 col-xs-12">
        <img alt="MEAN.JS" class="img-responsive text-center" src="modules/core/client/img/brand/Logo.png" />
      </div>
    </div>
    <br>
    <div class="row">
      <p class="lead">
        An app
      </p>
    </div>
    <div class="row">
      <p>
        <!-- <a class="btn btn-primary btn-lg" href="http://meanjs.org" target="_blank">Learn more</a> -->
      </p>
    </div>
  </div>
  <div ng-if="authentication.user">
    <h2>Congratulations! You are logged in.</h2>
    <div ng-include="'/modules/users/client/views/user/user-directory.client.view.html'"></div>
    <!-- <div ng-include="'/api/users'"></div> -->

  </div>

</section>

Here is my view for the userDirectory (modified from modules/users/client/views/admin/list-users.client.view.html):

<section ng-controller="UserDirectoryController">
  <div class="page-header">
    <div class="row">
      <div class="col-md-4">
        <h1>Users</h1>
      </div>
      <div class="col-md-4" style="margin-top: 2em">
        <input class="form-control col-md-4" type="text" ng-model="search" placeholder="Search" ng-change="figureOutItemsToDisplay()" />
      </div>
    </div>
  </div>
  <div class="list-group">
    <a ng-repeat="user in pagedSearchItems" ui-sref="users.user({userId: user._id})" class="list-group-item">
      <h4 class="list-group-item-heading" ng-bind="user.username"></h4>
      <p class="list-group-item-text" ng-bind="user.email"></p>
    </a>
  </div>

  <pagination boundary-links="true" max-size="8" items-per-page="itemsPerPage" total-items="filterLength" ng-model="currentPage" ng-change="pageChanged()"></pagination>
</section>

My Controller (modified from modules/users/client/controllers/admin/list-users.client.controller.js):

'use strict';

angular.module('users.user').controller('UserDirectoryController', ['$scope', '$filter', 'User',
  function ($scope, $filter, User) {
    User.query(function (data) {
      $scope.users = data;
      $scope.buildPager();
    });

    $scope.buildPager = function () {
      $scope.pagedSearchItems = [];
      $scope.itemsPerPage = 15;
      $scope.currentPage = 1;
      $scope.figureOutItemsToDisplay();
    };

    $scope.figureOutItemsToDisplay = function () {
      $scope.filteredItems = $filter('filter')($scope.users, {
        $: $scope.search
      });
      $scope.filterLength = $scope.filteredItems.length;
      var begin = (($scope.currentPage - 1) * $scope.itemsPerPage);
      var end = begin + $scope.itemsPerPage;
      $scope.pagedSearchItems = $scope.filteredItems.slice(begin, end);
    };

    $scope.pageChanged = function () {
      $scope.figureOutItemsToDisplay();
    };
  }
]);

My Service (modified from modules/users/client/services/users.client.service.js):

'use strict';

// Users service used for communicating with the users REST endpoint
angular.module('users').factory('User', ['$resource',
  function ($resource) {
    return $resource('api/userDirectory', {}, {
      update: {
        method: 'PUT'
      }
    });
  }
]);

//TODO this should be Users service
angular.module('users.user').factory('User', ['$resource',
  function ($resource) {
    return $resource('api/userDirectory/:userId', {
      userId: '@_id'
    }, {
      update: {
        method: 'PUT'
      }
    });
  }
]);

angular.module('users.user').factory('User', ['$resource',
  function ($resource) {
    return $resource('api/userDirectory/:userId', {
      userId: '@_id'
    }, {
      update: {
        method: 'PUT'
      }
    });
  }
]);

My new route.js (modified from modules/users/server/routes/admin.server.routes.js):

'use strict';

/**
 * Module dependencies.
 */
var userPolicy = require('../policies/userDirectory.server.policy'),
  userDirectory = require('../controllers/userDirectory.server.controller');

module.exports = function (app) {
  // User route registration first. Ref: #713
  require('./users.server.routes.js')(app);

  // Users collection routes
  app.route('/api/userDirectory')
    .get(userPolicy.isAllowed, userDirectory.list);

  //Single user routes
  app.route('/api/userDirectory/:userId')
    .get(userPolicy.isAllowed, userDirectory.read);

  // Finish by binding the user middleware
  app.param('userId', userDirectory.userByID);
};

And finally my new policy for userDirectory (modified from modules/users/server/policies/admin.server.policy.js):

'use strict';

/**
 * Module dependencies.
 */
var acl = require('acl');

// Using the memory backend
acl = new acl(new acl.memoryBackend());

/**
 * Invoke User Permissions
 */
exports.invokeRolesPolicies = function () {
  acl.allow([{
    roles: ['user'],
    allows: [{
      resources: '/api/userDirectory',
      permissions: ['post', 'get']
    }, {
      resources: '/api/userDirectory/:userId',
      permissions: ['post', 'get']
    }]
  }]);
};

/**
 * Check If User Policy Allows
 */
exports.isAllowed = function (req, res, next) {
  var roles = (req.user) ? req.user.roles : ['guest'];

// Check for user roles
  acl.areAnyRolesAllowed(roles, req.route.path, req.method.toLowerCase(), function (err, isAllowed) {
    if (err) {
      // An authorization error occurred.
      return res.status(500).send('Unexpected authorization error');
    } else {
      if (isAllowed) {
        // Access granted! Invoke next middleware
        return next();
      } else {
        return res.status(403).json({
          message: 'User is not authorized'
        });
      }
    }
  });
};

Upvotes: 0

Views: 173

Answers (1)

Luke Kroon
Luke Kroon

Reputation: 1103

I think the easiest way to do it is to add a new route to the server which only users can access and create a service to retrieve that data.

(This is only a service for getting the data from the DB, you can use it in any view/controller, or create a new view which is only accessible to the users/admins)

In your module (client side) create a service to perform a http post/get to the server:

(function() {
 'use strict';
 angular
   .module('yourModule')
   .factory('UserListService', function($http) {
     var routes = {};
     routes.getUserList = function(username) {
       return $http.post('/api/userlist', username); //you can do a 'post' to search for one username or just 'get' to see all users.
     };
     return routes;
   }
 );
})();

and then in modulses/yourModule/server/routes add the route:

app.route('/api/userlist').all(yourModulePolicy.isAllowed)
.post(yourModule.getUserList); //get or post its up to you

don't forget to add permissions in modulses/yourModule/server/policies

roles: ['user'],
allows: [{
  resources: '/api/userlist',
  permissions: ['post'] //get or post
}]

then handle the request in modulses/yourModule/server/controllers

exports.getUserList = function(req, res) {
  var name_to_search = req.body.username;
  //here you can query the database for all users or the name you
  //received in the post.
  //NOTE: if you query the user DB there can be sensitive information
  //such as the hashed password etc, make sure you delete that here
  //before you respond with the user/user list
  res.jsonp(user_list);
};

after this you can use the user information in the client side controller like this:

UserListService.getUserList({
   username: 'john'
 }).then(function(data) {
   vm.userlist = data.data;
 });

Make sure to inject UserListService into the controller and pass it to the controller function:

YourController.$inject = ['UserListService'];

and

function YourController(UserListService){...}

Hope this helps, i'm also new to the whole mean stack, don't even know if this is the best solution, but it will work.

EDIT:

When you create a new view on the client side with a controller you need to add it to the client side routes in modules/yourModule/client/config/yourModule.client.routes.js

.state('your.state', {
        url: '-yourUrl',
        templateUrl: 'modules/yourModule/client/views/user-list.client.view.html',
        controller: 'YourController',
        controllerAs: 'vm',
        data: {
          roles: ['user', 'admin'],
          pageTitle: 'User List'
        }
      })

Upvotes: 1

Related Questions