Code Monkey home page Code Monkey logo

casl's Introduction

CASL

Do you like this package?

Support Ukraine 🇺🇦

CASL logo

Financial Contributors on Open Collective build CASL codecov CASL Join the chat at https://gitter.im/stalniy-casl/casl

CASL (pronounced /ˈkæsəl/, like castle) is an isomorphic authorization JavaScript library which restricts what resources a given user is allowed to access. It's designed to be incrementally adoptable and can easily scale between a simple claim based and fully featured subject and attribute based authorization. It makes it easy to manage and share permissions across UI components, API services, and database queries.

Heavily inspired by cancan.

Features

  • Versatile
    An incrementally adoptable and can easily scale between a simple claim based and fully featured subject and attribute based authorization.
  • Isomorphic
    Can be used on frontend and backend and complementary packages make integration with major Frontend Frameworks and Backend ORMs effortless
  • TypeSafe
    Written in TypeScript, what makes your apps safer and developer experience more enjoyable
  • Tree shakable
    The core is only 6KB mingzipped and can be even smaller!
  • Declarative
    Thanks to declarative rules, you can serialize and share permissions between UI and API or microservices

Ecosystem

Project Status Description Supported envinronemnts
@casl/ability @casl/ability-status CASL's core package nodejs 8+ and ES5 compatible browsers (IE 9+)
@casl/mongoose @casl/mongoose-status integration with Mongoose nodejs 8+
@casl/prisma @casl/prisma-status integration with Prisma nodejs 12+
@casl/angular @casl/angular-status integration with Angular IE 9+
@casl/react @casl/react-status integration with React IE 9+
@casl/vue @casl/vue-status integration with Vue IE 11+ (uses WeakMap)
@casl/aurelia @casl/aurelia-status integration with Aurelia IE 11+ (uses WeakMap)

Documentation

A lot of detailed information about CASL, integrations and examples can be found in documentation.

Have a question?

Ask it in chat or on stackoverflow. Please don't ask questions in issues, the issue list of this repo is exclusively for bug reports and feature requests. Questions in the issue list may be closed immediately without answers.

CASL crash course

CASL operates on the abilities level, that is what a user can actually do in the application. An ability itself depends on the 4 parameters (last 3 are optional):

  1. User Action
    Describes what user can actually do in the app. User action is a word (usually a verb) which depends on the business logic (e.g., prolong, read). Very often it will be a list of words from CRUD - create, read, update and delete.
  2. Subject
    The subject or subject type which you want to check user action on. Usually this is a business (or domain) entity name (e.g., Subscription, BlogPost, User).
  3. Conditions
    An object or function which restricts user action only to matched subjects. This is useful when you need to give a permission on resources created by a user (e.g., to allow user to update and delete own BlogPost)
  4. Fields
    Can be used to restrict user action only to matched subject's fields (e.g., to allow moderator to update hidden field of BlogPost but not update description or title)

Using CASL you can describe abilities using regular and inverted rules. Let's see how

Note: all the examples below will be written in TypeScript but CASL can be used in similar way in ES6+ and Nodejs environments.

1. Define Abilities

Lets define Ability for a blog website where visitors:

  • can read blog posts
  • can manage (i.e., do anything) own posts
  • cannot delete a post if it was created more than a day ago
import { AbilityBuilder, createMongoAbility } from '@casl/ability'
import { User } from '../models'; // application specific interfaces

/**
 * @param user contains details about logged in user: its id, name, email, etc
 */
function defineAbilitiesFor(user: User) {
  const { can, cannot, build } = new AbilityBuilder(createMongoAbility);

  // can read blog posts
  can('read', 'BlogPost');
  // can manage (i.e., do anything) own posts
  can('manage', 'BlogPost', { author: user.id });
  // cannot delete a post if it was created more than a day ago
  cannot('delete', 'BlogPost', {
    createdAt: { $lt: Date.now() - 24 * 60 * 60 * 1000 }
  });

  return build();
});

