teunmooij / payload-tools

Collection of payload plugins and tools: payload-openapi, payload-swagger, create-payload-api-docs, payload-rbac
https://github.com/teunmooij/payload-tools/#readme
MIT License
101 stars 12 forks source link

[Feature request]: Add support for `roles` field access configuration #82

Open joshuaja opened 8 months ago

joshuaja commented 8 months ago

Package name

payload-rbac

Description

I need to prevent a user from changing their own user Roles and am currently doing this via the Collection-level access configuration on the User collection.

Field-level access configurations solve for this - inaccessible fields are grayed out in the Payload Admin view and it skips adding the field in the request body.

Without field-level access control, the user gets an error when trying to save their user record within the User edit screen (because Payload is adding the roles field to the request body even though it hasn't been changed in the UI), so they are never able to update their user in the Payload Admin view due to this.

If you do try to add a roles field to the Collection, you get this error: Unable to enable payload-rbac on collection ${collection.slug}: collection already has a 'roles' field!.

We need the ability to gray out the Roles select control on the Admin edit screen (via supporting Field-level access configuration).

Is there a way to merge the roles field configuration from the collection into the payload-rbac roles configuration so it can leverage the access field property?

image
ndcollins commented 5 months ago

@teunmooij related to this would also be great if, as part of this configuration, we could specify hasMany on this field, similar to relationships, such that if we only want the user to be assigned a single role rather than many, we can do so.

abernh commented 1 month ago

My current workaround is to re-enhance the config after it was passed through the rbac plugin. Crude but works.


export const pluginRbac = rbacWithRolesAccess({
    collections: ['users'],
    roles: ['admin', 'editor'],
    fieldAccess: {
        create: ({req: {user}}) => user?.roles.includes('admin'),
        update: ({req: {user}}) => user?.roles.includes('admin'),
    }
})

function rbacWithRolesAccess(options: Options & { access: Record<string, FieldAccess> }): (config: Config) => Config {
    return (incomingConfig) => {

        const config: Config = rbac(options)(incomingConfig)

        if (options.fieldAccess) {
            config.collections.forEach(col => {
                if (!options.collections.includes(col.slug))
                    return;

                const rolesField: Field | null = col.fields.find(f => f.name === 'roles')
                if (!rolesField)
                    return

                rolesField.access = {
                    ...options.fieldAccess
                }
            })
        }

        return config
    }
}

// payload.config

{
    ...
    plugins: [
         pluginRbac
    ]
    ...
}

This was just a quick copy paste ... hope I didn't miss anything ... if so, I guess you still get the gist.