1

I think I'm misunderstanding something fundamental about Strapi: If I want to populate a query in order to obtain all of its relation fields, I need to activate the "find" portion of their APIs. But that then exposes all of that model to everyone....

For example, I have a model called "articles", that has an associated "user" relation. In my custom article.js controller I am trying to allow anyone to see an article, and the corresponding users's name (but only their name). To do this I have something like:

async find(ctx) {
    const entries = await strapi.entityService.findMany('api::article.article', {
        populate: {
            user: {fields: ['username']}
        }
    });
    const sanitizedEntries = await this.sanitizeOutput(entries, ctx);
    return this.transformResponse(sanitizedEntries);
}

BUT, for this to work I need to enable "find" for the entire user model, which is insecure. Am I missing something obvious with how to achieve this? It seems like a pretty fundamental need in a relational database to allow population without exposing the entire model to everybody.

Thanks

0

1 Answer 1

0

One possible solution is to implement a middleware as described here.

Short description:

  1. Add a strapi-server.js to the user-permissions plugin. (more on that topic here)
  2. Add a middleware. (e.g. use yarn strapi generate)
  3. Implement object sanitization inside your middleware e.g.:
// sanitizer function
const sanitizeItem = (item, user) => {
  // check if user is not undefined
  if (user) {
    // check if user id is same as the item.id (user from request)
    if (user.id === item.id) {
      // if it's same return full object
      return item;
    }
  }
  // TODO: rename hiddenField to the field you want to hide
  let { hiddenField, ...rest } = item;
  return rest;
}

When trying to hide a users email your middleware could look like that:

'use strict';

/**
 * `user-sanitization` middleware
 */

module.exports = (config, { strapi }) => {
  return async (ctx, next) => {
    // before controller
    await next();
    // after controller
    // we need to check if the reponse is correct, 
    // otherwise we will have error message in the data
    if (ctx.response.status === 200) {
      // get the authenticated user, if no user - undefined
      const { user } = ctx.state;
      // get data from response
      let data = ctx.response.body;
      // check if data is array
      if (Array.isArray(data)) {
        // run sanitize function for each element
        data = data.map(item => sanitizeItem(item, user))
      } else {
        // else run for single item
        data = sanitizeItem(data, user);
      }
      // apply result to response
      ctx.response.body = data;
    }

  };
};

// sanitizer function
const sanitizeItem = (item, user) => {
  // check if user is not undefined
  if (user) {
    // check if user id is same as the item.id (user from request)
    if (user.id === item.id) {
      // if it's same return full object
      return item;
    }
  }
  // else extract email from object
  let { email, ...rest } = item;
  return rest;
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.