How to make a owner police in strapi v4

System Information
  • Strapi Version: 4
  • Operating System: windows
  • Database: sqllite
  • Node Version: v14.20.0
  • NPM Version: 6.14.17
  • Yarn Version:

Hi every one,
I Am new in Strapi I’m watching Strapi tutorial in that tutorial he teaches Strapi v3 and I use Strapi v4 I want to create is-owner policy that every user being able to delete and update it’s own data, i have two API one is event and anther is me, I can get the user data that he is authenticated but that user can delete and update the other users data also.
this is the Strapi v3 is Owner policy I want the same to work for strapi v4

"use strict";
const { sanitizeEntity } = require("strapi-utils");

module.exports = {
  // Create event with linked user
  async create(ctx) {
    let entity;
    if (ctx.is("multipart")) {
      const { data, files } = parseMultipartData(ctx);
      data.user = ctx.state.user.id;
      entity = await strapi.services.events.create(data, { files });
    } else {
      ctx.request.body.user = ctx.state.user.id;
      entity = await strapi.services.events.create(ctx.request.body);
    }
    return sanitizeEntity(entity, { model: strapi.models.events });
  },
  // Update user event
  async update(ctx) {
    const { id } = ctx.params;

    let entity;

    const [events] = await strapi.services.events.find({
      id: ctx.params.id,
      "user.id": ctx.state.user.id,
    });

    if (!events) {
      return ctx.unauthorized(`You can't update this entry`);
    }

    if (ctx.is("multipart")) {
      const { data, files } = parseMultipartData(ctx);
      entity = await strapi.services.events.update({ id }, data, {
        files,
      });
    } else {
      entity = await strapi.services.events.update({ id }, ctx.request.body);
    }

    return sanitizeEntity(entity, { model: strapi.models.events });
  },
  // Delete a user event
  async delete(ctx) {
    const { id } = ctx.params;

    const [events] = await strapi.services.events.find({
      id: ctx.params.id,
      "user.id": ctx.state.user.id,
    });

    if (!events) {
      return ctx.unauthorized(`You can't update this entry`);
    }

    const entity = await strapi.services.events.delete({ id });
    return sanitizeEntity(entity, { model: strapi.models.events });
  },
  // Get logged in users
  async me(ctx) {
    const user = ctx.state.user;

    if (!user) {
      return ctx.badRequest(null, [
        { messages: [{ id: "No authorization header was found" }] },
      ]);
    }

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

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

    return sanitizeEntity(data, { model: strapi.models.events });
  },
};

also after hours and hours searching I found something but it not work correct,
in the code that I found just the get method to get the current user data is working but other method delete update and create not work,
if some can help me please…

const { createCoreController } = require("@strapi/strapi").factories;

// module.exports = createCoreController('api::event.event');

module.exports = createCoreController("api::event.event", ({ strapi }) => ({

  //Find with populate ----------------------------------------

  async find(ctx) {

    const populateList = ["image", "user"];

    // Push any additional query params to the array

    populateList.push(ctx.query.populate);

    ctx.query.populate = populateList.join(",");

    // console.log(ctx.query)

    const content = await super.find(ctx);

    return content;

  },

  // Create user event----------------------------------------

  async create(ctx) {

    let entity;

    ctx.request.body.data.user = ctx.state.user;

    entity = await super.create(ctx);

    return entity;

  },

  // Update user event----------------------------------------

  async update(ctx) {

    let entity;

    // const { id } = ctx.params;

    const { id } = ctx.state.user;

    const query = {

      filters: {

        id: id,

        user: { id: id },

      },

    };

    const events = await this.find({ query: query });

    if (!events.data || !events.data.length) {

      return ctx.unauthorized(`You can't update this entry`);

    }

    entity = await super.update(ctx);

    return entity;

  },

  // Delete a user event----------------------------------------

  async delete(ctx) {

    // const { id } = ctx.params;

    const { id } = ctx.state.user;

    console.log("DELETED");

    const query = {

      filters: {

        id: id,

        user: { id: id },

      },

    };

    const events = await this.find({ query: query });

    if (!events.data || !events.data.length) {

      return ctx.unauthorized(`You can't delete this entry`);

    }

    const response = await super.delete(ctx);

    return response;

  },

  // Get logged in users----------------------------------------

  async get(ctx) {

    const { user } = ctx.state;

    if (!user) {

      return ctx.unauthorized({

        messages: "No authorization header was found",

      });

    }

    const query = {

      filters: {

        user: { id: user.id },

      },

    };

    const data = await this.find({ query: query });

    if (!data) {

      return ctx.notFound();

    }

    const sanitizedEntity = await this.sanitizeOutput(data, ctx);

    return this.transformResponse(sanitizedEntity);

  },

})); 
1 Like

