Search code examples
javascriptloopbackjsloopback

Granular access control with loopback


I'm using the latest version of loopback and I'd like to work on the acl now. My app has a project model that should be available tontge team members, relative owner and admin. Of course I would like to have multiple projects with members that can be team member in multiple projects ... An owner could own multiple projects and the admin should be able to see everything and to do everything.

Loopback seem to have the possibility to define that.

How would you approach that?

Thanks


Solution

  • First of all, define static roles in your boot scripts eq: TeamMeamber, Owner, Admin, etc

    see this example https://loopback.io/doc/en/lb3/Defining-and-using-roles.html#static-roles

    //create the admin role
    Role.create({
      name: 'admin'
    }, function(err, role) {
    
    });
    

    Then register RoleResolver in your boot scripts for your roles. This is the logic for determining wheter for arequest, the role applies or not.

    From the example at the above link.

    Role.registerResolver('teamMember', function(role, context, cb) {
        // Q: Is the current request accessing a Project?
        if (context.modelName !== 'project') {
          // A: No. This role is only for projects: callback with FALSE
          return process.nextTick(() => cb(null, false));
        }
    
        //Q: Is the user logged in? (there will be an accessToken with an ID if so)
        var userId = context.accessToken.userId;
        if (!userId) {
          //A: No, user is NOT logged in: callback with FALSE
          return process.nextTick(() => cb(null, false));
        }
    
        // Q: Is the current logged-in user associated with this Project?
        // Step 1: lookup the requested project
        context.model.findById(context.modelId, function(err, project) {
          // A: The datastore produced an error! Pass error to callback
          if(err) return cb(err);
          // A: There's no project by this ID! Pass error to callback
          if(!project) return cb(new Error("Project not found"));
    
          // Step 2: check if User is part of the Team associated with this Project
          // (using count() because we only want to know if such a record exists)
          var Team = app.models.Team;
          Team.count({
            ownerId: project.ownerId,
            memberId: userId
          }, function(err, count) {
            // A: The datastore produced an error! Pass error to callback
            if (err) return cb(err);
    
            if(count > 0){
              // A: YES. At least one Team associated with this User AND Project
              // callback with TRUE, user is role:`teamMember`
              return cb(null, true);
            }
    
            else{
              // A: NO, User is not in this Project's Team
              // callback with FALSE, user is NOT role:`teamMember`
              return cb(null, false);
            }
          });
        });
      });
    

    Then in you Models, use these roles by their names,

    {
      "accessType": "READ",
      "principalType": "ROLE",
      "principalId": "admin",
      "permission": "ALLOW",
      "property": "findById"
    }
    

    So basically,

    1. you create a few roles
    2. then declare in your model definition what roles have access to access to READ, WRITE or EXECUTE custom (list of) methods
    3. And then use a role resolver which will run for those models which have those roles and decide if the roles apply in the current context of the request.