strapi - restrict user to fetch only data related to him

Viewed 6680

Usually, a logged-in user gets all entries of a Content Type.

I created a "snippets" content type (_id,name,content,users<<->>snippets)

<<->> means "has and belongs to many" relation.

I created some test users and make a request: curl -H 'Authorization: Bearer eyJ...' http://localhost:1337/snippets/

Main Problem: an authenticated user should only see the entries assigned to him. Instead, a logged-in user gets all snippets, which is bad.

How is it possible to modify the fetchAll(ctx.query); query to take that into account so it does something like fetchAll(ctx.state.user.id); at the /-route->find-method ?

The basic find method is here:

find: async (ctx) => {

    if (ctx.query._q) {
      return strapi.services.snippet.search(ctx.query);
    } else {
      return strapi.services.snippet.fetchAll(ctx.query);
    }
},

Sub-Question: Does strapi even know which user is logged in when I do Bearer-Token Authentication ?

3 Answers

You could set up a /snippets/me route under the snippets config.

That route could call the Snippets.me controller method which would check for the user then query snippets based on the user.

So in api/snippet/config/routes.json there would be something like :

    {
      "method": "GET",
      "path": "/snippets/me",
      "handler": "Snippets.me",
      "config": {
        "policies": []
      }
    },

Then in the controller (api/snippet/controllers/Snippet.js), you could do something like:

  me: async (ctx) => {
    const user = ctx.state.user;    
    if (!user) {
      return ctx.badRequest(null, [{ messages: [{ id: 'No authorization header was found' }] }]);
    }

    const data = await strapi.services.snippet.fetch({user:user.id});  

    if(!data){
      return ctx.notFound();
    }

    ctx.send(data);
  },

Then you would give authenticated users permissions for the me route not for the overall snippets route.

The above is correct, except with newer versions of strapi. Use find and not fetch :)

const data = await strapi.services.snippet.find({ user: user.id });

Strapi v3.0.0-beta.20

A possibility would be to extend the query used by find and findOne in the controllers with a restriction regarding the logged in user. In this case you might also want to adapt the count endpoint to be consistent.

This would result in:

withOwnerQuery: (ctx, ownerPath) => {
  const user = ctx.state.user;
  if (!user) {
    ctx.badRequest(null, [
      { messages: [{ id: "No authorization header was found" }] },
    ]);
    return null;
  }
  return { ...ctx.query, [ownerPath]: user.id };
};

find: async (ctx) => {
    ctx.query = withOwnerQuery(ctx, "owner.id");
    if (ctx.query._q) {
      return strapi.services.snippet.search(ctx.query);
    } else {
      return strapi.services.snippet.fetchAll(ctx.query);
    }
},

// analogous for for findOne

Depending on your usage of controllers and services you could achieve the same thing via adapting the service methods.

This kind of solution would work with the GraphQL plugin.

Related