This is related to both The road to stable, then permanent, for granular_anonymous_and_logged_in_groups_permissions and Granular group-based permissions for anonymous and logged in users
In core as well as many themes and plugins, a pattern like this has become quite common:
const groupIds = this.currentUser.groups.map((g) => g.id);
const allowedGroupIds = this.siteSettings.some_group_setting.split("|").map((groupId) => parseInt(groupId, 10));
const hasPermission = allowedGroups.some((groupId) =>
userGroupIds.includes(groupId)
);
if (!hasPermission) {
return;
}
However, this is not an effective way of checking user permissions. A user can be a member of groups that are not visible to them, so they are not serialized to the client, and thus cannot be used consistently or accurately for security checks.
To make this more obvious, we are renaming currentUser.groups/user.groups to currentUser.visibleGroups/user.visibleGroups in the User model and deprecating the old property. The initial PR to do this is DEV: Deprecate calling user.groups on client directly - Pull Request #42711 - discourse/discourse - GitHub .
There are several alternatives if you need to check a user’s permission based on a list of group IDs on the client in JavaScript:
For plugins
Extend the current_user serializer with a new attribute, and check the user’s permissions with scope.in_any_groups? server-side, which also covers pseudogroups like logged_in_users and anonymous_users:
add_to_serializer(
:current_user,
:has_some_permission,
include_condition: -> do
SiteSetting.plugin_enabled
end,
) { scope.in_any_groups?(SiteSetting.group_list_setting_map) }
Then you can do this.currentUser.has_some_permission on the client.
For themes and components
For list type theme settings with list_type: group, you can use resolve_group_membership: true:
copy_button_allowed_groups:
default: "1|3"
type: list
list_type: group
resolve_group_membership: true
This will replace settings.copy_button_allowed_groups on the client with settings.user_in_copy_button_allowed_groups (prefixing the setting with user_in_) which is a boolean calculated on the server side based on the user’s group memberships.
This also works for object settings can with type: groups . Add resolve_group_membership: true to the groups property:
menu_sections:
type: objects
default:
- name: section 1
groups:
- 1
- 3
schema:
name: menu section
properties:
name:
type: string
groups:
type: groups
resolve_group_membership: true
Then access looks like this:
for (const section of settings.menu_sections) {
if (section.user_in_groups) {
// User is in at least one selected group for this section.
}
}