Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessing current user roles #424

Open
BibiSebi opened this issue Oct 14, 2024 · 16 comments
Open

Accessing current user roles #424

BibiSebi opened this issue Oct 14, 2024 · 16 comments
Assignees
Labels
enhancement New feature or request

Comments

@BibiSebi
Copy link
Contributor

Is your feature request related to a problem? Please describe.
The developer of a plugin would like to show/hide parts of the plugin based on the current user's roles. This is not possible at the moment as we are not passing the user information to the field plugin.

Describe the solution you'd like
useFieldPlugin() could expose the current user's roles and id. However we need to check if this can be exploited in any way.

Additional context
This request originates from a Discord thread.

@marckraw
Copy link

marckraw commented Oct 14, 2024

We're developing and maintaining a suite of plugins for our in-house "site builder" platform, which is powered by Storyblok.

As our plugins grow in complexity, we've identified a need to leverage the roles and permissions systems. This would allow us to dynamically show or hide elements within our plugins based on specific user access levels.

Consider a scenario where we have a plugin that displays various options depending on the user's role. To implement this functionality effectively, we need a way to access information about the user's assigned role or individual permissions within Storyblok. For instance, if a user has publishing rights for stories, we might want to present them with an expanded set of options within our plugin.

@marckraw
Copy link

Also, our specific use case is, that we have our own set of "fields" within custom plugin :) So based on the permissions we would like to show our internal plugin fields or not:
CleanShot 2024-10-14 at 17 28 52@2x

@pedrosousa13
Copy link

This would be great

@marckraw
Copy link

created the support ticket: https://support.storyblok.com/hc/en-us/requests/13886 this is becoming extra critical for us @BibiSebi

@Dawntraoz
Copy link
Contributor

Resend your request internally, so we can consider this for development sooner. cc @demetriusfeijoo

@Dawntraoz Dawntraoz self-assigned this Dec 12, 2024
@Dawntraoz Dawntraoz added the enhancement New feature or request label Dec 12, 2024
@Dawntraoz
Copy link
Contributor

In the latest package release, the userId was added back:

cc @marckraw. Should we keep it open for the roles, or can you make the proper request with the user ID to grab that information?

@marckraw
Copy link

@Dawntraoz amaaaaaazing! will test it right away today 🥇

@marckraw
Copy link

@Dawntraoz would be great if we can get the roles already, but we can start working with userId, will update comment after i test it today :)

@marckraw
Copy link

@Dawntraoz tested it out, and its there, thank you <3 If its possible, we would like to also have a user roles in there - right now, having the userId for our exploration phase we can find out what are his roles with management api, but perfect use case would be to have that information without the need to make management api request ourselves :)

@Dawntraoz
Copy link
Contributor

Thanks for checking you are the best! 🎉

Good news from our side also @marckraw, in the plugin we are building now we will need to get the roles too, so we will take the time to make it available for the SDK 🙏 It's coming 💃🏻

@marckraw
Copy link

@Dawntraoz that is amazing! Looking forward to it :)
what information from the overall roles situation in storyblok you would like to expose there ? the role name, or all the information about permissions, field_permissions ?

Was working on getting the roles for the user with management api, but it involved more back and forth than I expected :P

  • Right now, having a userId I know who is seeing the plugin, but to get his roles i have to do a request for the space and space roles
  • then I need to check if he is the owner of the space or collaborator (as i checked out, owner of the space is not on the collaborators list so i have to look for his roles information in different place)
  • if the user is the collaborator, i have to check if he has multi role or single and then merge fields from both roles, from space_roles endpoint.

Here is more or less what I do in the plugin to get all this info:

