Code Monkey home page Code Monkey logo

feathers-casl's Introduction

Hi there πŸ‘‹

I'm Frederik Schmatz, a Fullstack Dev πŸ‘¨πŸ½β€πŸ’» from Germany with experience in JS, HTML, CSS, TS, C#, python. My current main stack is vue.js, feathers.js. I love to contribute to open source projects. You'll probably find me on Slack/Discord servers of these frameworks. I enjoy connecting with other devs. Please say hello at my social media:

Personal: Twitter βˆ™ LinkedIn βˆ™ NPM βˆ™ ResearchGate

I'm the Co-Founder and CTO of Artesa. We develop an order planning software for craft business in Germany. We always look for new motivated employees. If you're interested please checkout the links below and feel free to contact me.

Artesa: Website βˆ™ Instagram βˆ™ LinkedIn

feathers-casl's People

Contributors

dasantonym avatar dbvcode avatar fratzinger avatar lukashass avatar vladimirmalkov 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

Watchers

 avatar  avatar

feathers-casl's Issues

Ability to have multiple "stages" for permissions?

I'm looking to use CASL for both a licensing system and a user permission system. Issue is that I need the licensing rules to override any user permissions. Even if a user is given a role that gives them permission to everything, if their license doesn't allow that it should fail.

Due to how CASL works, if my license defines that they can only "read" a subject, but their role gives them the ability to "write" for a subject, then they will be able to do things that their license doesn't allow for.

Really what I need is the ability to have "stages" of permission checks. If the license permissions fails, then it should error. If it passes, then it continues to check their user permissions. I'm curious, is this possible to use feathers-casl with multiple sets of independent rules?

I'm thinking I might be able to do this with a hook that swaps out the context.params.ability before running the authorizeHook each time:

