I want to implement a security policy in Strapi to limit document upload and deletion actions only to resources owned by users. How can I configure this restriction in Strapi to ensure data confidentiality and security? Any help or suggestions would be greatly appreciated. Thank you!
I am working on the same functionality. From what I see, the best way is to delete files using cron job which are not related to any entities (we don’t give users a right to delete files at all, but we give a right to remove relation between file and entity).
I ended up implementing the solution below for a content type “company” that contains a media field. To restrict permissions so that each user can only modify their own logo, I extended the functionalities of the Upload plugin by adding two new methods: uploadCheck and destroyCheck. These methods will check if the user is attempting to modify a document linked to a resource they own. If so, the method will then call the upload or destroy method depending on the situation. Thus, at the role level, I removed permissions on the upload and destroy APIs, as they are now controlled by the uploadCheck and destroyCheck methods.
To upload a media now, I call: POST http://localhost:1337/api/upload/check
To delete a media, I call: DELETE http://localhost:1337/api/upload/check/:id
//src/extensions/upload/strapi-server.ts
export default (plugin) => {
plugin.controllers['content-api'].hasPermission = async (type, id, user) => {
let granted = false;
let owner;
switch (type) {
case 'api::company.company':
owner = await strapi.service('api::company.company').getUserOwner(id)
if (owner.id == user.id) {
granted = true;
}
break;
default:
break;
}
return granted;
}
plugin.controllers['content-api'].destroyCheck = async (ctx) => {
const {
params: { id },
} = ctx;
const user = ctx.state.user;
const file = await strapi.plugin('upload').service('upload').findOne(id, 'related');
if (!file) {
return ctx.notFound('file not found');
}
let canDelete = false;
if (file.related.length == 1) {
canDelete = await plugin.controllers['content-api'].hasPermission(file.related[0].__type, file.related[0].id, user);
}
if (canDelete) {
await plugin.controllers['content-api'].destroy(ctx);
}
return {
result: canDelete
}
}
plugin.controllers['content-api'].uploadCheck = async (ctx) => {
const body = ctx.request.body;
const user = ctx.state.user;
if (!body && body.length != 3) {
return ctx.notFound('payload error');
}
let canCreate = await plugin.controllers['content-api'].hasPermission(body.ref, body.refId, user);
if (canCreate) {
await plugin.controllers['content-api'].upload(ctx);
}
return {
result: canCreate
}
}
plugin.routes['content-api'].routes.push(
{
method: 'DELETE',
path: '/check/:id',
handler: 'content-api.destroyCheck',
},
{
method: 'POST',
path: '/check',
handler: 'content-api.uploadCheck',
}
);
return plugin;
}