Making sense of CLP and ACL when using cloud functions

Iā€™m trying to secure my user management system, in which only the users with Role Administrator can assign users to certain Roles.

Iā€™m using this cloud function to changes roles based on a role name and user id, which works fine:

// change user role
Moralis.Cloud.define('changeRole', async (request) => {
  const userQuery = new Moralis.Query(Moralis.User);
  userQuery.equalTo('objectId', request.params.userId);
  const userObject = await userQuery.first({useMasterKey:true});
  const roleQuery = new Moralis.Query(Moralis.Role)
  const roles = await roleQuery.find({ useMasterKey: true })
  for (let i = 0; i < roles.length; i++) {
    if (roles[i].get('name') === request.params.roleName) {
      logger.info(roles[i].get('name'))
      roles[i].getUsers().add(userObject)
    } else {
      roles[i].getUsers().remove(userObject)
    }
    roles[i].save()
  }
  return request.params.roleName
},{
  fields : ['roleName', 'userId'],
  requireUser: true
})

But I only want users with the Administrator role to be able to change Role data.
Iā€™ve tried 2 things:

  1. setting CLP to ā€˜Role: Administratorā€™ with full access
  2. setting the ACL on all Roles to ā€˜Role: Administratorā€™ with full access

None of these work, Iā€™m not able to write when those are set, only when everything is set to Public access.

How do you use/identify Roles in cloud functions or should I use this client side?

Also, can you validate a cloud function on a Role, something like requireRole: ā€œAdministratorā€?

Hey @matiyin
Thank you for your patience :open_hands:

I recommend you take a look at Moralis Security.
To prevent class changes from the outside, I recommend you configure your CLI. You need to create a new role for example admin and give it an access to modify data.
image

For cloud functions, you can use simple checking like:

Moralis.Cloud.define('_User', request => {
  const user = request.object;
  if (user.get("role") !== "admin") {
    throw "No access";
  }
});

No worries I understand :slight_smile:

As I said here Add more examples for Roles / Security documentation it took me quite some time to figure out how to use it all.

Iā€™m looking for the best way to validate the user and userā€™s role securely in a cloud function like this:

Moralis.Cloud.define('changeRole', async (request) => {
  // do stuff with request.user?
})

Since the request does not have a request.object nor a request.user object with any methods, I cannot use your example.
What I did here to get all users and their role:

// get all users
Moralis.Cloud.define('getUsers', async (request) => {
   	const query = new Moralis.Query('User')
    query.descending('createdAt')
    const results = await query.find({ useMasterKey: true })
    const users = []
    for (let i = 0; i < results.length; i++) {
      // get number of NFTs
      const token = new Moralis.Query(request.params.network+'NFTOwners')
      token.equalTo('contract_type', 'ERC721')
      token.containedIn('owner_of', results[i].attributes.accounts)
      query.equalTo('token_address', request.params.tokenAddress)
      token.descending('createdAt')
      const tokens = await token.find({ useMasterKey: true })
      // get role for this user
      const roleQuery = new Moralis.Query(Moralis.Role)
      roleQuery.equalTo('users', results[i])
      roleQuery.include('user')
      const roles = await roleQuery.find({ useMasterKey: true })
      const role = results[i].get('role')
      users.push({
        'moralisId': results[i].id,
        'owner_of': results[i].attributes.accounts,
        'createdAt': results[i].createdAt,
        'username': results[i].attributes.username,
        'avatar': results[i].attributes.avatar ? results[i].attributes.avatar.url() : null,
        'num_of_tokens' : tokens ? tokens.length : null,
        'roles': roles,
        'role': role
      })
    }
    return users
},{
  fields : ['network', 'tokenAddress'],
  requireUser: true
})

Works fine, but is not secure for actual validation. In my first example, I want to validate if the cloud request is coming from a legit user (which i do with ā€˜requireUser:trueā€™) AND if this user belongs the the Role ā€˜Administratorā€™.
Hence my question: can we do a validation like:

{ requireUser: true, requireRole: ['Administrator','Editor'] }

If not, how else in a secure way? Just checking for userId per request seems not secure.
Hope that makes sense.

Hey @matiyin

You can make extra validation with request parameter. But you have to do some magic with it :mage:

Moralis.Cloud.define('getUsers', async (request) => {
  const userWhoCalledFunc = JSON.parse(JSON.stringify(request)).user;
});

Now userWhoCalledFunc has methods like objectId or even ip. Use them for validating user.
Let me know how it works for you :wink:

Not sure. Can request.user be trusted for validation? I see that a _SessionToken is sent in the request, so I assume the validation of the user is there?
If so I can indeed use the id to look up the user object.

Iā€™ve finally made this role validation for cloud functions:

async function validateUserRole(user, role) {
  const roleQuery = new Moralis.Query(Moralis.Role)
  roleQuery.equalTo('name', role)
  roleQuery.equalTo('users', user)
  return await roleQuery.first({ useMasterKey: true })
}

// get all users
Moralis.Cloud.define('getUsers', async (request) => {
    // validate user role
    if (!await validateUserRole(request.user, 'Administrator')) throw "No access"
   // do you stuff here if user role is validated
})

Any feedback welcome :slight_smile:

1 Like