Getting Started
About
Add access control with CASL to your feathers application.
This project is built for FeathersJS. An open source web framework for building modern real-time applications. It's based on CASL and is a convenient layer to use CASL in your feathers.js-project. Supported versions: @casl/ability^6 and @feathersjs/feathers^5.
Features
- Fully powered by Feathers 5 & CASL 6
- Allows permissions for all methods
create,find,get,update,patch,remove, orcreate,read,update,delete - Define permissions not based on methods:
can('view', 'Settings')(Bring your custom logic) - Restrict by conditions:
can('create', 'Task', { userId: user.id }) - Restrict by individual fields:
cannot('update', 'User', ['roleId']) - Native support for restrictive
$select:can('read', 'User', ['id', 'username'])->$select: ['id', 'username'] - Support to define abilities for anything (providers, users, roles, 3rd party apps, ...)
- Fully supported adapters:
@feathersjs/knex,@feathersjs/memory,@feathersjs/mongodb,feathers-sequelize, not supported:feathers-mongoose,feathers-nedb,feathers-objection - Support for dynamic rules stored in your database (Bring your own implementation 😉 )
- hooks:
checkBasicPermissionhook for client side usage as a before-hookauthorizehook for complex rules- Disallow/allow
multimethods (create,patch,remove) dynamically with:can('remove-multi', 'Task', { userId: user.id })
- channels:
- every connection only receives updates based on rules
channels-support also regards restrictive fields- rules can be defined individually for events
- utils:
checkCanto be used in hooks to check authorization before operations
- Baked in support for
@casl/angular,@casl/react,@casl/vueand@casl/aurelia
Installation
# npm
npm i feathers-casl @casl/ability
# yarn
yarn add feathers-casl @casl/ability
# pnpm
pnpm i feathers-casl @casl/abilityGetting Started
Provide app wide feathers-casl options
// app.ts
import { feathersCasl } from "feathers-casl";
app.configure(feathersCasl());The feathersCasl() function can be configured, to provide app wide options to feathers-casl
Define static rules
For most cases we want to define rules per user (or per user-role). So we first add a function which returns an ability from @casl/ability with these rules:
// src/services/authentication/authentication.abilities.ts
import { Ability, AbilityBuilder, createAliasResolver } from "@casl/ability";
// don't forget this, as `read` is used internally
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'
});
export const defineRulesFor = (user) => {
// also see https://casl.js.org/v6/en/guide/define-rules
const { can, cannot, rules } = new AbilityBuilder(Ability);
if (user.role && user.role.name === "SuperAdmin") {
// SuperAdmin can do evil
can("manage", "all");
return rules;
}
if (user.role && user.role.name === "Admin") {
can("create", "users");
}
can("read", "users");
can("update", "users", { id: user.id });
cannot("update", "users", ["roleId"], { id: user.id });
cannot("delete", "users", { id: user.id });
can("manage", "tasks", { userId: user.id });
can("create-multi", "posts", { userId: user.id });
return rules;
};
export const defineAbilitiesFor = (user) => {
const rules = defineRulesFor(user);
return new Ability(rules, { resolveAction });
};Add abilities to hooks context
feathers-casl by default looks for context.params.ability in the authorize-hook and connection.ability in the channels. You want to authorize users who are authenticated first with @feathers/authentication. We can add hooks to the /authentication service to populate things to context.params and connection under the hood. We use this here to put ability on these objects, which makes it available to all hooks after the authenticate(...)-hook. This way we can define rules in just one place: ``
// src/services/authentication/authentication.hooks.ts
import { defineAbilitiesFor } from "./abilities";
export default {
before: {
all: [],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
},
after: {
all: [],
find: [],
get: [],
create: [
(context) => {
const { user } = context.result;
if (!user) return context;
const ability = defineAbilitiesFor(user);
context.result.ability = ability;
context.result.rules = ability.rules;
return context;
}
],
update: [],
patch: [],
remove: []
},
error: {
all: [],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
}
};Add the authorize-hook to the services
The authorize-hook can be used for all methods and has support for multi: true. You should use it as a before AND a after hook at the same time. For more information, see: authorize hook
// src/services/tasks/tasks.hooks.ts
import { authenticate } from "@feathersjs/authentication";
import { authorize } from "feathers-casl";
// CAUTION! Make sure the adapter name fits your adapter (e.g. @feathersjs/mongodb, @feathersjs/knex, feathers-sequelize, ...)!
// You'll want to have the `authorize` as an early before-hook (right after the `authenticate` hook) and as a late after hook, since it could modify the result based on the ability of the requesting user
const authorizeHook = authorize({ adapter: "@feathersjs/mongodb" });
export default {
before: {
all: [authenticate("jwt")],
find: [authorizeHook],
get: [authorizeHook],
create: [authorizeHook],
update: [authorizeHook],
patch: [authorizeHook],
remove: [authorizeHook]
},
after: {
all: [authorizeHook],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
},
error: {
all: [],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
}
};Filters / Operators
For feathers-casl to work properly, you have to whitelist some operators in your service options. Also make sure to set the adapter option in your authorize hook like: authorize({ adapter: '@feathersjs/mongodb' })
- @feathersjs/memory:
app.use('...', new Service({ filters: { $nor: true }, operators: ["$nor"] })) - @feathersjs/mongodb:
app.use("...', new Service({ filters: { $nor: true }, operators: ["$nor"] )) - @feathersjs/knex: nothing special to configure 😃
- feathers-sequelize: This one is a little bit different than the others. See the following:
import { SequelizeService } from "feathers-sequelize";
import { Op } from "sequelize";
// ...
app.use(
"...",
new SequelizeService({
Model,
operatorMap: {
$not: Op.not
},
filters: {
$not: true
},
operators: ["$not"]
})
);Add CASL to channels
To unleash the full power of feathers-casl you want to add it to your channels.js so every user just gets updates only to items they can really read. It's as simple as the following example. For more information see: channels
// src/channels.ts
import { getChannelsWithReadAbility, makeChannelOptions } from "feathers-casl";
export default function (app) {
if (typeof app.channel !== "function") {
// If no real-time functionality has been configured just return
return;
}
// ...
app.on("login", (authResult: any, { connection }) => {
if (connection) {
// this is needed to map the ability from the authentication hook to the connection so it gets available in the HookContext as `params.ability` automatically
if (authResult.ability) {
connection.ability = authResult.ability;
connection.rules = authResult.rules;
}
// ...
}
});
// ...
const caslOptions = makeChannelOptions(app);
app.publish((data, context) => {
return getChannelsWithReadAbility(app, data, context, caslOptions);
});
}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.
// src/services/tasks/tasks.hooks.ts
export default {
before: {
all: [
authenticate("jwt"),
// Add this to set abilities, if a user exists
(context) => {
const { user } = context.params;
if (user) context.params.ability = defineAbilitiesFor(user);
return context;
}
]
// ...
}
// ...
};Testing
Simply run npm test and all your tests in the test/ directory will be run. The project has full support for Visual Studio Code. You can use the debugger to set breakpoints.
Help
For more information on all the things you can do, visit FeathersJS and CASL.