fastify-casbin-rest
Advanced tools
Comparing version 1.0.0 to 1.1.0
{ | ||
"name": "fastify-casbin-rest", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Plugin for fastify to add support for Casbin REST model", | ||
@@ -5,0 +5,0 @@ "main": "plugin.js", |
@@ -5,2 +5,8 @@ /// <reference types="node" /> | ||
export type Hook = | ||
| 'onRequest' | ||
| 'preParsing' | ||
| 'preValidation' | ||
| 'preHandler' | ||
export interface FastifyCasbinRestOptions { | ||
@@ -11,2 +17,3 @@ getSub?(request: FastifyRequest): string | ||
onDeny?(reply: FastifyReply, sub: string, obj: string, act: string): void | ||
hook?: Hook | ||
} | ||
@@ -13,0 +20,0 @@ |
@@ -12,3 +12,4 @@ 'use strict' | ||
throw new Forbidden(`${sub} not allowed to ${act} ${obj}`) | ||
} | ||
}, | ||
hook: 'preHandler' | ||
} | ||
@@ -25,7 +26,19 @@ | ||
if (routeOptions.casbin && routeOptions.casbin.rest) { | ||
routeOptions.preHandler = async (request, reply) => { | ||
const sub = options.getSub(request) | ||
const obj = options.getObj(request) | ||
const act = options.getAct(request) | ||
const { hook } = options | ||
if (!routeOptions[hook]) { | ||
routeOptions[hook] = [] | ||
} | ||
if (!Array.isArray(routeOptions[hook])) { | ||
routeOptions[hook] = [routeOptions[hook]] | ||
} | ||
const getSub = routeOptions.casbin.rest.getSub || options.getSub | ||
const getObj = routeOptions.casbin.rest.getObj || options.getObj | ||
const getAct = routeOptions.casbin.rest.getAct || options.getAct | ||
routeOptions[hook].push(async (request, reply) => { | ||
const sub = getSub(request) | ||
const obj = getObj(request) | ||
const act = getAct(request) | ||
fastify.log.info({ sub, obj, act }, 'Invoking casbin enforce') | ||
@@ -36,3 +49,3 @@ | ||
} | ||
} | ||
}) | ||
} | ||
@@ -39,0 +52,0 @@ }) |
@@ -7,5 +7,5 @@ # fastify-casbin-rest | ||
A plugin for [Fastify](http://fastify.io/) that adds support for [Casbin's](https://casbin.org/) RESTful model. | ||
A plugin for [Fastify](http://fastify.io/) that adds support for [Casbin](https://casbin.org/) RESTful model. | ||
It depends and builds on top of [fastify-casbin](https://github.com/nearform/fastify-casbin) and provides an opinionated approach to model an authorization scheme based on a RESTful model [Casbin's Node.js APIs](https://github.com/casbin/node-casbin) within a Fastify application. | ||
It depends and builds on top of [fastify-casbin](https://github.com/nearform/fastify-casbin) and provides an opinionated approach to model an authorization scheme based on a RESTful model using [Casbin Node.js APIs](https://github.com/casbin/node-casbin) within a Fastify application. | ||
@@ -43,11 +43,27 @@ ## Install | ||
### Route options | ||
This plugin introduces new route option `casbin.rest`. It can be either a `true` value (which enables default configuration) or an object. | ||
Supported object options: | ||
| Option | Type | Description | Default | | ||
| -------- | ------------------- | -------------------------------- | --------------------------- | | ||
| `getSub` | `Request => string` | Extracts `sub` from the request | Value from plugin options | | ||
| `getObj` | `Request => string` | Extracts `obj` from the request | Value from plugin options | | ||
| `getAct` | `Request => string` | Extracts `act` from the request | Value from plugin options | | ||
### Plugin options | ||
The API exposed by this plugin is the configuration options: | ||
| Option | Type | Description | Default | | ||
| -------- | ------------------------------- | ------------------------------------------------- | ------------------------------- | | ||
| `getSub` | `Request => string` | Extracts `sub` from the request | `r => r.user` | | ||
| `getObj` | `Request => string` | Extracts `obj` from the request | `r => r.url` | | ||
| `getAct` | `Request => string` | Extracts `act` from the request | `r => r.method` | | ||
| `onDeny` | `(Reply, sub, obj, act) => any` | Invoked when Casbin's `enforce` resolves to false | Returns a `403 Forbidden` error | | ||
| Option | Type | Description | Default | | ||
| -------- | ---------------------------------------------------------- | ------------------------------------------------- | ------------------------------- | | ||
| `getSub` | `Request => string` | Extracts `sub` from the request | `r => r.user` | | ||
| `getObj` | `Request => string` | Extracts `obj` from the request | `r => r.url` | | ||
| `getAct` | `Request => string` | Extracts `act` from the request | `r => r.method` | | ||
| `onDeny` | `(Reply, sub, obj, act) => any` | Invoked when Casbin's `enforce` resolves to false | Returns a `403 Forbidden` error | | ||
| `hook` | `'onRequest', 'preParsing', 'preValidation', 'preHandler'` | Which lifecycle to use for performing the check | `'preHandler'` | | ||
Note that extraction rules defined within route options take precedence over the rules defined in the plugin options. | ||
## Examples | ||
@@ -98,5 +114,7 @@ | ||
preValidation: [fastify.authenticate], | ||
// enable fastify-casbin-rest plugin on this route | ||
// enable fastify-casbin-rest plugin on this route, override default "getObj" rule | ||
casbin: { | ||
rest: true | ||
rest: { | ||
getObj: request => request.userId | ||
}, | ||
} | ||
@@ -103,0 +121,0 @@ }, |
@@ -143,1 +143,103 @@ 'use strict' | ||
}) | ||
test('works correctly if there is an existing preHandler hook', t => { | ||
t.plan(4) | ||
let counter = 0 | ||
const fastify = Fastify() | ||
fastify.register(makeStubCasbin()) | ||
fastify.register(plugin) | ||
fastify.get('/', { | ||
preHandler: (req, reply, done) => { | ||
counter++ | ||
done() | ||
}, | ||
casbin: { rest: true } | ||
}, () => 'ok') | ||
fastify.ready(async err => { | ||
t.error(err) | ||
fastify.casbin.enforce.resolves(false) | ||
t.equal((await fastify.inject('/')).statusCode, 403) | ||
t.ok(fastify.casbin.enforce.called) | ||
t.equal(counter, 1) | ||
fastify.close() | ||
}) | ||
}) | ||
test('supports specifying custom hooks', t => { | ||
t.plan(4) | ||
let counter = 0 | ||
const fastify = Fastify() | ||
fastify.register(makeStubCasbin()) | ||
fastify.register(plugin, { hook: 'onRequest' }) | ||
fastify.get('/', { | ||
preParsing: (req, reply, done) => { | ||
counter++ | ||
done() | ||
}, | ||
casbin: { rest: true } | ||
}, () => 'ok') | ||
fastify.ready(async err => { | ||
t.error(err) | ||
fastify.casbin.enforce.resolves(false) | ||
t.equal((await fastify.inject('/')).statusCode, 403) | ||
t.ok(fastify.casbin.enforce.called) | ||
t.equal(counter, 0) | ||
fastify.close() | ||
}) | ||
}) | ||
test('supports overriding plugin rules on route level', t => { | ||
t.plan(4) | ||
const fastify = Fastify() | ||
fastify.register(makeStubCasbin()) | ||
fastify.register(plugin, { | ||
hook: 'onRequest', | ||
getSub: request => request.user, | ||
getObj: request => request.url, | ||
getAct: request => request.method | ||
}) | ||
fastify.get('/', { | ||
casbin: { | ||
rest: { | ||
getSub: request => request.method, | ||
getObj: request => request.user, | ||
getAct: request => request.url | ||
} | ||
} | ||
}, () => 'ok') | ||
fastify.ready(async err => { | ||
t.error(err) | ||
fastify.casbin.enforce.callsFake((sub, obj, act) => { | ||
t.equal(sub, 'GET') | ||
t.equal(obj, undefined) | ||
t.equal(act, '/') | ||
return Promise.resolve(false) | ||
}) | ||
await fastify.inject('/') | ||
fastify.close() | ||
}) | ||
}) |
15588
264
126