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

default discord avatar
hunghvu
2 months ago
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!

  • default discord avatar
    hunghvu
    last week

    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;
Open the post
Continue the discussion in GitHub
Like what we're doing?
Star us on GitHub!

Star

Connect with the Payload Community on Discord

Discord

online

Can't find what you're looking for?

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