Do you see how easily business requirements were translated into CASL's rules?

Note: you can use class instead of string as a subject type (e.g., can('read', BlogPost))

And yes, Ability class allow you to use some MongoDB operators to define conditions. Don't worry if you don't know MongoDB, it's not required and explained in details in Defining Abilities

2. Check Abilities

Later on you can check abilities by using can and cannot methods of Ability instance.

// in the same file as above
import { ForbiddenError } from '@casl/ability';

const user = getLoggedInUser(); // app specific function
const ability = defineAbilitiesFor(user);

class BlogPost { // business entity
  constructor(props) {
    Object.assign(this, props);
  }
}

// true if ability allows to read at least one Post
ability.can('read', 'BlogPost');
// the same as
ability.can('read', BlogPost);

// true, if user is the author of the blog post
ability.can('manage', new BlogPost({ author: user.id }));

// true if there is no ability to read this particular blog post
const ONE_DAY = 24 * 60 * 60 * 1000;
const postCreatedNow = new BlogPost({ createdAt: new Date() });
const postCreatedAWeekAgo = new BlogPost({ createdAt: new Date(Date.now() - 7 * ONE_DAY) });

// can delete if it's created less than a day ago
ability.can('delete', postCreatedNow); // true
ability.can('delete', postCreatedAWeekAgo); // false

// you can even throw an error if there is a missed ability
ForbiddenError.from(ability).throwUnlessCan('delete', postCreatedAWeekAgo);

Of course, you are not restricted to use only class instances in order to check permissions on objects. See Introduction for the detailed explanation.

3. Database integration

CASL has a complementary package @casl/mongoose which provides easy integration with MongoDB and mongoose.

import { accessibleRecordsPlugin } from '@casl/mongoose';
import mongoose from 'mongoose';

mongoose.plugin(accessibleRecordsPlugin);

const user = getUserLoggedInUser(); // app specific function

const ability = defineAbilitiesFor(user);
const BlogPost = mongoose.model('BlogPost', mongoose.Schema({
  title: String,
  author: mongoose.Types.ObjectId,
  content: String,
  createdAt: Date,
  hidden: { type: Boolean, default: false }
}))

// returns mongoose Query, so you can chain it with other conditions
const posts = await BlogPost.accessibleBy(ability).where({ hidden: false });

// you can also call it on existing query to enforce permissions
const hiddenPosts = await BlogPost.find({ hidden: true }).accessibleBy(ability);

// you can even pass the action as a 2nd parameter. By default action is "read"
const updatablePosts = await BlogPost.accessibleBy(ability, 'update');

See Database integration for details.

4. Advanced usage

CASL is incrementally adoptable, that means you can start your project with simple claim (or action) based authorization and evolve it later, when your app functionality evolves.

CASL is composable, that means you can implement alternative conditions matching (e.g., based on joi, ajv or pure functions) and field matching (e.g., to support alternative syntax in fields like addresses.*.street or addresses[0].street) logic.

See Advanced usage for details.

5. Examples

Looking for examples? Check CASL examples repository.

Want to help?

Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on guidelines for contributing.

If you'd like to help us sustain our community and project, consider to become a financial contributor on Open Collective

Contributors

Code Contributors

This project exists thanks to all the people who contribute. [Contribute].

Financial Contributors

Become a financial contributor and help us sustain our community. [Contribute]

Individuals

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]

License

MIT License

Copyright (c) 2017-present, Sergii Stotskyi

casl's People

Contributors

adamborowski avatar ascott18 avatar bebsworthy avatar bodograumann avatar br0p0p avatar ccatterina avatar cerinoligutom avatar chuvikovd avatar dacevedo12 avatar deisner avatar dependabot[bot] avatar dfee avatar drudrum avatar emilbruckner avatar enheit avatar evilbuck avatar iamandrewluca avatar johannchopin avatar mbarzda avatar musicformellons avatar olena-stotska avatar probil avatar renovate-bot avatar renovate[bot] avatar scriptcoded avatar semantic-release-bot avatar stalniy avatar thejuan avatar vonagam avatar xuchaobei avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

casl's Issues

Convert repo to monorepo using lerna

  • convert to monorepo
  • reuse rollup configuration
  • update travis.yml
  • update outdated npm packages
  • fix coverage reports
  • configure code climate
  • add README to each package with explanation

The preview is in v2 branch

Wrong results testing abilities - bug correction suggested

Unexpected results in the code sample below are probably originated in line 110 of file casl/packages/casl-ability/src/ability.js which reads yet :

if (rules[i].matches(subject)) {

but when updated to :

if (rules[i].matches(field)) {

gives correct results with the following code example :

var casl = require('@casl/ability')
var ability = casl.AbilityBuilder.define(function(can, cannot) {
  can('read', 'all')
  can('manage', 'Post', { author: 'joe' })
  cannot('delete', 'Post', { 'comments.0': { $exists: true } })
})

console.log(ability.can('manage', 'Post')); // => false
console.log(ability.can('manage', 'Post', { author: 'al'})); // => false
console.log(ability.can('manage', 'Post', { author: 'joe'})); // => true

Without the suggested correction all results are unexpectedly true.

Hope this helps !
Joel

Decrease serialized rules size

  • implement packRules
  • implement unpackRules
  • add tests
  • add typings
  • add documentation

ability.toJSON() should return simplified representation of Ability which later can be effectively transferred over network. Array is a much simpler representation than object because for each object you need to duplicate its fields in JSON.
So, instead of doing this

JSON.stringify(ability.rules)
/* minified length = 95
[
  { "actions": "read", "subject": "Post" },
  { "actions": "read", "subject": "Book", "conditions": { id: 1 } },
  ...
]
*/

we can do this

JSON.stringify(ability)
/* minified length = 42
[
  ["read", "Post"],
  ["read", "Book", { "id": 1 }],
  ...
]
*/

Eventually we got 2 times reduction in rules size and as a result it decreases size of JWT token which can store this values and eventually speeds up your requests (Authorization header is included in every authorized request).

Ability.update should be able to work with both representations effectively.

// rule representation in array notation
[
  actions, // string separated by comma
  subjects, // string separated by comma,
  inverted, // true or undefined
  conditions, // object
  fields, // string separated by comma
  reason // string
]

Ability.concat

Create possibility to concatenate 2 and more abilities together. Can be useful for cases when user want to define separate ability instances for different purposes (e.g., feature flags checks, hardware checks, user permission checks). The resulting Ability instance is composed of n Ability instances and its can method returns true only if all underlying abilities returns true.

Only Ability with the same options (i.e., subjectName) can be concatenated together, otherwise exception should be thrown

const ability = AbilityBuilder.define(can => {
  can('read', 'Book')
})

const anotherAbility = Ability.define(can => {
  cannot('read', 'Book', { private: true })
})

const userAbility = ability.concat(anotherAbility)

userAbility.can('read', book) // if ability.can('read', book) && userAbility.can('read', book)
userAbility.rules 
/*
[
  [{ actions: 'read', subject: 'Book' }], 
  [{ actions: 'read', subject: 'Book', conditions: { private: true } }]\
]
*/

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Error type: undefined. Note: this is a nested preset so please contact the preset author if you are unable to fix it yourself.

TypeScript: missing optional parameter conditions on can and cannot

in index.d.ts can and cannot function for AbilityBuilderParts miss optional condition parameter of type Object.

export abstract class AbilityBuilderParts {
  rules: Rule[]

  can(action: string | string[], subject: string | string[]): Rule

  cannot(action: string | string[], subject: string | string[]): Rule
}

Documentation

Core:

  • Add examples in README
  • Add Contributing guidelines
  • Define abilities
  • Check Abilities
  • Abilities precedence
  • Error handling
  • Roles and Abilities
  • Storing Abilities
  • built-in MongoDB integration
  • How to provide integration for other storages
  • Feature flags and other interesting use cases

Articles:

  • Medium.com what is CASL?
  • habrahabr.ru what is CASL?

Examples and Integrations:

  • Aurelia integration
  • Express integration
  • Feathersjs Integration

Strip out siftjs from ES6/5m builds

Currently sift.js is embedded in builds. It needs to be moved out, so webpack can prevent module duplication (in case if you use sift.js elsewhere)

Implement `<can>` component for Vue

Requirements:

  • @casl/vue should export <can> component
  • this component should be functional
  • this component should work similar to v-if (i.e., remove/insert DOM nodes when condition changes)
  • <can I="read" a="Post"></can>
  • <can I="read title" of="Post"></can>
  • <can not I="read title" of="Post"></can>
  • <can I="read title" :of="post"></can>, post is an instance of Post
  • <can I="read" :this="post"></can>, post is an instance of Post

toMongoQuery

lets say I have a rule:
can('read', ['products'], { tid: 'someId' })
then when I'm using:
toMongoQuery(rules)

it returns { '$or': [ { tid: 'someId' } ] }, shouldn't it just be { tid: 'someId' } ?

Let AbilityBuilder DSL accept Constructor as subject

It would be handy if the AbilityBuilder supported the constructor function in lieu of the subject name when creating rules with can. After all, it already has the subjectName option passed in.

For example:

import { AbilityBuilder } from '@casl/ability'
import Post from './post'

function defineAbilitiesFor(user) {
  return AbilityBuilder.define((can, cannot) => {
    if (user.isAdmin()) {
      can('manage', Post)
    } else {
      can('read', Post)
    }
  })
}

I am happy to submit a pull request with this feature.

Update on props change

Hi,

I can't figure out what I am doing wrong. Can I show/hide UI element depending on object? here is some sample of code:

// ability.js
import { AbilityBuilder } from '@casl/ability'

function subjectName(item) {
  if (!item || typeof item === 'string') {
    return item
  }
  console.log('Type is:  ', item.__type);
  return item.__type
}

export default AbilityBuilder.define({ subjectName }, can => {
  can(['update', 'delete'], 'Account', { creator: 'John', role: 'Manager'})

})

then in my component I do:

import React, {Component} from 'react';
import Can from "./Can";

class MyComponent extends Component {
    state = {
        user: '',
        Perm: {
            creator: 'Jeff',
            role: 'Worker',
            __type: 'Account',
        }
    }

    clicked = () => {
        this.setState({Perm: {
            creator: 'John',
            role: 'Manager',
            __type: 'Account',
            }
        })
    }

    render () {
        console.log(this.state.Perm);
        return (
            <div>
                <h1>Permission</h1>
                <div>{this.state.Perm.creator}</div>

                <Can do='update' on={this.state.Perm}>
                   <button>Modify</button>
                </Can>


            <button onClick={this.clicked}>Change</button>
            </div>
        )
    }
}

export default MyComponent;

Jeff user indeed does get switched for John, so the component should be shown. I probably failed to understand something. The main reason why I am asking, is the fact that I will be getting roles from Redux, therefore I want to update my Navigation once user has logged in - depending on what role comes back from REST API

Appreciate any help!

Custom subjectName function does not work with instance check

Observed behaviour

Checking permission on creating a specific instance fails although the ability to create any object on the subject has been declared:

const { rules, can } = AbilityBuilder.extract()
can('create', 'users')
let ability = new Ability(rules, { subjectName: resource => resource.type })
// This logs false while it should log true
console.log(ability.can('create', {type : 'users'}))

Expected behaviour

Because creating any object on subject 'users' is allowed it should be allowed whatever the given instance to check.

Suspected faulty code

Ability.can() call the custom function to retrieve the subjectName (https://github.com/stalniy/casl/blob/master/src/ability.js#L91) but then perform an internal call to rulesFor() with the subjectName instead of the subject itself, leading to a bug when the custom function is called again (https://github.com/stalniy/casl/blob/master/src/ability.js#L108)

Better documention and examples on store ability templates

Hi,

I am trying to create a CRUD manager that allows me to create custom template rule but

i am having problems understanding how to write conditions inside the schema. Using you're example i want to add a condition that says "id" === '123'

{ "actions": ["read", "update"], "subject": "User", "conditions": { "id": "123" } }

when i try to do ability.can("read", "User", {user.id: "125"}) it returns true.

Can you explain more what conditions inside the schema does.

using casl/mongoose with typescript

Hello, I tried to integrate casl in my node js project, but I failed.

I user typescript and get the following erros during building:

node_modules/@casl/mongoose/index.d.ts(2,8): error TS1192: Module '"mongoose"' has no default export.
node_modules/@casl/mongoose/index.d.ts(7,3): error TS7010: 'permittedFieldsBy', which lacks return-type annotation, implicitly has an 'any' return type.

Thank you for helping.

add permittedFieldsBy to casl/mongoose

const mongoose = require('mongoose')
const { AbilityBuilder }  = require('@casl/ability')
const { accessibleRecordsPlugin } = require('@casl/mongoose')

mongoose.plugin(accessibleRecordsPlugin)

const Product = mongoose.model('Product', mongoose.Schema({
  title: String,
  price: Number,
  originalPrice: Number
}))

const ability = AbilityBuilder.define(can => {
  can('read', 'Product')
  can('update', 'Product', ['title', 'price'])
})

Product.permittedFieldsBy(ability) // returns ['title', 'price', 'originalPrice'] all fields of the Product entity. Reads `read` rules by default
Product.permittedFieldsBy(ability, 'update') // returns ['title', 'price']

Ability.cannot doesn't work with Objects

I'm trying to use CASL with Feathers and RethinkDB. My problem is that I can't get it working with conditions, like the user can only access own data.

I have this guide as reference and the only difference is, that I'm not using MongoDB or Mongose Models.

I'm digged in your Code, but I don't understand where you are checking your conditions from the rules.

import { AbilityBuilder, Ability } from 'casl'
import { Forbidden } from 'feathers-errors'

function defineAbilitiesFor(user) {
  const { rules, can, cannot } = AbilityBuilder.extract()

  console.log('define abilities for: ', user ? user.email : 'anonymous')

  if (user) {
    can(['get'], 'users', { id: user.id })
  } else {
    // anonymous
  }

  return new Ability(rules)
}

export default function authorize(name = null) {
  return async function(hook) {
    const action = hook.method
    const service = name ? hook.app.service(name) : hook.service
    const serviceName = name || hook.path

    hook.params.ability = defineAbilitiesFor(hook.params.user)

    const params = Object.assign({}, hook.params, { provider: null })
    const result = await service.get(hook.id, params)

    // works with (action, name) but without condition
    if (hook.params.ability.cannot(action, result)) {
      throw new Forbidden(`You are not allowed to ${action} ${serviceName}`)
    }

    return hook
  }
}

I'm getting every time a forbidden, only when I'm replacing the result with name in the cannot call, then it works as expected but without conditions.

Doing I something wrong or what is the problem?

Add support to check/define abilities per fields

  • allows to define rules per field
  • allows to check rules per field
  • ensure it correctly works in rulesToQuery function
  • add function to extract permitted fields from rules
  • update Typescript defs

It should be possible to define/check abilities per field:

const user = { id: 1 }
const ability = AbilityBuilder.define(can => {
  can('update', 'Post', ['likes'])
  can('update', 'Post', ['title', 'description'], { authorId: user.id })
})

console.log(ability.can('update', 'Post', 'title')) // false
console.log(ability.can('update', 'Post')) // true, because it's allowed to update Post partially its `likes` field

const post = new Post({ authorId: user.id })
console.log(ability.can('update', post, 'title')) // true
console.log(ability.can('update', post, 'createdAt')) // false

Incorrect output with cannot clause

Just been trying to fit this into my application and found getting cannot rules pretty hard to actually be taken as false. I must be missing something.

var casl = require('casl');

const ability = casl.AbilityBuilder.define((can, cannot) => {
    cannot('join', 'Room');
});

console.log(ability.can('join', 'Room'));

Or linked here on repl.it

Results in true, when I never said it could and explicitly said it couldn't.

ES5 support?

Is there an ES5 example somewhere? Thanks

var AbilityBuilder = require('casl');
AbilityBuilder.define... // Does not exist

Change priority checks for `all` alias

If there are specific rules for entity then they should be checked first only then rules for all alias.

Currently:

const ability = AbilityBuilder.define((can, cannot) => {
  can('delete', 'all')
  cannot('delete', 'Post')
})

console.log(ability.can('delete', 'Post')) // returns true

But should be false

Ability.merge

Allows to merge 2 and more Ability instances into. Only Ability instances with the same options can be merged together, otherwise exception should be thrown.

This feature will allow to merge rules of few abilities, basically this is a helper method for doing the next thing:

ability.update(ability.rules.concat(anotherAbility.rules))

This may be useful if you want to define some rules dynamically in the database and some statically in the code

Design:

Merged abilities are added at the end

const hardwareAbility = AbilityBuilder.define(can => {
   ...
})

const userAbility = AbilityBuilder.define(can => {
  ...
})

const ability = userAbility.merge(hardwareAbility)

In this case, hardwareAbility may enforce/overwrite any userAbility. ability is modified and hardwareAbility is not changed, update and updated events are emitted on Ability instance.

In case userAbility and hardwareAbility has different options for subject detection (i.e., subjectName) Ability.merge should throw an error telling that merged abilities are not compatible

update documentation

  • update package documentation (change imports)
  • change information about all alias
  • add README to @casl/ability
  • add README to @casl/mongoose
  • add README to @casl/angular
  • add README to @casl/aurelia
  • add README to @casl/react
  • add README to @casl/vue
  • toMongoQuery changes
  • rulesToQuery changes
  • pemittedFieldsOf feature
  • possibility to define abilities per fields

Add vue directive

Hi,

It would be really nice if we could do something like

<div v-can="deleteSomething">Delete something</div>

Update:

Requirements:

  • create a separate Vue plugin (e.g., AbilityDirectives)
  • this plugin should provide global directive v-can
  • directive should work similar to v-if (i.e., remove/insert DOM nodes when condition changes)
  • an array as argument to directive v-can="[action, subject, field]" (e.g., v-can="['read', post, 'title']")
  • arguments as directive arguments v-can.action.field.of="subject", .of is optional, just for readability (e.g., v-can.read.title.of="post", v-can.read="post")
  • arguments as directive arguments for class checks v-can.action.field.of.subject, .of is optional, just for readability (e.g., v-can.read.title.of.Post, v-can.read.Post)

sql & sequelize support

I am a bit puzzled by the following:

  1. As my app has returning users with account etc I suppose I do need a database for authorization rules etc.
  2. I really would prefer to use postgres (with sequelize) instead of mongodb. I saw some code examples in the docs for postgres & sequelize but it feels a bit like this is not really supported (yet) and I find it hard to judge whether I could get it up and running with these pointers. I guess my question boils down to: are these given examples enough to be up and running with postgres and sequelize, or are they just boilerplate examples on which more work is needed before you can use it?

Maybe you could also explain which features then would still be missing compared to the mongodb & mongoose solution.

Improve readability of `<Can>` component

Currently react version supports this API

<Can do="read" on="Post"></Can>

The better and more readable way would be this:

  • <Can I="read" a="Post"></Can>
  • <Can I="read title" of="Post"></Can>
  • <Can I="read title" of={this.props.post}></Can>, post is an instance of Post
  • <Can I="read" this={this.props.post}></Can>, post is an instance of Post

The old way should be deprecated (but still supported) and will be removed in 1.0 release

rulesToQuery validation

Throw exception in rulesToQuery if an array of different subject/action was passed as argument

accessibleBy and findOneAndUpdate returns MongoError: Unknown modifier: $__

If I use Model.accessibleBy(ability, action).findOneAndUpdate(query, callback) I get a MongoError Unknown modifier: $__.

If I only use Model.findOneAndUpdate(query, callback) everything works fine.

I wrote a pre hook function for findOneAndUpdate to logg the getUpdate object:

If I use Model.accessibleBy(ability, action).findOneAndUpdate(query, callback) the update object looks like this:

{ '$__':
   InternalCache {
     strictMode: true,
     selected: undefined,
     shardval: undefined,
     saveError: undefined,
     validationError: undefined,
     adhocPaths: undefined,
     removing: undefined,
     inserting: undefined,
     version: undefined,
     getters: {},
     _id: 5a4bcd0e217942001c28b6c0,
     populate: undefined,
     populated: undefined,
     wasPopulated: false,
     scope: undefined,
     activePaths: StateMachine { paths: [Object], states: [Object], stateNames: [Object] },
     pathsToScopes: {},
     ownerDocument: undefined,
     fullPath: undefined,
     emitter: EventEmitter { domain: null, _events: {}, _eventsCount: 0, _maxListeners: 0 },
     '$options': true },
  isNew: true,
  errors: undefined,
  _doc:
   { startDate: 2018-01-02T18:18:54.981Z,
     endDate: 2018-01-02T18:18:54.981Z,
     createDate: 2018-01-02T18:18:54.981Z,
     _id: 5a4bcd0e217942001c28b6c0,
     state: { _id: 'DRAFT', name: 'Draft' },
     price: '920',
     longDescription: 'Test',
     shortDescription: 'Test',
     title: 'Test' } 
}

If I use only Model.findOneAndUpdate(query, callback) the update object looks like this:

{ startDate: 2018-01-02T18:18:54.981Z,
     endDate: 2018-01-02T18:18:54.981Z,
     createDate: 2018-01-02T18:18:54.981Z,
     _id: 5a4bcd0e217942001c28b6c0,
     state: { _id: 'DRAFT', name: 'Draft' },
     price: '920',
     longDescription: 'Test',
     shortDescription: 'Test',
     title: 'Test' } 
}

Am I doing something wrong?
Thank you for your time.

Conditional rules & forbidden reasons

  • implement relevantRuleFor(action, subject, field)
  • add reason field to rules
  • add reason to pack/unpackRules functions
  • add forbidden action and subject to thrown error
  • throwUnlessCan should now throw exception with message of failed rule and use default in other cases
  • add because to RuleBuilder
  • add tests
  • add typings
  • add documentation

Sometimes you may want to define rules with possibility to specify when they should be applied and return more clear error message when ability.can fails. To do that we need to introduce a concept of conditional rule (rule with dynamic inverted property).

Lets consider example:

const ability = new Ability([])

ability.can('read', 'Post') // false

This example returns false but Ability doesn't know why (it just doesn't have defined rules) because it relates to business logic. But for the end user we would like to return something like You don't have a valid subscription or You exceeded your limits. To do so, ability explicitly should know why and what is going on.

To help ability to understand we could extend AbilityBuilder interface in 2 ways:

  • with help of cannot rules
const ability = AbilityBuilder.define((can, cannot) => {
  if (!user.haveActiveSubscription()) {
    cannot('post', 'Comment').because('has no active subscription')
  } else if (!loggedInUser.uploadsToday()) {
    cannot('post', 'Comment').because('exceeded amount of requests')
  } else {
    can('post', 'Comment', { user_id: user.id })
  }
})
  • with help of can rules:
AbilityBuilder.define((can, cannot) => {
  can('post', 'Comment')
    .when(() => loggedInUser.hasActiveSubscription(), { message: 'has no active subscription' })
    .when(() => loggedInUser.uploadsToday(), { message: 'exceeded amount of X comments per day' })
})

So, need to decided on the best approach which can be used here, receive feedback and implement this functionality

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.