11

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 ?

sgohl
  • 391
  • 3
  • 17

3 Answers3

21

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.

sgohl
  • 391
  • 3
  • 17
Moses
  • 326
  • 2
  • 6
  • I see that this is the first/only answer you gave and I wanted to explicitly thank you for being so kind to register to help me! :-) – sgohl Nov 13 '18 at 14:08
  • I get only one snippet. does fetch({user:user.id}); fetch only one entry? fetchAll({user:user.id}) does not give any output – sgohl Nov 13 '18 at 15:55
  • are the ?_sort/find querys still possible with that solution? – sgohl Nov 13 '18 at 15:57
  • can the query of ctx.query be extended to restrict to the user:user.id part? so I could use the /-route find model – sgohl Nov 13 '18 at 16:04
  • The query construction might be off using fetchAll(). If there is more than one snippet per user, that should work. Play around with it. _sort and other params should work, you just have to construct the query including those properties. – Moses Nov 13 '18 at 16:48
  • I'm not exactly sure what you're asking in the last question, but the reason I suggested this method is because the user object exists on the backend. AFAIK, you don't know the user id until you get server side. To use the /route (which I think you're suggesting maybe /route/:id?) method you would have to already know which user was authenticated, which means that data was coming from the front end, which means your user object is on the front end, which is a bad idea, it contains all your password info. – Moses Nov 13 '18 at 16:48
  • the user is only stored in the strapi backend. Frontend is not existing yet. Before I can do a query (I simulate that via curl or insomnia), I have to authenticate, so I get a Bearer token, and with this Bearer token I can make a query. At this moment strapi should know who I am because I've sent my token along with my query. And I really mean the main route, in my case "path": "/snippets" - which uses the "find" module.export. so, instead return strapi.services.snippet.fetchAll(ctx.query); I need something which extends the ctx.query with my {user:user.id}, I guess – sgohl Nov 13 '18 at 17:11
  • Unfortunately it returns an item not belonging to the logged-in user. Does strapi even know which user is logged in when I do Bearer-Token Authentication ? Or is this token authentication actually just a legitimation ? then it should not be called authentication – sgohl Nov 13 '18 at 19:41
  • ctx.state.user should be the logged in user object. If the snippets have a relation to the users, you should be able to find associated snippets by the user id. Without seeing your code and your project, I can't really speak to any more specifics. – Moses Nov 13 '18 at 21:05
  • I can only advise you to look through the strapi documentation for filters. if the snippets content types have a user as a property, you should be able to construct a query object using that user as a filter and send that with fetchAll. – Moses Nov 14 '18 at 17:33
  • 1
    That would work, thanks!. However, what about GraphQL plugin? It won't get affected as Controller is web api-specific, isn't it? There should be more data-oriented solution and not api-access-point-specific implementation. Just thinking aloud... – Sergey Lukin Oct 10 '19 at 12:36
  • `.fetch` has now to be replaced by `.find` – Made in Moon Jun 20 '20 at 18:00
4

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

Jeremy Rajan
  • 662
  • 3
  • 12
  • Where does the snippet service come from? I'm getting "TypeError: Cannot read property 'find' of undefined" – Aido Apr 16 '20 at 19:04
  • It will be available in the controller file If you follow the right controller structure. Or use the strapi CLI to generate the controller. – Jeremy Rajan Apr 18 '20 at 14:55
  • Ooh this is a separate object. IS there any way to do this for user objects? /users/me is only for get requests. – Aido Apr 18 '20 at 17:11
4

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.

manuelkruisz
  • 1,142
  • 7
  • 4