Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating Rules By Template #225

Closed
orozcojd opened this issue Sep 15, 2019 · 4 comments
Closed

Creating Rules By Template #225

orozcojd opened this issue Sep 15, 2019 · 4 comments
Labels

Comments

@orozcojd
Copy link

Hi,

I know this question has been asked several times, but its still unclear to me how this works.
I have read the following documentation
https://www.npmjs.com/package/@casl/ability
https://www.npmjs.com/package/@casl/vue
https://stalniy.github.io/casl/abilities/storage/2017/07/22/storing-abilities.html#storing-abilities
Issue 177

The Issue

I have a Vue front-end and node back-end I'm trying to share the same rules with. For starters, I just want to simply be able to send the rule set upon login, along with token, to the front-end and load it in vue. I am having trouble understanding how to define the abilities, given a rule set and a user object.

The rule set I am using looks like the following:

let rules = [
	{
		'actions': ['read'],
		'subject': 'all'
	},
	{
		'actions': ['update', 'delete'],
		'subject': 'Post',
		'conditions': {
			'contributorId': '${user.contributorId}'
		}
	},
	{
		'actions': ['create', 'read', 'update', 'delete'],
		'subject': 'Post',
		'conditions': {
			'user': '${user}'
		}
	}
];

The user object looks like

let user = {
	contributorId: 1,
	email: '[email protected]',
	_id: 2,
	permission: {
		level: 1,
		name: 'CREATOR',
		_id: 1234
	}
};

What I am having trouble understanding is this piece of code specifically:

const { Ability } = require('@casl/ability')
const getByPath = require('lodash.get')

function parseJSON(template, variables) {
  return JSON.parse(template, (key, rawValue) => {
    if (rawValue[0] !== '$') {
      return rawValue
    }

    const name = rawValue.slice(2, -1)
    const value = getByPath(variables, name)

    if (typeof value === 'undefined') {
      throw new ReferenceError(`Variable ${name} is not defined`)
    }

    return value
  })
}

function defineAbilitiesFor(user) {
  const template = findAbilityTemplateFor(user)

  return new Ability(parseJSON(template, { user }))
}

The code above is parsing the rule JSON but I'm not sure what its returning.

Why wouldn't something like this work?

export default function defineAbilitiesFor(template, user) {
	return new Ability(template, user)
}

Anything helps!

@stalniy
Copy link
Owner

stalniy commented Sep 17, 2019

Hi,

In that example parseJSON expects to receive a string which contains JSON (this could be stored as a TEXT field in SQL dB).

Then parseJSON replaces placeholders in string with real variables. As a result you get a list of rules. Which you can pass then to Ability constructor.

The last example doesn’t work because Ability constructor expects different parameters.

@stalniy
Copy link
Owner

stalniy commented Sep 19, 2019

@orozcojd does it make sense?

@orozcojd
Copy link
Author

Ok, it makes sense to me now. It took a bit of reading the documentation thoroughly. Thank you, Stalniy.

@Zoot01
Copy link

Zoot01 commented May 21, 2023

import { AbilityBuilder, PureAbility } from '@casl/ability';
import { PrismaQuery, Subjects, createPrismaAbility } from '@casl/prisma';
import { UserPayload } from '@dropdash/shared';
import { Canidates, JobOrders, Roles, Submittals, Users } from '@prisma/client';
import get = require('lodash.get');

export type Action = 'manage' | 'create' | 'read' | 'update' | 'delete';

type AppAbility = PureAbility<
  [
    Action,
    Subjects<{
      Users: Users;
      Roles: Roles;
      Submittals: Submittals;
      Canidates: Canidates;
      JobOrders: JobOrders;
    }>
  ],
  PrismaQuery
>;

interface Permission {
  action: Action | Action[];
  subject: any;
  fields?: string | string[] | undefined;
  conditions?: any | undefined;
}

const parseJSON = (template: any, variables: any) => {
  console.log(template);
  return JSON.parse(template, (key, rawValue) => {
    if (rawValue[0] !== '$') {
      return rawValue;
    }

    const name = rawValue.slice(2, -1);
    const value = get(variables, name);

    if (typeof value === 'undefined') {
      throw new ReferenceError(`Variable ${name} is not defined`);
    }

    return value;
  });
};

export const defineAbilitiesFor = (user: UserPayload) => {
  const { permissions: data } = user;

  const permissions = parseJSON(data, { user_id: user.user_id });

  const { can: allow, build } = new AbilityBuilder<AppAbility>(
    createPrismaAbility
  );

  permissions.forEach((item: Permission) => {
    return allow(item.action, item.subject, item.fields, item.conditions);
  });

  return build();
};

I have these permissions saved inside my SQL database, Auth0 login flow makes a call to my API and attaches the permissions response to my JWT. keep in mind the response is parsed with JSON.stringify().

permissions: [
      { action: 'read', subject: 'Clients' },
      { action: 'read', subject: 'Users' },
      { action: ['read', 'create'], subject: 'Canidates' },
      {
        action: ['update'],
        subject: 'Canidates',
        conditions: { created_by: '${user.id}' },
      },
      { action: 'read', subject: 'JobOrders' },
      { action: ['read', 'create'], subject: 'Submittals' },
      {
        action: ['update', 'delete'],
        subject: 'Submittals',
        conditions: { submitted_by: '${user.id}' },
      },
    ],

package.json

"dependencies": {
        "@casl/ability": "^6.5.0",
        "@casl/prisma": "^1.4.0",
        "lodash.get": "^4.4.2",
}

let me know if you have any questions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants