Ограничить доступ пользователей Strapi к их собственным записям

Это была одна из первых проблем, с которой я столкнулся, когда начал использовать Strapi версии 4 в качестве бэкенда для моего сайд-проекта потокового сервиса для паровых трансляций.

Я хотел, чтобы когда пользователь запрашивал список или определенную сущность, бэкенд должен был отвечать только сущностями, принадлежащими пользователю. По умолчанию Strapi будет отправлять все сущности от всех.

Допустим, вы создаете приложение TODO с конечной точкой /tasks. Каждый пользователь должен иметь возможность создавать задачи, делая запросы POST, но если он делает запрос GET к /tasks, он не должен видеть все задачи. Также запросы PUT должны работать только тогда, когда данная задача принадлежит пользователю.

Так как же нам этого добиться?

tl;dr

Используйте вызов сервиса в методе create() для установки поля owner с системными привилегиями.
Во всех остальных методах используйте фильтр по id текущего пользователя.

Расширение контроллера Strapi

В этой статье мы остановимся на примере tasks. Как только вы создадите этот тип данных через Strapi Content-Type Builder, Strapi создаст фиктивный контроллер в /src/api/task/controllers/task.js.

Содержимое будет выглядеть следующим образом:

'use strict';

/**
 *  task controller
 */

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

module.exports = createCoreController('api::task.task');

Вход в полноэкранный режим Выход из полноэкранного режима

Чтобы добиться желаемого поведения, нам нужно создать собственные методы find(), findOne(), update() и delete() для ограничения доступа. Маршрут create() также должен быть определен, поскольку — насколько я знаю — Strapi не может автоматически связать текущего пользователя с записью.

Итак, давайте начнем с сохранения создателя задачи в его собственном поле. Я добавил новое поле в Strapi Content-Type Builder, которое является отношением к таблице пользователей. Да, Strapi будет отслеживать пользователя, который был создателем записи, но это поле нельзя редактировать. Если вы как администратор создадите задачу для пользователя в интерфейсе администратора, ваше имя будет установлено в качестве создателя, и вы не сможете ничего сделать, чтобы изменить это. Поэтому мы создаем «реальное поле» в записи.

Метод create()

Теперь обновим контроллер task, чтобы при создании поле owner было установлено в id пользователя, который выдал запрос.

'use strict';

/**
 *  task controller
 */

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

module.exports = createCoreController('api::task.task', {

    async create(ctx) {
        const user = ctx.state.user;

        const task = await super.create(ctx);

        const updated = await strapi.entityService.update("api::task.task", task.data.id, {
            data: {
                owner: user.id
            }
        });

        return result;
    },

});

Вход в полноэкранный режим Выход из полноэкранного режима

С этим кодом на месте — каждый раз, когда пользователь создает новую задачу через http POST, контроллер будет считывать объект пользователя из состояния запроса (немного похоже на сессию), создавать задачу и затем немедленно обновлять ее, устанавливая поле owner в id пользователя.

Почему процесс состоит из двух шагов?

Когда я читаю приведенный выше код, то сразу же думаю: почему бы не пронести идентификатор владельца в полезную нагрузку, передаваемую в super.create(), и избежать отдельного вызова update()?

Причина в том, что это будет работать только в том случае, если выдающий пользователь имеет доступ к изменению таблицы users. В противном случае Strapi ни за что не позволит вам установить это значение. Поэтому, вызывая сначала super.create(ctx), мы убеждаемся, что запись задачи будет создана от имени пользователя.

Наш следующий вызов strapi.entityService.update() выполняется с системными привилегиями, жестко. Действие не связано с пользователем, поэтому никакие разрешения проверяться не будут. Поле owner будет установлено с привилегиями супер-администратора (поэтому будьте особенно осторожны при выполнении этого действия).

Внимание!

Автоматическое назначение владельца не будет работать, если вы создаете задачи в интерфейсе администратора. Методы контроллера вызываются только при запросах через публичный REST или GraphQL api.

Метод find()

Когда приложение задач делает запрос GET к /tasks, Strapi вернет список всех существующих задач всех пользователей. Чтобы предотвратить это, нам нужно принудительно добавить фильтр к запросу.

async find(ctx){
    const user = ctx.state.user;

    ctx.query.filters = {
        ...(ctx.query.filters || {}),
        owner: user.id
    };

    return super.find(ctx);
}
Войти в полноэкранный режим Выход из полноэкранного режима

Опять же, мы получаем объект текущего пользователя из состояния запроса. Затем мы обновляем свойство filters объекта query для данного запроса. Мы копируем все фильтры, которые были пройдены пользователем, затем устанавливаем поле owner в поле текущего пользователя id. Таким образом, фильтр всегда устанавливается на стороне сервера, даже если пользователь передаст поддельный параметр owner извне. Невозможно будет запросить задания другого пользователя.

Другие методы

Поскольку обертывание других методов в один и тот же фильтр является по сути копированием/вставкой, я помещу их все вместе здесь:

async findOne(ctx){
    const user = ctx.state.user;

    ctx.query.filters = {
        ...(ctx.query.filters || {}),
        owner: user.id
    };

    return super.findOne(ctx);
},
async update(ctx){
    const user = ctx.state.user;

    ctx.query.filters = {
        ...(ctx.query.filters || {}),
        owner: user.id
    };

    return super.update(ctx);
},
async delete(ctx){
    const user = ctx.state.user;

    ctx.query.filters = {
        ...(ctx.query.filters || {}),
        owner: user.id
    };

    return super.delete(ctx);
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Возможно, вы захотите немного разгрузить код, но я скопировал/вставил эти части, чтобы иметь возможность быстрее сделать другие настройки.


Этот пост был опубликован в моем личном блоге.

Вы должны следить за мной на dev.to, чтобы получать больше советов, подобных этому. Нажмите кнопку «Следовать» в боковой панели! 🚀

Оцените статью
Procodings.ru
Добавить комментарий