Возможность хранения в экспресс-сессии?

Я видел экспресс-пример, где способность сохраняется через промежуточное ПО в объекте req. Затем он использует следующий метод для оценки разрешений:

ForbiddenError.from(req.ability).throwUnlessCan('read', article);

Я хочу добиться того же. Моя идея состоит в том, чтобы сохранить возможность внутри экспресс-сеанса, который используется совместно с socket io websockets. Через обмен req.session = socket.handshake.session. Мой подход заключается в следующем: я делаю запрос из внешнего интерфейса, чтобы получить правила для обновления возможностей внешнего интерфейса. Бэкэнд сохраняет возможность внутри экспресс-сеанса:

// abilities.js file
import { Ability } from '@casl/ability';

export const defineAbilitiesFor = (rules) => {
  return new Ability(rules);
};

export default defineAbilitiesFor;
// handler for express route to get permissions from the frontend
export const getPermissions = async (req, res) => {
...
  rules.push({
    action: ['view'],
    subject: views,
  });
  // manage all own processes
  rules.push({
    action: ['manage'],
    subject: 'Process',
    conditions: {
      userId: req.kauth.grant.access_token.content.sub,
    },
  });
// store ability in session
  req.session.rules = defineAbilitiesFor(rules);
  const token = jwt.sign({ token: packRules(rules) }, 'secret');
  if (token) {
    return res.status(200).json(token);
  } else {
    return res.status(400).json('Error');
  }
...

Затем, когда происходит запрос веб-сокета, я хочу проверить в бэкэнде, есть ли у пользователя разрешения на выполнение этого действия:

ForbiddenError.from(socket.handshake.session.rules).throwUnlessCan('view', 'Process');

Однако это вызывает следующую ошибку:

TypeError: this.ability.relevantRuleFor is not a function
    at ForbiddenError.throwUnlessCan

Кажется, что объект сеанса имеет правильный объект способности. Когда я использую console.log socket.handshake.session.rules, я получаю следующий результат:

{
  h: false,
  l: {},
  p: {},
  '$': [
    { action: [Array], subject: 'Process', conditions: [Object] },
    { action: [Array], subject: [Array] },
    { action: [Array], subject: 'Process', conditions: [Object] }
  ],
  m: {}
}

Также банка функционирует, и все остальное, что я пробовал, не работало. Я думаю, что нужно хранить простые правила как объект внутри сеанса, а затем обновлять класс способностей до того, как каждый запрос будет работать, но я не хочу этого делать. Я хочу сохранить способность прямо внутри сеанса, чтобы мне нужно было только выполнить throwUnlessCan или функции can.

Возможно ли это вообще, и если да, то как бы вы это сделали?

Пока спасибо.


person Kevin Hertwig    schedule 02.03.2021    source источник


Ответы (1)


Вместо того, чтобы хранить весь экземпляр Ability, вам нужно хранить только его правила! rules - это простой массив объектов js, поэтому его можно легко сериализовать, поэтому измените код на этот:

export const getPermissions = async (req, res) => {
...
  rules.push({
    action: ['view'],
    subject: views,
  });
  // manage all own processes
  rules.push({
    action: ['manage'],
    subject: 'Process',
    conditions: {
      userId: req.kauth.grant.access_token.content.sub,
    },
  });
// store ability RULES in session
  req.session.rules = rules;
  const token = jwt.sign({ 
    token: packRules(rules)  // packRules accepts an array of RawRule! not an Ability instance
  }, 'secret');

  if (token) {
    return res.status(200).json(token);
  } else {
    return res.status(400).json('Error');
  }

Чтобы использовать Ability в других обработчиках, добавьте промежуточное ПО:

function defineAbility(req, res, next) {
  if (req.session.rules) {
    req.ability = new Ability(req.session.rules);
    next();
  } else {
    // handle case when there is no rules in session yet
  }
}

// later

app.get('/api/users', defineAbility, (req, res) => {
  req.ability.can(...);
  // or
  ForbiddenError.from(req.ability).throwUnlessCan(...);
})
person Sergii Stotskyi    schedule 04.03.2021