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