Strapi v4, No relation fields in User collection types

System Information
  • Strapi Version: 4.0.0
  • Operating System: Windows
  • Database: sqlite3 5.0.2
  • Node Version: v14.17.3
  • NPM Version: 6.14.13
  • Yarn Version:

Hi!
I am trying to add some relation fields to my
User (collection types, default strapi collection)

I created new collection types called User-Roles

I am able to retrieve them as an Authenticated user (USERS & PERMISSIONS PLUGIN)

{
    "data": [
        {
            "id": 1,
            "attributes": {
                "role_name": "simple-user",
                "createdAt": "2021-12-07T20:05:55.205Z",
                "updatedAt": "2021-12-07T20:05:56.259Z",
                "publishedAt": "2021-12-07T20:05:56.257Z"
            }
        },
        {
            "id": 2,
            "attributes": {
                "role_name": "admin-user",
                "createdAt": "2021-12-07T20:06:02.975Z",
                "updatedAt": "2021-12-07T20:06:03.458Z",
                "publishedAt": "2021-12-07T20:06:03.458Z"
            }
        }
    ],
    "meta": {
        "pagination": {
            "page": 1,
            "pageSize": 25,
            "pageCount": 1,
            "total": 2
        }
    }
}

And I am able to see them in Content manager

But when I am trying to fetch them I don’t see my relation field, and populate all doesn’t work

http://localhost:1337/api/users/me?populate=*

{
    "id": 2,
    "username": "test",
    "email": "test@test.test",
    "provider": "local",
    "confirmed": true,
    "blocked": false,
    "createdAt": "2021-12-07T19:52:40.305Z",
    "updatedAt": "2021-12-07T20:30:16.124Z"
}

I also don’t see them when login
http://localhost:1337/api/auth/local

2 Likes

If you want to see relations you need to use population, read docs.
But for some reason, this doesn’t work with the User collection type

1 Like

I think this might be an issue with permissions – by default quite a bit of the Users (roles / permissions) endpoint are disabled and will need to be updated before reading from them!

1 Like

Any ideas of how to do this?
Because I can’t find anything in the documentation, or just was careful enough reading it.

Yes thank you, I somehow missed this.

I have been wondering the same thing.
Apparently the users/me route is not populeted by default, but i cant get the v3 solotion to work:

This PR probably can help.

Note: It is not merged yet.

Thanks!
Will check when will be merged and will reply here

You’re welcome!

I tried Strapi v4.0.2 and I still have the same issue.
No idea of how I can get my relations in /users endpoints.
Any ideas?

1 Like

Hello!
It looks like the PR was not merged for 4.0.2.

In the mean time you can look at this: Fix unable to populate User in Users-Permissions by iicdii · Pull Request #11960 · strapi/strapi · GitHub

you can try to extend the user controller and manually merge the code changes.

I am not sure if it works but let me know!

Just wondering, Does this issue also includes the users/me not populating the roles in the record via REST API?

Because I am a little lost on how to do the frontend user authorization checks for example if I have made a custom “Support” role to show specific parts of the website and show a different one if its only “Authorized”

Sample

Just tried it and seems like it’s not helping, or maybe I did something wrong.
So I placed my extended file in
./src/extension/users-permissions/server/controllers/user.js
And the code from PR

'use strict';

/**
 * User.js controller
 *
 * @description: A set of functions called "actions" for managing `User`.
 */

const _ = require('lodash');
const utils = require('@strapi/utils');
const { convertPopulateQueryParams } = require('@strapi/utils/lib/convert-query-params');
const { getService } = require('../utils');
const { validateCreateUserBody, validateUpdateUserBody } = require('./validation/user');

const { sanitize } = utils;
const { ApplicationError, ValidationError } = utils.errors;

const sanitizeOutput = (user, ctx) => {
  const schema = strapi.getModel('plugin::users-permissions.user');
  const { auth } = ctx.state;

  return sanitize.contentAPI.output(user, schema, { auth });
};

