Hide a field based on a role

default discord avatar
gregwhitworth11 months ago
41

I am wanting to both hide fields as well as alter readonly capabilities in the admin section of a field. I can't seem to get checkRole to work with these fields that expect bools. Here is my code:



admin: { readOnly: Boolean(({ req: { user } }) => checkRole(['guest'], user)), hidden: Boolean(({ req: { user } }) => checkRole(['guest'], user)) },

Thanks in advance.



So what I ended up doing was leveraging was

condition

which works but doesn't seem like the path that I would like to take as there will be scenarios in which I do want them visibly rendered but readonly for certain roles. This gets me around this one usecase for the timebeing. So I'd still like help on the above but if someone wants to completely hide a field from a user based on the role here is how to do that:



admin: { condition: ({user}) => { return !checkRole(['admin'], user) } },


@jesschow @jacobsfletch do you all think this is worth a Github issue?

  • discord user avatar
    jacobsfletch
    11 months ago

    Hey @gregwhitworth it looks like you just you need to use functions for your

    readOnly

    and

    hidden

    properties, right now you have it defined as a constant, i.e. change

    Boolean()

    to

    () => Boolean()
  • default discord avatar
    eustachi010 months ago

    I want to hide a field based on the user role too, but I can't make it work. There is a type error. I have tried adding the boolean type but it doesn't work



    I tried this



    Trying hiding the field using condition, I was having an error, so I wrote some console.log to see what user prints, and it prints undefined. The user data works well i.e when it's called in defaultValue property

    image.png
    image.png
    image.png
  • default discord avatar
    artmelkon10 months ago

    This is how solved the problem. The function will return true when the role is other than 'admin'. The image is hard to read. admin: {


    useAsTitle: 'title',


    hidden: ({ user }) => {


    if (!user.roles.toString().includes('admin')) {


    return true;


    }


    }


    },

    Screenshot_2023-07-25_at_4.51.40_PM.png
  • default discord avatar
    eustachi010 months ago

    Hi, thanks for helping. One question, Is that admin property at the collection level or field level? The code I posted works fine to hide a collection based on the user role but it doesn't work if it's used to hide a field. I'll try your solution later when I'm back to work. Cheers

  • default discord avatar
    artmelkon10 months ago

    I"m using it at the collection level, and I believe it should work at the field level too.

  • default discord avatar
    eustachi010 months ago

    That's what I thought, but for some reason it's not working.

  • default discord avatar
    artmelkon10 months ago

    do you get any error messages or can you console log it?

  • default discord avatar
    eustachi010 months ago

    Yes, I get a type error, and I haven't been able to console.log it because of the type error



    For example, in your code if you console.log(user) you'll see the user data, but if you use the same code in a hidden property at a field level it doesn't work. Maybe I'm missing something

  • default discord avatar
    artmelkon10 months ago

    Good Morning. The documentation, unlike the collection level, doesn't mention that you can use a function at the field level.

  • default discord avatar
    eustachi010 months ago

    This works for me too thanks. Have you managed to make use of the hidden property? I haven't been able to, I get type errors.


    Another option I tried and it worked for me was to use the access property at field level


    ...
      access: {
        // To hide a field using access at a field level
        read: ({req: { user } }) => user.roles.includes("admin"), // only admin user will read
        
        // To make a field read only
        create: create: ({req: { user } }) => user.roles.includes("admin"), // only admin users can create and update
        update: ({req: { user } }) => user.roles.includes("admin"),
      }
    ...
  • default discord avatar
    artmelkon10 months ago

    The only time I use hidden is when I don't want users to see some collections on the dashboard. I use access at the field level revoking the user's ability to edit some specific fields.

  • default discord avatar
    eustachi010 months ago
  • default discord avatar
    sam646610 months ago

    Just throwing this in here, might give you some ideas



    https://gist.github.com/sdjnes/b63e025496cdd0961359f6b03f705425

    We created some helper functions to cleanly define who can see/access collections.

  • default discord avatar
    eustachi010 months ago

    Yes, it seems like this is the workflow to use. Cheers

  • default discord avatar
    gregwhitworth10 months ago

    @artmelkon how are you getting access to { user } at the collection level as it's throwing a type error for me.

  • default discord avatar
    eustachi010 months ago

    Are you using {req: {user}}? If so, use only {user}

  • default discord avatar
    gregwhitworth10 months ago

    @Eustachio I've tried both





    image.png
    image.png
  • default discord avatar
    eustachi010 months ago

    Can you try it like this? This works for me @gregwhitworth

  • default discord avatar
    gregwhitworth10 months ago

    like what?



    if you posted an image it doesn't seem to come through in a reply :/

  • default discord avatar
    eustachi010 months ago

    hidden: ({user}) => (user as any).roles.includes('admin')



    This should work at the collection level



    Sorry I'm typing on my phone. This will return true when the user role is admin, add ! at the beginning to make it return false

  • default discord avatar
    gregwhitworth10 months ago

    Thanks @Eustachio but I am truly baffled. The one on the right has no role, the one on the left has the role of admin and they both get the same result. So I decided to go oldschool and console.log the output and sure enough if I add this:



    console.log('Is admin: '+ (user as any).roles?.includes('admin'))

    It results in:



    Is admin: undefined

    Any ideas as to how it could be undefined?



    Sure enough, looking at the object it results in an object with no roles. So now I'm going to look to see how this is getting removed.



    looking at the field, it is included in the JWT:



     name: 'roles',
                  type: 'select',
                  // Save to JWT so it is passed to the user object in admin hidden fields (indirectly from useAuth hook)
                  saveToJWT: true,
                  hasMany: true,
                  options: [
                    {
                      label: 'Admin',
                      value: 'admin',
                    },
                  ],


    I would like to note that mongodb and the field itself is filled in with the role of admin if they are one

    image.png
  • default discord avatar
    eustachi010 months ago

    Can you console the user object to see what you are getting?

  • default discord avatar
    gregwhitworth10 months ago

    sure @Eustachio

    {email: '<myemailaddress>', id: '647bd27c803c975ea9f20c31', collection: 'users', iat: 1690662787, exp: 1690669987}
  • default discord avatar
    eustachi010 months ago

    So the roles are not present, that's why it throws undefined.



    Is the role field in the users collection?



    I cant see you're saving the roles field to JWT

  • default discord avatar
    gregwhitworth10 months ago

    I know, that's what I said above



    and showed that it is saved to JWT



    https://discord.com/channels/967097582721572934/1124822503659937812/1134944275663179826
  • default discord avatar
    eustachi010 months ago

    So, you can add the roles to the user collection as a relationTo field (if it's not there already)

  • default discord avatar
    gregwhitworth10 months ago

    this is the user collection

  • default discord avatar
    eustachi010 months ago
    Screenshot_20230729-221348.png
  • default discord avatar
    gregwhitworth10 months ago

    I linked to the definition above, saveToJWT is set to true



    thus my confusion :/

  • default discord avatar
    eustachi010 months ago

    Yes, I saw that. For what I understand, the user collection has to be Auth enabled.

  • default discord avatar
    gregwhitworth10 months ago

    it is



    const Users: CollectionConfig = {
      slug: 'users',
      admin: {
        useAsTitle: 'name',
        defaultColumns: ['name', 'email'],
        hidden: ({user}) => {
          console.log(user);
          return !(user as any)?.roles?.includes('admin')
        }
      },
      access: {
        read: isAdminOrSelf,
        create: anyone,
        update: isAdminOrSelf,
        delete: admins,
        admin: ({ req: { user } }) => {
          console.log('Access object: ' + user)
          return checkRole(['admin', 'supplier'], user);
        }
      },
      hooks: {
        afterChange: [loginAfterCreate],
      },
      auth: true,
      fields: UserFields,
      timestamps: true,
    };
    
    export default Users;
  • default discord avatar
    eustachi010 months ago

    I will be back at home soon, I'll check my user collection and share it with you.



    Ok ok. In which collection you have the roles field?



    I can't see any fields on your users collection

  • default discord avatar
    gregwhitworth10 months ago

    The users collection has the roles field

  • default discord avatar
    eustachi010 months ago

    this is my users collection setting


    export const Users: CollectionConfig = {
        slug: "users",
        auth: true,
        admin: {
            useAsTitle: "email",
        },
        access: {
            // Admins can read all, but any other logged in user can only read themselves
            read: isAdminOrSelf,
            // only admins can create users
            create: isAdmin,
            // Admins can update all, but any other logged in user can only update themselves
            update: isAdminOrSelf,
            // only admins can delete
            delete: isAdmin,
            admin: isAdminOrSiteUser,
        },
        fields: [
            {
                type: "row",
                fields: [
                    {
                        name: "firstName",
                        type: "text",
                        required: true,
                    },
                    {
                        name: "lastName",
                        type: "text",
                        required: true,
                    },
                ],
            },
            {
                name: "roles",
                // Save this field to JWT so we can use from `req.user`
                saveToJWT: true,
                type: "select",
                hasMany: true,
                defaultValue: ["editor"],
                access: {
                    // only admins can create or update a value for this field
                    create: isAdminFieldLevel,
                    update: isAdminFieldLevel,
                },
                options: [
                    {
                        label: "Admin",
                        value: "admin",
                    },
                    {
                        label: "Editor",
                        value: "editor",
                    },
                ],
            },
        ],
    };


    this is what I get from the console

    image.png
  • default discord avatar
    gregwhitworth10 months ago
    // import payload from 'payload';
    import type { CollectionConfig, FieldHook } from 'payload/types';
    
    import { admins } from '../../access/admins';
    import { anyone } from '../../access/anyone';
    import { isAdminOrSelf } from '../../access/isAdminOrSelf';
    import { slugField } from '../../fields/slug';
    import hideCollectionFor from '../../utilities/hideCollectionFor';
    import { checkRole } from './checkRole';
    import { loginAfterCreate } from './hooks/loginAfterCreate';
    
    const checkFirstUser: FieldHook = async ({ siblingData, data, value, req }) => {
      const users = await req.payload.find({ collection: 'users' });
      if (users.totalDocs > 0) return data?.role || value;
      if (users.totalDocs === 0) {
        siblingData.roles = ['admin'];
      }
    };
    
    const UserFields: CollectionConfig['fields'] = [
      {
        type: 'tabs',
        tabs: [
          {
            label: 'Account',
            fields: [
                {
                  name: 'name',
                  type: 'text',
                },
                {
                  name: 'roles',
                  type: 'select',
                  // Save to JWT so it is passed to the user object in admin hidden fields (indirectly from useAuth hook)
                  saveToJWT: true,
                  hasMany: true,
                  options: [
                    {
                      label: 'Admin',
                      value: 'admin',
                    },
                  ],
                  hooks: {
                    // beforeChange: [checkFirstUser, protectRolesBeforeCreate],
                    beforeChange: [checkFirstUser],
                  },
                  access: {
                    create: admins,
                    update: admins,
                  },
                },
            ]
          },
        ]
      }
    ];
    
    const Users: CollectionConfig = {
      slug: 'users',
      admin: {
        useAsTitle: 'name',
        defaultColumns: ['name', 'email'],
        hidden: ({ user }) => hideCollectionFor(user, ['supplier', 'customer', 'host']),
      },
      access: {
        read: isAdminOrSelf,
        create: anyone,
        update: isAdminOrSelf,
        delete: admins,
        admin: ({ req: { user } }) => checkRole(['admin', 'supplier'], user),
      },
      hooks: {
        afterChange: [loginAfterCreate],
      },
      auth: true,
      fields: UserFields,
      timestamps: true,
    };
    
    export default Users;


    To simplify this I've removed a number of fields that we track for the users but this is my user collection in general



    That's while you'll only see one tab, whereas we actually have 3 for profile photos, orders, etc

  • default discord avatar
    eustachi010 months ago

    I think you need to bring your UserFields inside the User collection



    From your previous console log of the user object, none of the fields in the UserFields have been registered, I think you might have to add this in your User collection: (I don't guarantee this is the solution)


    ...
        timestamps: true,
        fields:  UserFields,
    ...


    Apologies, I missed that you already did this, but I think these fields are not being registered correctly within the User collection



    here it is



    add the saveToJWT to the top level in to the tabs field as roles is nested within, I replicated your setup and it works



    edit: this doesn't apply, see below explanation



    I think there's a bug with Payload, and I think there's nothing wrong with your code. If you login and you console.log the user object without refreshing the browser, the roles array won't come up, but if you refresh the browser the roles array will show. Screenshot without a refresh:


    no _strategy: "local-jwt"



    Now, screenshot after a browser refresh, the roles array is there as well as _strategy: "local-jwt"



    I have had a similar issue reading a value from a nested field within the user object.

    https://discord.com/channels/967097582721572934/1133806906968907806

    I think this is happening since one of the latest updates. But, I might be wrong and it's possible that it's something I'm missing 🤔



    This works (see screenshot). Moving the roles field to a top level position instead of being nested within a tab field.



    Reading again the docs, the field to be savedToJWT has to be top level, so it seems like it can't be nested within other fields. Pls, someone corrects me if I'm wrong... (so, maybe there's not a bug and this is the normal behaviour)

    image.png
    image.png
    image.png
    image.png
  • default discord avatar
    gregwhitworth10 months ago

    @Eustachio actively working in another branch but this would make sense since this

    was

    working at some point and then broke. I'll file a bug once I confirm this works as expected

  • default discord avatar
    eustachi010 months ago

    Cheers. Yes, it was working fine for me before as well.

  • default discord avatar
    gregwhitworth10 months ago

    @Eustachio alas that did not fix it. I completely removed the tabs, moved all of the fields directly into the fields area, etc to no avail. Are you working on a next project or directly with payload alone?

  • default discord avatar
    eustachi010 months ago

    Ah, I work with payload alone. Have you checked that when you refresh the browser if the jwt fields come up?



    Hi, I did a test today, I console.log the user object from the hidden property (which runs on the client )at a collection level and another console.log for the user from the access property (which runs on the server). The test is done on first load or after login without any page refresh. Please see screenshot


    Edit: As you can see in the screenshot the user data on the client doesn't show the roles field. I used the roles field nested back within a tab field as per your original code for this test.



    The data from the server is correct. I guess that it's better to hide a collection using access control as the data is more accurate.



    @gregwhitworth I'll be interested to see if you run a console.log on the server if you get the roles field. Screenshot shows how I run the test

    image.png
    image.png
  • default discord avatar
    puetzz4 months ago

    Hello, sorry for opening up this old topic but did anyone manage to get the initial request to work? I'm talking about how to hide one field (column) of a collection using the hidden property with a function.

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.