const customAuthorizeHook = async (context) => {
   context.params.ability = context.licenseAbility;
   await authorizeHook(context);
   context.params.ability = context.userAbility;
   await authorizeHook(context);

This however, wouldn't do anything for channels permissions.

Do you have any thoughts on supporting something like this?

I could use inverted rules for everything, but since its generally recommended to give instead of take away permissions I'd rather keep as many cannots to a minimum.

Potential security issue for authorize hook?

Hi, I'm new to feathers-casl and was playing around with it today. I might have found a potential security issue for the authorize hook but I'm not 100% sure. Please see the following setup and let me know if something's wrong. Thanks!

This is the hook file for a service called 'test-tables'. I made two blocks (BLOCK 1) and (BLOCK 2) and they are in before all hook and before create hook correspondingly.

before: {
    all: [
      authenticate('jwt'),
      // BLOCK 1
      context => {
        const user = context.params.user

        const defineRulesFor = () => { 
          const { can, rules } = new AbilityBuilder()
          if (user.role && user.role === 'everyone') {
            can('manage', 'all')
          }

          return rules
        }

        const rules = defineRulesFor()
        const ability = makeAbilityFromRules(rules, { resolveAction })
        context.params.ability = ability
      }
    ],
create: [
      // BLOCK 2
      authorize({
        ability: context => {
          const user = context.params.user

          const defineRulesFor = () => { 
            const { cannot, rules } = new AbilityBuilder()
            if (user.role && user.role === 'everyone') {
              cannot('create', 'testTables')
            }

            return rules
          }

          const rules = defineRulesFor()
          return makeAbilityFromRules(rules, { resolveAction })
        }
      })
    ]
}

The following were declared on top of the hook file

const { authorize } = require('feathers-casl').hooks
const { AbilityBuilder, createAliasResolver, makeAbilityFromRules } = require('feathers-casl')

const resolveAction = createAliasResolver({
  update: 'patch',       // define the same rules for update & patch
  read: ['get', 'find'], // use 'read' as a equivalent for 'get' & 'find'
  delete: 'remove'       // use 'delete' or 'remove'
})

Here are the behaviors that I discovered:

  1. If I commented out BLOCK 1, the user would NOT be able to create an entry to test-tables (with error "error: Forbidden: You are not allowed to create test-tables"). This makes sense because of the cannot rules set in BLOCK 2.

  2. And here comes the potential issue: if I have both BLOCK 1 and BLOCK 2, the request was able to create an entry in test-tables. I would assume the authorize hook's ability option should have precedent over BLOCK 1 because anyone in the public can fake the same structure produced in BLOCK 1?

Thanks for your time.

silentThrow

Throwing Forbidden maybe should not be exposed, so maybe it's a better idea to set context.result = [] or context.result = undefined;

Nest JS authorization with CASL doesn't work as expected

Expected behavior

Be able to get user info with id equal to my id only (which is saved in JWT token).

Actual behavior

I am able to get info about all users with any id.

  1. /casl-ability.factory.ts
type Subjects = InferSubjects<typeof User | typeof Role | 'User'> | 'all';
export type AppAbility = Ability<[Action, Subjects]>;

export class CaslAbilityFactory {
  createForUser(userDataFromJWT: JwtAccessTokenInput) {
    const { can, cannot, build } = new AbilityBuilder<
      Ability<[Action, Subjects]>
    >(Ability as AbilityClass<AppAbility>);

    // TESTING THIS CASE
    can(Action.Read, User, {
      id: userDataFromJWT.sub,
    });

    return build({
      detectSubjectType: (item) =>
        item.constructor as ExtractSubjectType<Subjects>,
    });
  }

  private hasRole(roles: unknown[], role: UserRoles): boolean {
    return roles.includes(role);
  }
}
  1. /getUser.policyHandler.ts
  export class GetUserPolicyHandler implements IPolicyHandler {
      handle(ability: AppAbility) {
        return ability.can(Action.Read, User);
      }
    }
  1. /types.ts
export enum Action {
  Manage = 'manage',
  Create = 'create',
  Read = 'read',
  Update = 'update',
  Delete = 'delete',
}

export interface IPolicyHandler {
  handle(ability: AppAbility): boolean;
}

type PolicyHandlerCallback = (ability: AppAbility) => boolean;

export type PolicyHandler = IPolicyHandler | PolicyHandlerCallback;
  1. /policies.guard.ts
@Injectable()
export class PoliciesGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    private caslAbilityFactory: CaslAbilityFactory,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const policyHandlers =
      this.reflector.get<PolicyHandler[]>(
        CHECK_POLICIES_KEY,
        context.getHandler(),
      ) || [];

    const ctx = GqlExecutionContext.create(context);
    const { user }: { user: JwtAccessTokenInput } = ctx.getContext().req;
    const ability = this.caslAbilityFactory.createForUser(user);

    return policyHandlers.every((handler) =>
      this.execPolicyHandler(handler, ability),
    );
  }

  private execPolicyHandler(handler: PolicyHandler, ability: AppAbility) {
    if (typeof handler === 'function') {
      return handler(ability);
    }
    return handler.handle(ability);
  }
}
  1. /user.resolver.ts
@Resolver(() => User)
export class UserResolver {
  constructor(private readonly userService: UserService) {}

  @Query(() => User, { name: 'user' })
  @UseGuards(PoliciesGuard)
  @CheckPolicies(new GetUserPolicyHandler())
  @UseInterceptors(UserNotExistsByIDInterceptor)
  async findOne(@Args('id', { type: () => Int }) id: number): Promise<User> {
    return await this.userService.findOne(id);
  }
}

Define permissions not based on methods

Hi @fratzinger

Apologies for asking a question, but I can't seem to get any appropriate documentation on this.

As listed in the readme, one can define permissions not based on methods:
Define permissions not based on methods: can('view', 'Settings')

I've added a simple rule (inside defineRulesFor):
can('view', 'Map');

I assume I now have to defined what the rule means i.t.o the Feathers services' CRUD operations?
How does one implement such a permission rule?

Enable verbose logging/debug with `relevantRuleFor`

It would really nice to optionally enable verbose logging using CASL's relevantRuleFor feature. https://casl.js.org/v5/en/advanced/debugging-testing#debugging

I can think of three different ways that we might expose it

  1. Attach data to the Forbidden error when something like authorize({ relevantRule: true })
  2. Use something like the debug library and just print the info when an environment variable is set. I think feathers core does this.
  3. Give the necessary context to the actionOnForbidden callback and leave the logging to the library consumer.

What do you think? If any of these sound good, I would be willing to make a PR.