const determinePermission = async (args: DeterminePermissionArgs): Promise<Permission | Permission[]> => {
        const {plugin, store} = args;

        if (store.apis?.managementApi) {
            const currentSpaceDetails = await store.apis.managementApi.get(`/spaces/317084/`)
            const spaceRoles = currentSpaceDetails.data.space.space_roles

            // 1. check if the user is the owner of the space - which mean he has admin permissions
            const isOwner = currentSpaceDetails.data.space.owner.id === plugin?.data?.userId
            if(isOwner) {
                // If its owner, we return the role of the space, which is the role of the owner I think..
                return {
                    role: currentSpaceDetails.data.space.role,
                    permissions: null,
                    field_permissions: null,
                    space_role_ids: null,
                    space_roles: null
                }
            }

            // If it's not owner, we check the collaborators
            const userRole = currentSpaceDetails
                .data
                .space
                .collaborators
                .find((collaborator: any) => collaborator.user.id === plugin?.data?.userId)

            if(userRole) {
                // We found the user in the collaborators
                // Now we need to check what exact role he has
                // if its multi or single or what
                if(userRole.role === "multi") {
                    const userSpaceRoles = spaceRoles.filter((spaceRole: any) => userRole.space_role_ids.includes(spaceRole.id))

                    const mergePermissions = userSpaceRoles.reduce((acc: string[], role: any) => [...acc, ...role.permissions], [])
                    const mergeFieldPermissions = userSpaceRoles.reduce((acc: string[], role: any) => [...acc, ...role.field_permissions], [])
                    return {
                        role: "multi",
                        space_roles: userSpaceRoles,
                        permissions: mergePermissions,
                        field_permissions: mergeFieldPermissions,
                        space_role_ids: userRole.space_role_ids
                    }
                } else {
                    if(userRole.space_role_id) {
                        const userSpaceRole = spaceRoles.find((spaceRole: any) => userRole.space_role_id === spaceRole.id)                   
return {
                        role: userRole.role,
                            permissions: userSpaceRole.permissions,
                            field_permissions: userSpaceRole.field_permissions,
                            space_role_ids: [userRole.space_role_id],
                            space_roles: userSpaceRole
                        }
                    } else {
                        return {
                            role: userRole.role,
                            permissions: null,
                            field_permissions: null,
                            space_role_ids: null,
                            space_roles: null
                        }
                    }
                }
            } else {
                return {
                    role: null,
                    permissions: null,
                    field_permissions: null,
                    space_role_ids: null,
                    space_roles: null
                }
            }
          }
          return {
            role: null,
            permissions: null,
            field_permissions: null,
            space_role_ids: null,
            space_roles: null
        }
    }

At least that was my flow, but for sure i'm missing something :D

Anyway, would be nice to know, what is your idea of what will you exposed, so we can prepare ourselves accordingly :)

@Dawntraoz
Copy link
Contributor

Hi @marckraw 👋 We are currently finishing a solution that may cover your company's needs, we decided to create an action called "requestUserContext" that will return a user object with the properties: isSpaceAdmin boolean and permissions JSON, something like this:

image

Initially, it won't be returned; you need to call it and await the result to get it, so it's not in the load process of the plugin but an additional feature, so it doesn't affect the plugin performance 🙏 Will this cover all your needs?

@marckraw
Copy link

marckraw commented Jan 6, 2025

Hey @Dawntraoz super nice to hear it!

It’s great to hear from you—thanks for taking care of it! 😊 I have a 2 more questions:

  • Is it possible to also return the name of the role?
    Having access to the role name would be super helpful for implementing custom permissions based on roles we assign to users.

  • Can we add custom permission strings to the permissions array (or field_permissions or any other property)?
    Or is the backend strictly validating these values? For example:

permissions = [
    "show_subfield1",
    "show_subfield2"
];

Here’s why I ask: In our plugin, we have subfields we’d like to show or hide depending on the role. If we can retrieve the role name, we can implement this logic directly in our plugin. But if the roles themselves could store these custom strings and return them, it would simplify things even more.

For context, we mostly create roles through the Management API (using sb-mig) during space setup. Here’s an example of our configuration for the only-content-editor custom role:

import type { StoryblokRolesSchemaBase } from '@/src/storyblok/schema-types';

