Deprecating the user of `user.groups` in the `User` model in JavaScript

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.
  }
}

Hi Martin,
Will these changes take effect when the PR merges, or with the next update after the PR merges?

Why does this matter? This won’t break anything. It will simply show a warning in the browser console, alerting developers to adjust their code.

I have theme components that I know rely on this and wanted clarification. If it’s just a warning but will still function for a bit, I’m less concerned.

Usually, deprecated code is still supported until the next extended support release. Otherwise, forums using that would have no chance to see the warning before stuff breaks.