module.exports = {
  /**
   * Create a/an user record.
   * @return {Object}
   */
  async create(ctx) {
    const advanced = await strapi
      .store({ type: 'plugin', name: 'users-permissions', key: 'advanced' })
      .get();

    await validateCreateUserBody(ctx.request.body);

    const { email, username, role } = ctx.request.body;

    const userWithSameUsername = await strapi
      .query('plugin::users-permissions.user')
      .findOne({ where: { username } });

    if (userWithSameUsername) {
      if (!email) throw new ApplicationError('Username already taken');
    }

    if (advanced.unique_email) {
      const userWithSameEmail = await strapi
        .query('plugin::users-permissions.user')
        .findOne({ where: { email: email.toLowerCase() } });

      if (userWithSameEmail) {
        throw new ApplicationError('Email already taken');
      }
    }

    const user = {
      ...ctx.request.body,
      provider: 'local',
    };

    user.email = _.toLower(user.email);

    if (!role) {
      const defaultRole = await strapi
        .query('plugin::users-permissions.role')
        .findOne({ where: { type: advanced.default_role } });

      user.role = defaultRole.id;
    }

    try {
      const data = await getService('user').add(user);
      const sanitizedData = await sanitizeOutput(data, ctx);

      ctx.created(sanitizedData);
    } catch (error) {
      throw new ApplicationError(error.message);
    }
  },

  /**
   * Update a/an user record.
   * @return {Object}
   */
  async update(ctx) {
    const advancedConfigs = await strapi
      .store({ type: 'plugin', name: 'users-permissions', key: 'advanced' })
      .get();

    const { id } = ctx.params;
    const { email, username, password } = ctx.request.body;

    const user = await getService('user').fetch({ id });

    await validateUpdateUserBody(ctx.request.body);

    if (user.provider === 'local' && _.has(ctx.request.body, 'password') && !password) {
      throw new ValidationError('password.notNull');
    }

    if (_.has(ctx.request.body, 'username')) {
      const userWithSameUsername = await strapi
        .query('plugin::users-permissions.user')
        .findOne({ where: { username } });

      if (userWithSameUsername && userWithSameUsername.id != id) {
        throw new ApplicationError('Username already taken');
      }
    }

    if (_.has(ctx.request.body, 'email') && advancedConfigs.unique_email) {
      const userWithSameEmail = await strapi
        .query('plugin::users-permissions.user')
        .findOne({ where: { email: email.toLowerCase() } });

      if (userWithSameEmail && userWithSameEmail.id != id) {
        throw new ApplicationError('Email already taken');
      }
      ctx.request.body.email = ctx.request.body.email.toLowerCase();
    }

    let updateData = {
      ...ctx.request.body,
    };

    const data = await getService('user').edit({ id }, updateData);
    const sanitizedData = await sanitizeOutput(data, ctx);

    ctx.send(sanitizedData);
  },

  /**
   * Retrieve user records.
   * @return {Object|Array}
   */
  async find(ctx) {
    const users = await getService('user').fetchAll(
      ctx.query.filters,
      convertPopulateQueryParams(ctx.query.populate || {})
    );

    ctx.body = await Promise.all(users.map(user => sanitizeOutput(user, ctx)));
  },

  /**
   * Retrieve a user record.
   * @return {Object}
   */
  async findOne(ctx) {
    const { id } = ctx.params;
    let data = await getService('user').fetch(
      { id },
      convertPopulateQueryParams(ctx.query.populate || {})
    );

    if (data) {
      data = await sanitizeOutput(data, ctx);
    }

    ctx.body = data;
  },

  /**
   * Retrieve user count.
   * @return {Number}
   */
  async count(ctx) {
    ctx.body = await getService('user').count(ctx.query);
  },

  /**
   * Destroy a/an user record.
   * @return {Object}
   */
  async destroy(ctx) {
    const { id } = ctx.params;

    const data = await getService('user').remove({ id });
    const sanitizedUser = await sanitizeOutput(data, ctx);

    ctx.send(sanitizedUser);
  },

  /**
   * Retrieve authenticated user.
   * @return {Object|Array}
   */
  async me(ctx) {
    const user = ctx.state.user;

    if (!user) {
      return ctx.unauthorized();
    }

    ctx.body = await sanitizeOutput(user, ctx);
  },
};

seems the fix is still not merged yet :frowning:

This content should properly be moved to ./src/extensions/users-permissions/strapi-server.js and be rewritten according to: Plugins extension - Strapi Developer Docs

I think this could be working then.

And additionally it seems like some changes are scheduled for the Version 4.0.5 (in the high hopes of it being merged): Fix unable to populate User in Users-Permissions by iicdii · Pull Request #11960 · strapi/strapi · GitHub

Hi,

I also have the same problem. Do you know if there have been any new ones?

Thanks

Hi

Same here, I’ve updated strapi to 4.2.2 and when I do (as an authenticated user) http://localhost:1337/api/users/me I get the same payload than if I do http://localhost:1337/api/users/me?populate=*, just the default payload and no role field at all; the same for a UserRole conllection type I created with a relationship with users.

I can’t figure out any workaround for now, so any hint to make this work is appreciated :relaxed:

Cheers!

Hey,

I’m not aware of any changes, but in my case, what I did as a workaround was to use the “me” query just to get the userId in the front end.
Then I make a “findOne” query in users-permissions with this userId to get all the infos I need.
To do that you have to let any logged user query a single user, wich is not ideal regarding security.
But you can add a route middleware to restrict these queries : juste make sure the user is querying himself aka the userID in the query match the id of the authenticated user doing the query (you can get this ID from the context in the middleware)

I’m using GraphQL so the middleware looks like that :
strapi/src/index.js

module.exports = {
  register({ strapi }) {
    // Users
    extensionService.use({
      resolversConfig: {
        // findOne
        'Query.usersPermissionsUser': {
          auth: false, // Bypass strapi permissions
          middlewares: [
            'global::has-valid-role', // Test if is autenticated
            'global::user-query-himself' // test if query himself
          ],
        },
// ...

and I’ve got my middleware in a separate file
src/middlewares/user-query-himself.js

module.exports = (next, parent, args, ctx, info) => {
  const user = ctx.state.user.id; // user ID that makes the query
  const queriedUserId = args.id;
  const userIsHimself = user.id && user.id == queriedUserId
  if (!userIsHimself) throw new Error('You are not allowed to see this')
  return next(parent, args, ctx, info)
};

It surely don’t seems to be the right way, but as a workaround, it works.
Hope that helps

OK, I fixed by setting permissions for find and findOne in Settings → User permissions plugin → Role → Authenticated for the UserRole collection type and for user - me and user - find for the User permissions one

4 Likes

thanks! its worked for me.