# Casbin Middleware for Hono

This is a third-party [Casbin](https://casbin.org) middleware for [Hono](https://github.com/honojs/hono).

This middleware can be used to enforce authorization policies defined using Casbin in your Hono routes.

## Installation

```bash
npm i hono @hono/casbin casbin
```

## Configuration

Before using the middleware, you must set up your Casbin model and policy files.

For details on how to write authorization policies and other information, please refer to the [Casbin documentation](https://casbin.org/).

### Example model.conf

```conf
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")
```

### Example policy.csv

```csv
p, alice, /dataset1/*, *
p, bob, /dataset1/*, GET
```

## Usage with Basic HTTP Authentication

You can perform authorization control after Basic authentication by combining it with `basicAuthorizer`.
(The client needs to send `Authentication: Basic {Base64Encoded(username:password)}`.)

Let's look at an example.
Use the `model` and `policy` files from the [Configuration](#configuration) section.
You can implement a scenario where `alice` and `bob` have different permissions. Alice has access to all methods on `/dataset1/test`, while Bob has access only to the `GET` method.

```ts
import { Hono } from 'hono'
import { basicAuth } from 'hono/basic-auth'
import { newEnforcer } from 'casbin'
import { casbin } from '@hono/casbin'
import { basicAuthorizer } from '@hono/casbin/helper'

const app = new Hono()
app.use('*',
  basicAuth(
    {
      username: 'alice', // alice has full access to /dataset1/test
      password: 'password',
    },
    {
      username: 'bob', // bob cannot post to /dataset1/test
      password: 'password',
    }
  ),
  casbin({
    newEnforcer: newEnforcer('examples/model.conf', 'examples/policy.csv'),
    authorizer: basicAuthorizer
  })
)
app.get('/dataset1/test', (c) => c.text('dataset1 test')) // alice and bob can access /dataset1/test
app.post('/dataset1/test', (c) => c.text('dataset1 test')) // Only alice can access /dataset1/test
```

## Usage with JWT Authentication

By using `jwtAuthorizer`, you can perform authorization control after JWT authentication.
By default, `jwtAuthorizer` uses the `sub` in the JWT payload as the username.

```ts
import { Hono } from 'hono'
import { jwt } from 'hono/jwt'
import { newEnforcer } from 'casbin'
import { casbin } from '@hono/casbin'
import { jwtAuthorizer } from '@hono/casbin/helper'

const app = new Hono()
app.use('*',
  jwt({
    secret: 'it-is-very-secret',
  }),
  casbin({
    newEnforcer: newEnforcer('examples/model.conf', 'examples/policy.csv'),
    authorizer: jwtAuthorizer
  })
)
app.get('/dataset1/test', (c) => c.text('dataset1 test')) // alice and bob can access /dataset1/test
app.post('/dataset1/test', (c) => c.text('dataset1 test')) // Only alice can access /dataset1/test
```

Of course, you can use claims other than the `sub` claim.
Specify the `key` as a user-friendly name and the `value` as the JWT claim name. The `Payload` key used for evaluation in the enforcer will be the `value`.

```ts
const claimMapping = {
  username: 'username',
}
// ...
casbin({
  newEnforcer: newEnforcer('examples/model.conf', 'examples/policy.csv'),
  authorizer: (c, e) => jwtAuthorizer(c, e, claimMapping)
})
```

## Usage with Customized Authorizer

You can also use a customized authorizer function to handle the authorization logic.

```ts
import { Hono } from 'hono'
import { newEnforcer } from 'casbin'
import { casbin } from '@hono/casbin'

const app = new Hono()
app.use('*', casbin({
  newEnforcer: newEnforcer('path-to-your-model.conf', 'path-to-your-policy.csv'),
  authorizer: async (c, enforcer) => {
    const { user, path, method } = c
    return await enforcer.enforce(user, path, method)
  }
}))
```

## Author

sugar-cat https://github.com/sugar-cat7