I guess you are working through the same tutorial I am and having the same fun.

There are two parts to this. The first is making sure when you create an event you associate it with the required user.

in my project I have the relationship between events and users defined like this

note the name is “author”

on your controller /src/api/event/controller/event.js

'use strict';

/**
 *  event controller
 */

const {createCoreController} = require('@strapi/strapi').factories;

module.exports = createCoreController("api::event.event", ({strapi}) => ({
  async me(ctx, next) {
    const user = ctx.state.user
    if (!user) {
      return ctx.badRequest(null, [{messages: [{id: "No auth header found"}]}])
    }

    const data = await strapi.entityService.findMany("api::event.event", {
      populate: 'image',
      filters: {
        "author": {
          "id": user.id
        }
      },

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

    const sanitizedEvents = await this.sanitizeOutput(data, ctx);

    return this.transformResponse(sanitizedEvents);
  },
  async create(ctx) {
    const {id} = ctx.state.user; //ctx.state.user contains the current authenticated user
    const response = await super.create(ctx);
    const updatedResponse = await strapi.entityService
      .update('api::event.event', response.data.id, {data: {author: id}})
    return updatedResponse;
  },
  
}));

That will ensure the event is created and associated with the authenticated user.

The next part is ensuring users can edit/delete only their own records. You could do this in the controller as shown here in some draft documentation I found which really helped https://github.com/nextrapi/documentation/blob/4cb0a1d2d0d8f6b2342f8ba29864e9d220791e81/docs/developer-docs/latest/guides/is-owner.md

cleaner though to do this in a policy as you can create as many as you need and chain them together

create a folder in /src/api/event/policies and add an is-owner.js file (name is important since will be used in a minute)

In this file add the following.

'use strict';
/**
 * `is-owner` policy.
 */
module.exports = async (policyCtx, config, {strapi}) => {
  // get user id/record to update/delete.
 const {id : userId} = policyCtx.state.user;

 const {id : eventId} = policyCtx.request.params;

 strapi.log.info('In is-owner policy.');

 const [event] = await strapi.entityService
    .findMany('api::event.event', {
      filters: {
        id: eventId,
        author: userId
      }
    })
  // we have an event owned by the authenticated user
  if (event) {
    return true;
  }

  // we don't have an event owned by the user.
   return false;

};

Return true if OK, false if not, this will move to the controller or the next policy (policies are evaluated before the request hits the controller).

Lastly, apply the policy to the routes you need, I wanted to ensure users can only update and delete their own records.

in /src/api/events/routes/events.js apply the policy like so

'use strict';

/**
 * event router.
 */

const { createCoreRouter } = require('@strapi/strapi').factories;

module.exports = createCoreRouter('api::event.event', {
  config: {
    update: {
        "policies" : ["is-owner"]
    },
    delete: {
        "policies" : ["is-owner"]
    }
  }
});

Note the policy name matches the policy file name we created earlier. With all that done, if you try to modify a record you don’t own you get back

{
  "data": null,
  "error": {
    "status": 403,
    "name": "PolicyError",
    "message": "Policy Failed",
    "details": {}
  }
}

Hopefully it helps.

Hi im doing something like this. greetings

async delete(ctx) {
    const { id } = ctx.params;
    const { id: userId } = ctx.state.user;

    const entry = await strapi.db.query("api::wallet.wallet").findOne({
      where: { id, user: { id: userId } },
    });
    if (entry) {
      const response = await super.delete(ctx);
      return response;
    }

    return ctx.notFound();
 async create(ctx) {
 
    ctx.request.body.data.user = ctx.state.user.id;
    const response = await super.create(ctx);
    return response;
  },

@smoaty I am very new to coding and strapi as well and your is-owner policy worked well! I am now trying to build twitter clone and I want to use that policy also for comments modal. I found out that there are also global policies and applied to different routes. How can I implement your solution globally and use in different events? Is is possible or should I repeat the same code for different events?