koa2-rbac-router
v0.1.5
Published
Koa2 router with out-of-box role-based access control
Downloads
8
Maintainers
Readme
koa2-rbac-router
Koa2 router middleware with integrated role-based access control.
- Classic routes definition using
router.get
,router.post
,router.put
,router.delete
, etc. - Named URL parameters (with configurable marker).
- Named routes (actions).
- Multiple routers.
- Nestable routers / middlewares.
- ES7 async request handlers.
- Out-of-box simple but yet flexible role-based access control (RBAC).
- MIT License.
Intro
Main goal of this lib is to provide flexible Koa2 router with integrated role-based access
control (RBAC). Basic idea is simple: to perform access control route should be defined with name
(named routes are called 'actions'). Router automatically matches access to the route with RBAC
definitions using route name as RBAC permission (ability) identifier. RBAC role, in general, is a
simple list of accepted actions (route names). When request arrives router fetches context role(s)
(through special callback function ctxRolesFetcher
) and resolves them into list of allowed
actions (route names), then verifies if current route name exists in resolved actions list. If
action is allowed router passes request to downstream handlers or returns 403 Forbidden
HTTP
error otherwise. Additionally, RBAC supports roles inheritance and actions/roles exclusion.
For example:
RBAC.setup({
// define `guest` role
guest: 'index, signup, signin',
// define common `user` role
user: '@guest, ownAction, !signup, !signin'
});
Example above defines:
guest
role with allowed actions:index
,signup
andsignin
;user
role which 'inherits' roleguest
(with@guest
construct), defines own permission:ownAction
and excludes permissionssignup
andsignin
inherited fromguest
role. Resultinguser
role permissions are:index
andownAction
.
Roles might be excluded as well with construction
!@<role>
, what means exclusion of all<role>
permissions recursively: permissions inherited by<role>
get excluded as well.
Futhermore, any dependend role will be automatically adjusted if inherited role gets updated, for example above:
RBAC.apply('guest', 'index, signup, signin, welcome');
adjusts user
role as well, and its resulting permissions set becomes: index
, welcome
, and
ownAction
.
Since roles definitions are simple strings (or arrays) it's easy to store them in files, DBs or any other storages: it's up to a programmer how to handle this. Same is about storing request context roles which are expected to be strings of space and/or comma separated list of role names (or array of role names). Obvious solution here is to use a session storage.
Installation
- npm:
npm install koa2-rbac-router
- yarn:
yarn add koa2-rbac-router
API Reference
Router
new Router ([opts])
Instance members
map(descriptor) => Router
map([name], mapping, handler) => Router
get
|post
|put
|delete
|...
|all([name], path, handler) => Router
use(prefix, mw) => Router|Function
Static members
Error
CTX_ACTION
CTX_PARAMS
HTTP_VERBS
PARAM_MARK
PATH_DELIM
Role-based access control (RBAC)
build([force=false]) => RBAC
imply(name, spec) => RBAC
match(perm, roles) => Boolean
resolve(name) => Set<String>
setup(specs, [prebuild=true]) => RBAC
unset(name) => RBAC
Error
EXCLUDE_MARK
ROLE_REF_MARK
RX_DELIMITER
Example
Router
Exported class.
new Router([opts])
Create a new router instance.
opts
Object
optionalRouter instance configuration properties:
ctxRolesFetcher
[Async]Function
optionalContext roles fetcher. Automatic RBAC checks are disabled if this routine not set.
Call signature:
[async] function (ctx) {
// example:
return ctx.session.roles;
}
prohibitHandler
[Async]Function
optionalRequest prohibit handler (see RBAC). By default (when this option is omitted)
ctx.throw(403)
is used.Call signature:
[async] function (ctx) {
// example:
ctx.body = 'Access denied';
ctx.throw(403);
}
preambleHandler
[Async]Function | Array<[Async]Function>
optionalFunction (or array of functions) to be invoked before any request handlers in call chain.
Call signature:
[async] function (ctx, next) { ... }
`next` is _asynchronous_ downstream invokation routine:
async function (ctx, next) {
// preprocess request
...
// await for downstream handlers
await next();
// postprocess request
...
}
notFoundHandler
[Async]Function
optionalRequest route not found handler.
Call signature:
[async] function (ctx) { // default behaviour: ctx.throw(404); }
noMethodHandler
[Async]Function
optionalRequest method not found handler. By default
opts.notFoundHandler
routine is used (see above).Call signature:
[async] function (ctx) { // example: ctx.throw(501); // return `Not Implemented` HTTP error }
NOTE:
[async]
notation means optionally asynchronous.
Instance members
map(descriptor) => Router
map([name], mapping, handler) => Router
Define new route.
descriptor
Object
Route descriptor object of following options:
name
String
optionalUnique (in scope of all
Router
instances) route name, which is used for matching access permissions (see RBAC below). Unnamed routes are handled unrestricted (with no RBAC checks).mapping
String
requiredRoute mapping of form:
'[<METHOD>] <PATH>'
, where:<METHOD>
is HTTP method name:GET
,POST
,PUT
,DELETE
, etc; omitted or specified as'*'
value means wildcard or default (fallback) HTTP method handler.<PATH>
is route path of form{ /<chunk>|:<param> }
, where:<chunk>
is path chunk and:<param>
is path named parameter (additionally, seePARAM_MARK
).Important: route params MUST be same-named in same route paths. For example, following mappings cause parameters collision error, due to attempt of use different names for same parameter:
'GET /items/:serNum'
'PUT /items/:serNum'
'DELETE /items/:itemId'
- ERROR: parameter should be:serNum
as in other routes of path/items
.
handler
[Async]Function | Array<[Aync]Function>
requiredAsynchronous (optionally) request handling routine (or array of routines).
Call signature:
[async] function (ctx, next) { ... }
next
is asynchronous downstream invocation routine:async function (ctx, next) { // preprocess request ... // await for downstream handlers await next(); // postprocess request ... }
Important: Before calling request specific handler(s) router invokes its
opts.preambleHandler
if it was configured (seenew Router([opts])
).
router.get|post|put|delete|...|all([name], path, handler) => Router
Route classic definition helpers. Functions determine corresponding route HTTP methods. The list of
exposed functions is specified by Router.HTTP_VERBS
parameter.
name
- seemap(...)
name
path
- seemap(...)
mapping <PATH>
handler
- seemap(...)
handler
router.use(prefix, target) => Router | Function
Mount sub-router or middleware function on specified prefix.
prefix
String
requiredTarget mount point. This
prefix
will be cut off fromctx.path
before passing control to downstream handlers.NOTE: at the moment parametrized prefixes are not supported.
target
Router | [Async]Function
requiredTarget
Router
instance or Koa2 common middleware function to be used on specified prefix.
Important: Sub-routers are not enforced to define own config options with new Router([opts])
.
Each subrouter recursively 'inherits' unspecified config options from its parent router(s).
Static members
Router.Error
class
Router-specific error class.
Router.CTX_ACTION
String
Koa context property containing request action (route name). Default: 'action'
.
Router.CTX_PARAMS
String
Koa context property containing request path parameters. Default: 'params'
.
Router.HTTP_VERBS
Array<String>
List of HTTP verbs to expose as old-style Router
methods. Default: ['get', 'post', 'put', 'delete']
.
Router.PARAM_MARK
String
Route parameter marker char. Default: ':'
.
Router.PATH_DELIM
String|RegExp
Delimiter used for splitting route path into chunks. Default: /\/+/
.
Role-based access control (RBAC)
Following methods, classes and options are members of RBAC
namespace.
RBAC.build([force=false]) => RBAC
Preprocess and compile roles (see setup(...)
).
force
Boolean
optionalAll roles re-compilation forcing flag.
RBAC.apply(name, spec) => RBAC
Apply role (new) spec, initiate dependent roles recompilation.
name
String
requiredRole name.
spec
String|Array<String>
requiredRole spec in one of following formats:
String
: space and/or comma separated list of spec tokens;Array<String>
: array of spec tokens.
Spec tokens could be:
route action (named route) inclusion, meaning that specified action is permitted by the role;
another role inclusion, marked by role reference mark (see
ROLE_REF_MARK
, default:@
), meaning recursive inclusion of all actions from specified role;exclusion of action or role, marked by exclude mark (see
EXCLUDE_MARK
, default:!
), meaning removal of an action or recursive removal of all actions from specified role (marked as:!@excludedRoleName
).
Important: appearance order of tokens in role spec DOES matter: precedence grows from left to right, what means, for example, inclusion of role that permits (contains) action
someAction
after exclusion of this action (!someAction
) leads to presense ofsomeAction
in subject role.
RBAC.match(action, roles) => Boolean
Check if action is permitted by specified role(s).
action
String
requiredAction name to match.
roles
String|Array<String>
requiredSingle role name or space and/or comma separated list or array of roles to match.
This method is used internally by Router
to match access permissions (see ctxRolesFetcher
).
RBAC.resolve(name) => Set<String>
Resolve role to a set of permitted actions.
name
String
requiredName of role to resolve.
RBAC.setup(specs, [prebuild=true]) => RBAC
Reset RBAC controller with provided roles specs batch.
specs
Object
requiredRole spec object, where property name is treated as role name and value as role spec (see
RBAC.apply
).prebuild
Boolean
optionalRoles prebuild flag (default:
true
). If set tofalse
none role spec is preprocessed untilRBAC.resolve(...)
orRBAC.build()
invoked.
RBAC.unset(name) => RBAC
Undefine role and initiate dependent roles recompilation.
RBAC.Error
RBAC-specifix error class.
RBAC.EXCLUDE_MARK
Action/role exclude mark. Default: !
.
RBAC.ROLE_REF_MARK
Role reference mark. Default: @
.
RBAC.RX_DELIMITER
Spec delimiter regexp. Default: /[,\s]+/
.
Tests
npm run test
or
yarn test
Tests with coverage:
npm run test+cover
or
yarn test+cover
Example
Please visit koa2-rbac-router-example
License and Copyright
This software is a subject of MIT License:
Copyright 2019 Igor V. Dyukov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.