Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

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

default discord avatar
hunghvu2 years 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!

  • 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 dedicated engineering support directly from the Payload team.