Documentation section 'add-abilities-to-hooks-context': wrong import file reference

Steps to reproduce

https://feathers-casl.netlify.app/getting-started.html#add-abilities-to-hooks-context

Expected behavior

References file from the previous paragraph:
import { defineAbilitiesFor } from "./authentication.abilities";

Actual behavior

References file from the previous paragraph by the wrong name:
import { defineAbilitiesFor } from "./abilities";

TBD

Just another little one - sorry these trickle in now...
It might be worth noting that /src/services/authentication/ is not created when using the FeathersJS CLI. As a consequence, the authentication service has no auto-generated hooks and validators/resolvers.
Instead, there is /src/authentication.js, which should probably be the default place for the hooks in your proposed src/services/authentication/authentication.hooks.ts

Viele Grüße

Handling "stale" ability on socket connection

This might just be a documentation thing, but I thought I'd share because it was not obvious to me.

When setting up the ability as demonstrated in the docs -- attaching it to params after authentication, it seems that ability will only be updated when a new connection is established by the client. This can lead to confusing permission errors that then disappear on a page refresh.

At least I'm pretty sure that's what's happening.

I can workaround this issue by defining service-level hooks that remove context.params.ability then create a new ability with authorize({ ability: context => createAbility( ... ) })

To make this workaround easier, I wonder if the authorize hook should prefer to use its ability parameter over context.params.ability? That would make sense to me since more local/specific is typically how an override is done, but if you don't want to break the current behavior, maybe just a flag? Is there another way to handle this?

Thanks!

option: `throwOnMultiPatchMismatch`

feathers-casl works with multi-patches as of v0.1.8-0. It filters fields that are not allowed to change and changes only allowed fields.

An additional option throwOnMultiPatchMismatch: boolean could be useful, which prevents the described behavior and throws instead if there are fields in data, that are not allowed for patch.

Came up in #8.

Support for casl v6 ?

Hi,

I'm trying the latest version 1.0.0-1, and I discover the peerDependencies of casl is limited to >= 5.0.0 < 6.

Do you think we could upgrade feathers-casl to use the latest version of casl ? (6, actually)

I don't know which breaking changes occur between 5 and 6.

Do you think there could be some breaking changes ?

find rule with restricted field not working

Bug Explanation and Fix Proposal

Steps to Reproduce
Use the following rule: can('read', 'users', ['id', 'pseudo']);
my authorize hook looks like
const authorizeHook = authorize({
adapter: '@feathersjs/knex',
availableFields: Object.keys(userSchema.properties),
});

Expected Behavior

{
"total": 2,
"limit": 10,
"skip": 0,
"data": [
{
"id": "cefd6229-1e38-4dc4-8f18-0eae5abb8509",
"pseudo": "mattp"
},
{
"id": "490a4045-3c61-4878-a3d0-4984edc24354",
"pseudo": "mattp"
}
]
}

Actual Behavior

{
"total": 2,
"limit": 10,
"skip": 0,
"data": [
{}
]
}

Error Source

The issue arises in the getItemsIsArray function called in authorizeAfter when the options parameter to { from: "result" } and the method is "find". In this scenario, the returned items value is incorrect.

Actual

return in "items" = { "total": 2, "limit": 10, "skip": 0,"data": [[object],[object]]} and is array = false

Expected

expected: should return when method = "find" items = [[object],[object]] and is array = true

Proposed Fix

two potential fixes:

#1
Change options = { from: "result" } to options = { from: "automatic" }.
#2
Add the following line to the getItemsIsArray function:
itemOrItems = itemOrItems && context.method === "find" ? itemOrItems.data || itemOrItems : itemOrItems;

Code Samples
Here are the relevant code snippets for the proposed fixes:

Fix 1:

const authorizeAfter = async (context, options) => {
// ...
let { isArray, items } = feathersUtils.getItemsIsArray(context, { from: "automatic" });
// ...
};
Fix 2:
const getItemsIsArray = (context, options) => {
const {
from = "automatic"
} = options || {};
let itemOrItems;
if (from === "automatic") {
itemOrItems = context.type === "before" ? context.data : context.result;
itemOrItems = itemOrItems && context.method === "find" ? itemOrItems.data || itemOrItems : itemOrItems;
} else if (from === "data") {
itemOrItems = context.data;
} else if (from === "result") {
itemOrItems = context.result;
itemOrItems = itemOrItems && context.method === "find" ? itemOrItems.data || itemOrItems : itemOrItems; // here
}
const isArray = Array.isArray(itemOrItems);
return {
items: isArray ? itemOrItems : itemOrItems != null ? [itemOrItems] : [],
isArray
};
};

