Skip to content

Instantly share code, notes, and snippets.

@stalniy
Created August 10, 2020 12:52
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save stalniy/b710d65ac8a6c15f37a435c910624ef7 to your computer and use it in GitHub Desktop.
Save stalniy/b710d65ac8a6c15f37a435c910624ef7 to your computer and use it in GitHub Desktop.
CASL + Objection
const { defineAbility } = require('@casl/ability');
const { rulesToQuery } = require('@casl/ability/extra');
const Knex = require('knex');
const { Model } = require('objection');
const { interpret } = require('@ucast/objection')
const { CompoundCondition } = require('@ucast/core')
const knex = Knex({
client: 'sqlite3',
connection: ':memory:'
});
Model.knex(knex);
class User extends Model {
static tableName = 'users'
static get relationMappings() {
return {
projects: {
relation: Model.HasManyRelation,
modelClass: Project,
join: { from: 'users.id', to: 'projects.user_id' }
}
}
}
}
class Project extends Model {
static tableName = 'projects'
static get relationMappings() {
return {
user: {
relation: Model.BelongsToOneRelation,
modelClass: User,
join: { from: 'users.id', to: 'projects.user_id' }
}
}
}
}
async function createSchema() {
await knex.schema.createTable('users', (table) => {
table.increments('id').primary();
table.string('name');
table.string('email');
});
await knex.schema.createTable('projects', (table) => {
table.increments('id').primary();
table.string('name');
table.integer('user_id').references('users.id');
})
}
const createAbility = user => defineAbility((can) => {
can('manage', User, { id: user.id });
can('manage', Project, { user_id: user.id });
});
function toObjectionQuery(ability, action, query) {
const { $and = [], $or = [] } = rulesToQuery(ability, action, query.modelClass(), (rule) => {
if (!rule.ast) {
throw new Error('Unable to create Objection.Query without AST')
}
return rule.ast;
});
const condition = new CompoundCondition('and', [
...$and,
new CompoundCondition('or', $or)
]);
return interpret(condition, query);
}
async function setup() {
await createSchema();
const user = await User.query().insert({
name: 'John',
email: 'sergiy@gmail.com'
});
const projects = await Promise.all([
{ name: 'Project #1', user_id: user.id },
{ name: 'Project #2', user_id: user.id + 1 },
{ name: 'Project #3', user_id: user.id },
{ name: 'Project #4', user_id: user.id + 2 },
].map(record => Project.query().insert(record)));
return { user, projects };
}
async function main() {
const { user } = await setup();
const ability = createAbility(user);
const items = await toObjectionQuery(ability, 'read', Project.query());
console.log(items)
}
main()
.then(() => process.exit(0))
.catch(console.error)
{
"name": "casl-objection-example",
"version": "0.0.1",
"scripts": {
"start": "node index.js"
},
"author": "",
"license": "MIT",
"dependencies": {
"@casl/ability": "^5.0.0",
"@ucast/core": "^1.2.0",
"@ucast/objection": "^2.0.0",
"knex": "^0.21.2",
"objection": "^2.2.1",
"sqlite3": "^5.0.0"
}
}
@egluhbegovic
Copy link

egluhbegovic commented Nov 22, 2020

I am not sure I understand the significance of this line:

if (!rule.ast) {
      throw new Error('Unable to create Objection.Query without AST')
    }

Why not do this:

const ruleToObjection = (rule: any) => {
			return rule.inverted ? { $not: rule.conditions } : rule.conditions;
		}

		const query = rulesToQuery(
			ability,
			action,
			serviceName,
			ruleToObjection,
		) as any;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment