item level permission in casl

Viewed 515

In my case, the permission is not based on role and also there won't be one central permission object which I can put after login. The permission happens on item level. By permission happens on item level, I mean to say if I have an object of todos which has around 4 items then each item might have different permission levied as below

const todos = {
  data: [
    {
      id: 1,
      title: "Todo 1",
      sub: "Let's start!",
      permissions: [
        {
          action: "read",
          subject: "todo"
        },
        {
          action: "delete",
          subject: "todo"
        }
      ],
      __typename: "Todo"
    },
    { id: 2, title: "Todo 2", sub: "Let's start 2!", __typename: "Todo" },
    {
      id: 3,
      title: "Todo 3",
      sub: "Let's start 3!",
      permissions: [
        {
          action: "read",
          subject: "todo"
        }
      ],
      __typename: "Todo"
    },
    { id: 4, title: "Todo 4", sub: "Let's start 4!", __typename: "Todo" }
  ]
};

For such case, how do i handle permission using casl?

This is what I was doing

ability.js

import { Ability } from "@casl/ability";

export default new Ability([
  {
    action: "read",
    subject: "todo"
  },
  {
    action: "delete",
    subject: "todo"
  }
]);

can.js

import { createContext } from "react";
import { createContextualCan } from "@casl/react";

export const AbilityContext = createContext();
export const Can = createContextualCan(AbilityContext.Consumer);

index.js

import React from "react";
import { Ability } from "@casl/ability";
import { AbilityContext, Can } from "./can";

const todos = {
  data: [
    {
      id: 1,
      title: "Todo 1",
      sub: "Let's start!",
      permissions: [
        {
          action: "read",
          subject: "todo"
        },
        {
          action: "delete",
          subject: "todo"
        }
      ],
      __typename: "Todo"
    },
    { id: 2, title: "Todo 2", sub: "Let's start 2!", __typename: "Todo" },
    {
      id: 3,
      title: "Todo 3",
      sub: "Let's start 3!",
      permissions: [
        {
          action: "read",
          subject: "todo"
        }
      ],
      __typename: "Todo"
    },
    { id: 4, title: "Todo 4", sub: "Let's start 4!", __typename: "Todo" }
  ]
};

const Permission = () => {
  const ability = React.useContext(AbilityContext);
  return (
    <>
      <h1>Permission Based System</h1>
      {todos.data.map((todo) => (
        <div key={todo.id}>
          <Can I="read" a="todo">
            <div style={{ display: "flex", alignItems: "center" }}>
              <div style={{ display: "flex", flexDirection: "column" }}>
                <h1 style={{ margin: 0 }}>{todo.title}</h1>
                <h3>{todo.sub}</h3>
              </div>
              <Can I="delete" a="todo">
                <span>Delete</span>
              </Can>
            </div>
          </Can>
        </div>
      ))}
    </>
  );
};

export default Permission;

I have created a sandbox as well

https://codesandbox.io/s/mystifying-leaf-i968u?file=/src/permission/index.js:0-1472

1 Answers

For case, when you have permissions embedded into entities, you don't need CASL. You can use it but it won't bring any benefits. Why?

  1. You duplicate subject Todo in response for every todo item in an array for no purpose. You know that you requested Todo and even GraphQLs __typename property tells you this.
  2. It requires to create an Ability instance per subject type (in this case Todo), iterate over all todos, generate an object of conditions by id (so we can later check ability.can('read', todo)).

All this are useless, brings additional complexity and not benefits at all.

A better structure for your case would be this:

permissions: {
  canRead: true,
  canDelete: true
}

So, you can easily check permissions on UI - todo.permissions.canRead

Related