Module and Node.js Versions
Module versions: "^2.0.0"
Node.js version: v18.13.0

Examples from documentation not work with TS

This code example
Issue with type annotations for makeAbilityFromRules function.
Please fix it or replace with new Ability(rules, { resolveAction }). I think it is especially important that all examples can be quickly launched with TS (TS documentation will be greatest solution).

Documentation contains non-existent reference to @casl/ability -> makeAbilityFromRules

Steps to reproduce

getting-started.md

Expected doc

Since now using Ability() directly from CASL, makeAbilityFromRules is no longer a referenced export

import {
  Ability,
  AbilityBuilder,
  createAliasResolver
} from "@casl/ability";

Actual doc

import {
  Ability,
  AbilityBuilder,
  createAliasResolver,
  makeAbilityFromRules
} from "@casl/ability";

Thanks for this awesome project! - I have been looking forward to this update!

custom actions for channels/realtime updates

Currently channels get filtered by action get.
This should be a option. Similar to multi-actions, that can be used by option, there could be use-cases for actions like:

  • receive: get all realtime updates
  • receive:created: get only created events
  • receive:updated: get only updates events
  • receive:patched: get only patched events
  • receive:removed: get only removed events

Even with fields and conditions this could become powerful.

Add sequelize_paranoid

  • can("update:sequelize_paranoid", ...)
  • considerSequelizeParanoid
  • make it work with multi

slim $select instead of hide/restore $select

Situation

If $select is provided and it has less fields, than needed for conditions and restricted fields, appropriate checks are not possible. So a minimum subset for the fields in conditions and restricted fields is necessary.

Problem:

Because of this, as of now, the full $select gets hidden in before and restored in after completely. That could be a performance issue for bigger applications

Solution:

Get necessary fields from conditions and restricted fields and match them with $select, so only necessary fields are fetched.
https://casl.js.org/v4/en/api/casl-ability#possible-rules-for

[email protected] - error with 'feathers-utils' is a CommonJS module

Hello,

trying to use the [email protected] together with the pre34 of feathers I get the following error message:

import { getItemsIsArray, mergeArrays, mergeQuery, isMulti, markHookForSkip, shouldSkip } from 'feathers-utils';
^^^^^^^^^^^^^^^
SyntaxError: Named export 'getItemsIsArray' not found. The requested module 'feathers-utils' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'feathers-utils';
const { getItemsIsArray, mergeArrays, mergeQuery, isMulti, markHookForSkip, shouldSkip } = pkg;

at ModuleJob._instantiate (node:internal/modules/esm/module_job:123:21)
at async ModuleJob.run (node:internal/modules/esm/module_job:189:5)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:526:24)
at async loadESM (node:internal/process/esm_loader:91:5)
at async handleMainPromise (node:internal/modules/run_main:65:12)

[nodemon] app crashed - waiting for file changes before starting...

It is a fresh install, so no other libraries are referencing to feathers-utils.

Any ideas?

Thanks
Martin

Custom actions - ex : can('createMainAccount', 'accounts')

Hi.

Is it possible to define custom action like this can('createMainAccount', 'accounts') ?

How to check if it's authorize with feathers-casl ?

From the website it seems possible : "Define permissions not based on methods: can('view', 'Settings')"

My use-case :

I would like to pass action with data.action : createMainAccount from the client and check if user can on the server.
So i can do POST on accounts with data.action : createMainAccount

Thanks

Fields are exposed when pagination is turned off

When using hooks.authorize() in the after hook, and the disablePagination hook from feathers-hooks-common it doesn't filter the fields properly, exposing all fields in the response.

Correct paginated response based on the casl fields: ['id']:

/users

{
    "total": 162,
    "limit": 10,
    "skip": 0,
    "data": [
        {
            "id": "2"
        },
        {
            "id": "12"
        },
        {
            "id": "13"
        },
    ]
}

I'm seeing all fields when the data is not paginated:

/users?$limit=-1

[
    {
        "id": "2",
        "email": "[email protected]",
        "name": "System User",
        "language": "en-US",
    },
    {
        "id": "12",
        "email": "[email protected]",
        "name": "System User 1",
        "language": "en-US",
    },
    {
        "id": "13",
        "email": "[email protected]",
        "name": "System User 2",
        "language": "en-US",
    }
}

Missing resource results in "You are not allowed to get X" message

Steps to reproduce

Not sure if I am using this wrong:

  • Try GETting a missing record

Expected behavior

  • Server returns 404

Actual behavior

  • Server says "You are not allowed to get X"

System configuration

I have set up the hooks like this:

module.exports = {
  before: {
    all: [
      authorize({ adapter: 'feathers-mongoose' })
    ]
  },
  after: {
    all: [
      authorize({ adapter: 'feathers-mongoose' })
    ]
  }
}

It seems to me that the authorize hook in after croaks and then produces this error, because I am depending on a field in the result to be checked. Could that be?

new util to update connections

brought up in #34 (thanks @robbyphillips!) we need a way to recalculate abilities on connections. Something like:

//pseudocode
import assignAbilityToConnection from "feathers-casl";

const userId = context.params.user?.id;
if (!userId) { return }
const ability = createAbility(...);

assignAbilityToConnection(ability, (connection) => {
  return connection.user?.id === userId
});

You're not allowed to get on 'users'

When configuring feathers-casl I ran into a problem when authenticating with @feathersjs/authentication:

You're not allowed to get on 'users'

The responsible code that calls get with provider set should be:
https://github.com/feathersjs/feathers/blob/d14f57ed8316c89ffde85c9acb17ecd790e454c5/packages/authentication/src/jwt.ts#L112

Steps to reproduce

lukashass/feathers-chat-ts@a0e2604

Possible fix

My current approach to fix this would be to explicitly add the ability with a hook on users.get, just like https://feathers-casl.netlify.app/getting-started.html#using-casl-with-the-rest-express-js-transport.

Maybe there is even an easy fix that could be applied in feathers-casl?

System configuration

Module versions:

This occurred while using dove but the linked reproduction is:

  • feathers: ^4.5.11
  • feathers-casl: ^0.7.1

NodeJS version: v16.13.1

getAffectedItems/Ids - util

Get item (ids) for items that will be affected by update, patch, remove with a specific query.

Usecase: I want to know which items will be changed in a before hook, so I can fetch these items before and compare the changes in an after hook.

  • getQuery for request from ability (with the provided method) and merge with provided query
  • fetchItems with $select or remove select
  • return items

Documentation Difficulties

I currently try to wrap my head around feathers-casl and read the documentation. There are some things that I have trouble understanding. Since later, I might be unable to recount these problems, I want to document them here in hope they can help to improve the documentation. I hope it is okay, that I bundled them in one issue, since I did not want to swamp the list of open issues.

  1. "Add abilities to hooks context"
    The documentation writes: "We use this here to put ability on these objects, which makes it available to all hooks after the authenticate(...)-hook.". The code example adds the code for population to the create action, which runs when a user logs in (according to the services’ documentation).
    I fail to understand how this provides the ability to all hooks that run after authentication and not just to hooks that run on "create" on the authentication service (Or is this somehow cached? I could not find anything in the main feathers documentation)

  2. "Add the authorize-hook to the services "
    This is minor, but I do not understand why
    a) authorizeHook defined for each service method instead of just for "all"
    (as [authenticate("jwt"), authorizeHook]) and
    b) why this is not an around β†’ all hook (like the authenticate hook in the CLI-generated feathers v5/dove code).

  3. Using CASL with the REST (Express.js) transport
    β€œIn case you are not using sockets and want to use feathers-casl with the Express transport, you need to define the abilities right after your authenticate() hook and before the authorize() hook for each service relying on CASL.” β†’ does this mean that the equivalent of what the code snippet below does is done for me automatically, when I use sockets and, if I use sockets, I can just expect abilities to be there? And is the setup that enables this for sockets what "Add abilities to hooks context" does?

Look into using `$and` instead of merging queries

Something that I do in one of my advanced feathers projects, when adding "security" filters, I add them to an $and, so that they won't conflict with the main query.

Say I have the following:

// incoming query
{
  id: { $in: [1, 2, 3] },
  status: 'complete',
}
// casl abilities query:
{
  id: { $in: [2, 3, 4] },
  status: { $in: ['complete', 'pending', 'draft'] },
}

I'm not sure how smart the merging logic is, but what you can just do is:

{
  id: { $in: [1, 2, 3] },
  status: 'complete',
  $and: [{
    id: { $in: [2, 3, 4] },
    status: { $in: ['complete', 'pending', 'draft'] },
  }],
}

All you need to do is check if $and exists as an array, otherwise initialise it, then push the ability query to the end of it.

I'm not certain it $and is supported by all adaptors, maybe having this as a togglable option instead of merging if you want to do more advanced filtering and your adaptor supports it?

Basic Working example

Hi πŸ‘‹,

First thing first, thank you for this awesome Casl integration initiative.
I followed your "Getting Started", but it does not work.
hooks in authentication.hooks.js file seem never called, maybe because authentication service (and hooks) is not registered in authentication.js generated file.
Do you have any basic working example repository ?

Thank you and have a nice day !

Is the anonymous channel needed anymore?

This is a question and not an issue per say.
If feathers-casl takes care of the channels:

  app.publish((data: any, hook: HookContext) => {
    return channels.getChannelsWithReadAbility(app, data, hook, caslOptions);
  });

Is there any use for the 'anonymous' channel any more? This is the default one for unauthenticated users proposed by feathers.

Next version for feathers 5

We are currently trying to use your plugin with feathers version 5. To get it to work some dependencies like the feathers-utils & feathers-hooks-common libs have to be updated as well. I saw that you already planned that somehow for feathers-utils: fratzinger/feathers-utils#4. As it seems that for example "only" getItems & replaceItems is used from feathers-hooks-common do you think it would be possible to add a light version / just the needed code of that functionality to feathers-casl directly, so dependencies would be reduced?
Would like to do / help you with that update.

AbilityBuilder expected 1 arguement, but got 0

Steps to reproduce

I just installed feathers-casl in a typescript feathers project. While figuring casl out I just started from your basic example to define the rules for the newly authenticated user.

const { can, cannot, rules } = new AbilityBuilder(); throws the error Expected 1 arguments, but got 0. and expects AbilityType: AnyClass<T> as input.

How do I deal with this? And funny enough am I the first one to get the error?

Expected behavior

Should work according to the docs :)

Actual behavior

It throws the error and does not compile.

System configuration

Not much to say here.

Module versions:
"@angular/animations": "13.1.1",
"@angular/common": "13.1.1",
"@angular/compiler": "13.1.1",
"@angular/core": "13.1.1",
"@angular/forms": "13.1.1",
"@angular/platform-browser": "13.1.1",
"@angular/platform-browser-dynamic": "13.1.1",
"@angular/router": "13.1.1",
"@angular/service-worker": "^13.1.1",
"@casl/ability": "^5.4.3",
"@casl/angular": "^6.0.0",
"@feathersjs/authentication": "^4.5.12",
"@feathersjs/authentication-client": "^4.5.13",
"@feathersjs/authentication-local": "^4.5.12",
"@feathersjs/configuration": "^4.5.12",
"@feathersjs/express": "^4.5.12",
"@feathersjs/socketio": "^4.5.13",
"@feathersjs/socketio-client": "^4.5.13",
"@nrwl/angular": "13.4.6",
"@nrwl/web": "13.4.6",
"axios": "^0.21.4",
"compression": "^1.7.4",
"cors": "^2.8.5",
"express": "4.17.1",
"feathers-authentication-management-ts": "^2.1.2",
"feathers-casl": "^0.7.1",
"feathers-hooks-common": "^5.0.6",
"feathers-mongodb": "^6.4.1",
"feathers-reactive": "^0.8.2",
"feathers-validate-joi": "^4.0.1",
"helmet": "^4.6.0",
"joi": "^17.5.0",
"lodash": "^4.17.21",
"luxon": "^2.3.0",
"mongodb": "^3.7.3",
"mongodb-core": "^3.2.7",
"ng-zorro-antd": "^12.1.1",
"nodemailer": "^6.7.2",
"rxjs": "~6.6.0",
"shx": "^0.3.4",
"ts-node-dev": "^1.1.8",
"tslib": "^2.3.1",
"winston": "^3.4.0",
"zone.js": "0.11.4"

NodeJS version:
v14.18.2

[Question] Custom adapter

Hello, thank you for maintaining this library! I'm using a custom adapter - feathers-pouchdb which is not in your supported adapter list. How do I define the adapter when using this library? Or use by default adapter will be workable? Any guidance will be appreciate.

Unexpected auth rejection

Hi

I'm having some issues with a rule that doesn't seem to work correctly or as expected.

I have rules that are populated for the logged in user via the DB. One of the rules is:

{
    action: 'edit',
    subject: 'parts',
    conditions: { approved: false }
}

What I'm expecting to happen is that the user should be able to edit (patch) the parts service is the approved field is set to false and fail if it is set to true. Do I understand this correctly?

Currently it fails no matter what approved is set to.
When I remove the condition however, the rule does work as expected.

Any help would be appreciated.

After hook throwing forbidden error for get only conditional rules

Hello :

Thank you for this great tool!!. I'm migrating my older application that was working well with feathers and CASL from feathers v3 (js) to v4 with ts using feathers-casl.

The rules are saved in DB and all of them are working well but one which has get only permission with conditional clause. To reproduce the problem, If I statically define the rule can('get', 'users', { id: user.id }) instead of 'read' because I don't want to allow find, the after kook throws forbidden error. As soon as I remove the after hook it works fine.

This is because the forOneEl function tests for 'read' and if not allowed returns undefined causing the forbidden error .
image

Thank you.

Remove ability on logout

Steps to reproduce

  • Have a service that does not have authenticate('jwt') on it.
  • Have authorize({ adapter: 'feathers-mongodb' })
  • Login with a user(I do it from Angular) that has a role and gets Ability
  • Logout

Expected behavior

I would expect that after logout I would not be able to use the service, because I wouldn't have Ability to.

Actual behavior

After logout I am definitely kicked out of the channels because I don't get any more updates on the service.
But funny enough when I try to use the service according to the Ability I had as a logged user, I can do it.

After digging around a bit I see ability is not erased from context.params on logout. A page refresh clears this.

Is this a bug, or something not know, not documented? Should I handle this on the authenticate hook myself?

ps. I assume if you only have systems where the same user uses is over and over you don't get into this issue.

Rules should be combined by logical OR

The CASL documentation states that rules should be combined by logical OR. feathers-casl is combining rules by logical AND.

I added a test to help demonstrate this.

it("should combine rules by logical OR", async function() {
  await service.create({ test: true, userId: 1, published: false });
  await service.create({ test: true, userId: 2, published: true });

  const items = (await service.find({ paginate: false })) as unknown[];
  assert(items.length === 2, "has two items");

  const returnedItems = await service.find({
    //@ts-ignore
    ability: defineAbility(
      (can) => {
        can("read", "tests", { published: true });
        can("read", "tests", { userId: { $in: [1] } });
      },
      { resolveAction }
    ),
    paginate: false,
  });

  // @ts-ignore
  assert.strictEqual(returnedItems.length, 2);
});

Combining these rules by logical OR, we should be getting all records where published=true OR userId IN [1]. This would result in both created records being returned. Instead, we get no records because the query is where published=true AND userId IN [1].

I'm not exactly sure where to go from here. I tried pushing a branch with this test, but don't have permission. I looked through the getQueryFor logic, and it's pretty complex. @fratzinger I see that you're not using rulesToQuery or other helpers that CASL provides for this kind of thing. Is there a reason for that?

Channels don't work

Hi @fratzinger

Me again!

The channels configuration as shown here: https://feathers-casl.netlify.app/getting-started.html#add-casl-to-channels
does not seem to work out of the box.

I'm patching a model and not getting that change event in my client as I do when I leave it as the default:
return app.channel('authenticated');

In the context of the model change getChannelsWithReadAbility(app, data, context, caslOptions) returns an empty array.

