Search code examples
node.jsloopbackjsstronglooploopback

Loopback how to fetch data from User base model


I'm learning Loopback and I want to know the best loopback practices. I have a member model based on user default model and a follow model. A member can have many followers.

{
  "name": "Member",
  "base": "User",
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "nickname": {
      "type": "string"
    }
  },
  "validations": [],
  "relations": {
    "messages": {
      "type": "hasMany",
      "model": "Message",
      "foreignKey": ""
    },
    "followers": {
      "type": "hasMany",
      "model": "Member",
      "foreignKey": "followeeId",
      "through": "Follow"
    },
    "following": {
      "type": "hasMany",
      "model": "Member",
      "foreignKey": "followerId",
      "through": "Follow"
    }
  },
  "acls": [
    {
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "$everyone",
      "permission": "ALLOW"
    }
  ],
  "methods": {}
}

Here is my Follow model

{
  "name": "Follow",
  "base": "PersistedModel",
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {},
  "validations": [],
  "relations": {
    "follower": {
      "type": "belongsTo",
      "model": "Member",
      "foreignKey": ""
    },
    "followee": {
      "type": "belongsTo",
      "model": "Member",
      "foreignKey": ""
    }
  },
  "acls": [],
  "methods": {}
}

I want the member to be able to fetch its followers but what is the best way to achieve that:

  • Should I modify the default User ACL to be able to fetch data from Member with /Members/{id}/followers (which is currently blocked) ?
  • Should I create a new remote method for member to achieve this? What is the best way to do that ? Should i check the member identity like I did for another method with the accesstoken (example below)?

    Professionaldemand.createDemand = function (accessToken, cb) {
    //Rejection if not an authenticated User
    if (!accessToken) {
        return errorUtility.CallbackError("Access Token is not valid", 400, cb);
    }
    
    var currentDate = new Date();
    Professionaldemand.create({ Acceptation: false, CreationDate: currentDate, memberId: accessToken.userId },
        function (err) {
            if (err) {
                return errorUtility.CallbackError("Professionaldemand creation error", 400, cb);
            }
        }, cb(null));
    };
    

Method definition:

Professionaldemand.remoteMethod(
    'createDemand', {
        http: {
            path: '/createDemand',
            verb: 'post'
        },
        description: ["Permits client to ask for a professional permission"]
        ,
        accepts: [
            {
                arg: 'access_token',
                type: 'object',
                http: function (ctx) {
                    return ctx.req.accessToken;
                }
            }
        ]
    }
);

Solution

  • Your model definition and relation look pretty good.

    What needs to be better separated is the access control from the business logic.

    Access control needs to be enforced with the acls property inside each model definition. It should not be handled by your remote method, because otherwise you will need to repeat the code for each remote method, and you probably have better things to do than that :)

    ACLS

    First, for each model, it is good practice to DENY everything to everyone. It is a good practice to ensure that you're not leaving holes inside your API.

    Then, authorize a particular type of user to each specific route or method. It works because very specific acl rule has priority over broad acl rule.

    Ex:

    1. Member all methods all users Deny
    2. Member __addFollowower__ $owner Allow

    This way the remote method Member.addFollower will only be allowed for the Member $owner, basically the Member himself. He won't be able to add a follower on behalf of someone else.

    Fetching followers

    I want the member to be able to fetch its followers but what is the best way to achieve that. [...] Should I create a new remote method for member to achieve this ?

    In this case you don't necessarily need to create a remote method, in fact it would be redundant with your relation-generated method. You can simply call

    GET api/Member/:id/followers?access_token=e23saa2ragkuljkh2...

    If you don't want to keep track of the :id client-side, you can also use

    GET api/Member/me/followers?access_token=e23saa2ragkuljkh2...

    In this case, loopback will use the access token to determine the id of the logged in user, and replace me by this id.

    PS: This works under the condition that you configured loopback to support this.