I'm trying to follow along with the docs and receive an ID with my access control functions.
I'm expecting the req
and id
parameter, but am only seeing req
. It looks like the URL is /access
and not the collection I'm checking (This is when loading the update page).
Additionally, is there any way to figure out what collection I'm in on an access control method? I can see the user, but I'd also like to do more advanced checks depending on which collection I'm on.
Hey @ryanlanciaux — I can give some insight here.
The id
parameter is optional and only going to be present in some access control methods, in certain cases. For example, there is no id
yet in create
access control.
Also—you're seeing your access control function being called from the access
operation, which goes through and calls all access control through all your collections and globals. But, while your access control functions are being called by the access
operation, there will be no ID present there either.
The access
operation is responsible for telling the admin UI what you can and can't do. You can hit it in your browser by going to /api/access
to see what it looks like.
Also, in regards to knowing what collection you're in, I would recommend abstracting your access control functions in a way that allows you to pass them the collection that you're on so you have full control. Like this:
export const accessControl = ({ req }) => {
// how do we know what collection we're in?
return true; // simple example
}
import { accessControl } from './access';
const pages = {
slug: 'pages',
access: {
read: accessControl,
}
}
You're probably sharing a function like this through many collections, and within that function there is no way to know what collection you're on.
Instead, you could do this:
export const getAccessControl = (collectionSlug) => ({ req }) => {
// Now we know that our collection is `collectionSlug`
return true; // simple example
}
import { getAccessControl } from './access';
const pages = {
slug: 'pages',
access: {
read: getAccessControl('pages'),
}
}
Does that make sense? There are many other patterns of abstraction that can work here but this is the one we'd generally use.
Also - can I ask what you need the ID of the document for? Are you familiar with the nature of how returning query constraints
can help you write super clean access control functions? In about 99% of cases you can do what you need with returning a query constraint instead of dealing directly with a document ID.
Thanks, I think it makes sense. In regard to the ID, I may need to describe the configuration I'm currently trying to have admins, and organization admins.
// kind of pseudocode - not entirely valid
const Users = {
role: 'Admin' | 'OrganizationAdmin',
// conditional based on admin or organizationAdmin
organizations: { type: 'relationship', hasMany: true, name: 'organizations', relationTo: ['organizations'] }
}
const Organizations = {
... fields for organizations, there are some blocks here too (most of my confusion is here)
}
Against organizations, when I write a query, it always is showing up. Initially I attempted to handle this with a query somewhat similar to the following (accepting user off of req
)
return {
id: {
in: userOrganizationIds // array
},
}
This always provides access to update the fields when I'm expecting it should be like returning false
from the method.
This is where I'm trying to obtain the ID and the collection I'm in. It feels like an unideal way to achieve the result I'm looking for, but the thinking is I could manually perform the query with the payload.find
and return true / false depending on the results.
OK I'm following what you're doing.
I think it would be good to see your whole access control function code. This may just be a case of making sure that you are reducing down your req.user.organizations
to their IDs only.
Take a look at the following documentation regarding how relationship data is saved:
https://payloadcms.com/docs/fields/relationship#how-the-data-is-saved
You'll see that because you have your relationTo
property set to an array of values, your data will be saved as the Has Many - Polymorphic
section from the docs. Like this:
const user = {
// the rest of the user here
organizations: [
{
relationTo: 'organizations',
value: '34jl34ij53l4ijrl34ijr43', // some ID
},
{
relationTo: 'organizations',
value: '34jl34ij53l4ijrl34ijr43', // some ID
},
]
}
Which means that your access control should be formatted like this:
export const restrictByOrganization = ({ req }) => {
if (req.user && Array.isArray(req.user.organizations)) {
// As you are saving your user orgs as `hasMany: true` and `relationTo` as an array,
// we need to reduce down the user organizations to only their values
const orgIDs = req.user.organizations.reduce((ids, org) => {
return [
...ids,
org.value,
]
}, []);
// Now we'll end up with an array of IDs appropriately
// and can use the IDs to build a query constraint
return {
id: {
in: orgIDs
}
}
}
return false;
};
Is this where your problem lies? Are you sure you're returning an array of IDs only?
Thanks a ton - This works. I think reducing the values made a difference as opposed to trying to query the items directly.