const OnlyContentEditorRole: StoryblokRolesSchemaBase = {
  resolved_allowed_paths: [],
  allowed_paths: [],
  field_permissions: [
    'sb-accordion.tab_design',
    'sb-accordion-flex-group.tab_design',
    'sb-accordion-section.tab_design',
    'sb-animated-icon.tab_design',
    'sb-breadcrumbs.tab_design',
    ...
    ...
    ...
  ],
  permissions: [
    'read_stories',
    'save_stories',
    'publish_stories',
    'delete_stories'
  ],
  role: 'only-content-editor',
  subtitle: 'User that only edits content - doesn’t have access to the design tab',
  datasource_ids: [],
  component_ids: [],
  branch_ids: [],
  allowed_languages: [],
  asset_folder_ids: [],
  ext_id: null,
};

export default OnlyContentEditorRole;

If we could extend the permissions array with custom strings, like so:

permissions: [
    'read_stories',
    'save_stories',
    'publish_stories',
    'delete_stories',
    'show_subfield1',
    'show_subfield2',
];

…it would make things perfect for our use case! Let me know if this is feasible :P

Thanks a lot for your help! :)

@marckraw
Copy link

marckraw commented Jan 6, 2025

@Dawntraoz I just checked that it doest not validate hard on backend and i can send some arbitrary strings to permissions array. But want to double check if it will not be validated harder in the future :D becasue that's actually solving all our cases I think.

@Dawntraoz
Copy link
Contributor

Hi @marckraw 👋 Let me answer your questions:

Is it possible to also return the name of the role?

What is the use case here if the permissions are already based on the user roles 🤔 Aren't they sufficient?

Can we add custom permission strings to the permissions array (or field_permissions or any other property)?
Or is the backend strictly validating these values?

That's more of a product API question; I'm afraid the Field Plugin doesn't have control over it. We are just representing here what the product API is returning, so if you can add them and get them with the MAPI, then the plugin will showcase the same ones you got.

PS: If you want to be sure if this will be validated harder in the future, I recommend you to open a question query in Support, so they can answer you with the right information 🙏

@marckraw
Copy link

marckraw commented Jan 6, 2025

@Dawntraoz

Hi @marckraw 👋 Let me answer your questions:

Is it possible to also return the name of the role?

What is the use case here if the permissions are already based on the user roles 🤔 Aren't they sufficient?

The issue we’re facing is that our plugin introduces its own concept of “subfields,” which aren’t recognized by Storyblok’s permission system. Because of this, we can’t leverage Storyblok’s GUI to manage which of these subfields should be shown or hidden for specific roles.

  • Using role names
    We could rely on Storyblok role names and maintain a mapping in our plugin. For example, we’d create an object that defines which subfields to show or hide for each role name. While this works, it’s a bit indirect and not ideal.

  • Adding custom permission strings
    If we could add custom strings to the permissions array, this would simplify things significantly. Using the requestUserContext action, we could easily check if specific permissions exist and adjust what’s shown in the plugin accordingly.

To provide some context, here’s a snapshot of what our situation looks like. This Design field is a custom field plugin, and inside it, we have additional subfields:

CleanShot 2025-01-06 at 11 40 00@2x

Not sure if I've explained it enough :D I can also record some screencast or we can hop on a call and I can show you in details.

So yeah, returning the name of the role will give us some way to add some more permissions if we need to. Still we can get the role name using management api workaround, but if you would add it to the response we would have 100% use cases covered.

Anyway, thank you for investigating it even further :)

Can we add custom permission strings to the permissions array (or field_permissions or any other property)?
Or is the backend strictly validating these values?

That's more of a product API question; I'm afraid the Field Plugin doesn't have control over it. We are just representing here what the product API is returning, so if you can add them and get them with the MAPI, then the plugin will showcase the same ones you got.

Got it 👍🏻

PS: If you want to be sure if this will be validated harder in the future, I recommend you to open a question query in Support, so they can answer you with the right information 🙏

Will do that too then :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants