How to build an access control list using query on a relationship fields?

default discord avatar
hunghvulast year
2 1

Let's say I have 3 collections: Roles, Articles, and Users.

  • One user has many roles (one-to-many relationship).
  • One article can be viewed by multiple roles (one-to-many relationships).
// Users collection
{
  slug: "users"
  ....
  fields: [
    {
      name: "roles"
      type: "relationship"
      relationTo: "user-roles" // User Roles collection
      hasMany: true,
    }
  ]
}

// Articles collection
{
  slug: "articles"
  ....
  fields: [
    {
      name: "whoCanView"
      type: "relationship"
      relationTo: "user-roles"
      hasMany: true,
    }
  ]
}

Now, if the field is string, then the comparison is relatively easy, as below.

const query = {
  whoCanView: {
    equals: "user.roles"
  }
}

However, I was not able to find a sample for a many-to-many comparison. Even one-to-one (hasMany: false) fails when the compared fields are of type relationship. I suppose this is because relationships are generic objects, not regular strings. When a request comes in, user.roles can be read pretty straightforwardly. If there is a way to expose article.whoCanView without using a query, it would be much easier to implement a custom comparison.

With that said, can someone shed a light on this matter for me? Thanks!

  • Selected Answer
    default discord avatar
    hunghvulast year

    It just came to my mind that I have not updated the solution on this.


    When a record hasMany relations, the related objects are referenced by ID. For example, if an article can be read by front-desk or management, then the database is represented as followed.

    articleNo1: {
      whoCanRead: [
        0: "123",
        1: "456",
      ]
    }
    
    roles: {
      front-desk: {
        id: "123"
      },
      management: {
        id: "456"
      },
    }
    

    Hence comparison should be based on object.id, not object.otherValue.


    Each query is considered an object itself, we can construct a dynamic query for many-to-many comparisons. Let's have a sample context here:

    • John has two roles: Front-desk and Management
    • Article can be read by two roles: Tech support and Management
    • The article can be read by John because he has the Management role.
    // read access function
    ...
    const query = {
      or: []
    }
    for (let role of user.roles) {
      query.or.push({
        recordReadAccess: {
         contains: role.id
       }
      }
    }
    // In other words, allow read access if the record contains any of the user roles
    // => Contains "Management" or "Front-desk"
    return query;
Star on GitHub

Star

Chat on Discord

Discord

online

Can't find what you're looking for?

Get help straight from the Payload team with an Enterprise License.