Am I missing something? Should I do anything else to get the channels to work?

using $exists in casl using createMongoAbility throws error (knex)

This issue happens with knex adaptor

when defining an ability using createMongoAbility, the authorize hook does not take $exists into consideration. it took me a long time to understand that feathers-casl is responsible for handling this.

Expected behavior

handle $exists for the fields at hook level, not database, which makes it faster and universal.

Actual behavior

throws BadRequest: Invalid query parameter $exists which seems to be working with mongo db only.

edit: I have not confirmed that it works with mongodb, it's just a guess from what it seems

System configuration

{
  "@casl/ability": "6.7.0",
  "@feathersjs/*": "5.0.24",
  "feathers-casl": "2.1.1"
}

More Info

when basic check happens inside authorizeBefore it probably doesn't need to pass $exists to the query, as the $exists is for the incoming request, not the actual data (correct me if I'm wrong)

so we need to clean it up before it reaches the query I guess?

Possible workaround

Simply ignoring using $exists and create an inverted rule restricting the troublesome fields
edit:it seems nothing can replace the $exists operator for making sure that a property is not set
edit2: availableFields plays an important role in making this workaround work, which is fine but not ideal

remove: No record found for id

The record does not get deleted.
I simply call remove and the initial query looks like this:

{
  _id: '61e950d8fd8d585d5f23530e'
}

Then softDelete and authorize add their stuff and the query ends up like:

{
  _id: '61e950d8fd8d585d5f23530e',
  deleted: { '$ne': true },
  '$and': [ { _id: '61e950d8fd8d585d5f23530e' } ]
}

The remove action fails with the error: No record found for id '61e950d8fd8d585d5f23530e'.
The record exists and is not deleted so it should be good for deletion.
Without the authorize hook it gets deleted.
I assume the redundant $and parameter in the query is the culprit.

My hooks look like this:

import { disablePagination, setNow, softDelete } from 'feathers-hooks-common';
import { authorize } from 'feathers-casl/dist';
import logContext from '../../hooks/log-context';

export default {
  before: {
    all: [authorize({ adapter: 'feathers-mongodb' }), softDelete({})],
    find: [disablePagination()],
    get: [],
    create: [setNow('createdAt')],
    update: [setNow('updatedAt')],
    patch: [setNow('updatedAt')],
    remove: [setNow('updatedAt')]
  },

  after: {
    all: [authorize({ adapter: 'feathers-mongodb' })],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },

  error: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
};

The applied rule for the user doing the delete:
can('manage', 'artisans', { _id: '61e950d8fd8d585d5f23530e' });

How to handle rules with related models ?

Hello,

First, thanks for your effort to create this library, great to discover :-)

I'm asking myself how to do a use case by checking permissions against a related model.

I'm using feathers-objection, that allow use of joinRelated (by using $joinRelation keyword), and let me filter on properties of these relations.

This helps me a lot to check if a user have access to something, for example a Groups (table group) and with which role, eg ADMIN, MEMBER (in a relation based on a table usergroup between Group and Users).

I can do something like that with a filter applied on Group like this one :

{
  "query": {
    "$joinRelation": "users",
    "users.id": "${user.id}"
  }
}

This makes me knowing which groups the user have access, with which role,
according to these data, I can create rules to allow users see groups, or check that they can update groups.

What's the way of doing this with feathers-casl ? Could we handle it ?

Actually, I add a rule with condition { id: { $in: [] } } where ids are retrieved from the previous filter.

Same for limiting power depending on your role.

I'm not happy with this solution, because I think I make too much request on database, am I wrong ?

Maybe that's not the right use case, but I was thinking it could be great to allow keywords of DB adapters ?

What do you think about it ?

Restricting fields access

Hi!

I'm trying to switch from feathers-permissions to CASL and I came across feathers-casl. One of the features I wanted to leverage from CASL is restricting some fields from being updated.

Expected behaviour:

An agency admin should only be able to update roles field and nothing else.

My rules definition:

Screenshot 2021-02-08 at 12 26 35

NB: [angency-users] is the service.

Current behaviour

It allows update for all the fields but restrict the results to only roles.

My question is, how can I restrict update to only roles and deny the others? What I'm I doing wrong?

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.