Policy Machine for Role Based Access Control
Designed for use with the Bunjil GraphQL server, Wahn is flexible policy based authorization engine. It is inpsired by other policy engines including AWS IAM.
yarn add wahn
npm install wahn
// Typescript
import { Wahn, Policy, PolicyAction, PolicyOperator } from "wahn";
// Define a policy
const policy: Policy = {
// This is a simple policy with only one resource
resources: ["test::resource"],
// You can either Allow or Deny access
action: policyAction.Allow,
// This policy is then attached to the role `authenticated user`
roles: ["authenticated user"],
};
// Now we create a Wahn
const wahn: Wahn = new Wahn({
policies: [policy],
});
// javascript
import { Wahn } from "wahn";
// Define a policy
const policy = {
// This is a simple policy with only one resource
resources: ["test::resource"],
// You can either `Allow` or `Deny` access
action: "Allow",
// This policy is then attached to the role `authenticated user`
roles: ["authenticated user"],
};
// Now we create a Wahn
const wahn = new Wahn({
policies: [policy],
});
Now you have an Wahn
instance with policies, you can attempt to access something.
// typescript
import { RequestContext } from "wahn";
// In your request you will have information about the user, their roles,
// and possibly other information about their session, like their
// OS or IP
// You pass these to `wahn` during your evaluateAccess check via the
// context object
const context: RequestContext = {
user: {
id: "UserId",
roles: ["authenticated user"],
},
};
// Now lets check if our user has access to our test resource
const hasAccess: boolean = wahn.evaluateAccess({
context,
resource: "test::resource",
});
// hasAccess === true
// Now lets check if our user has access to a different resource
const hasAccess: boolean = wahn.evaluateAccess({
context,
resource: "AResourceTheUserCannotAccess",
});
// throws AuthorizationDeniedError
//javascript
import { RequestContext } from "whan";
// In your request you will have information about the user, their roles,
// and possibly other information about their session, like their
// OS or IP
// You pass these to `wahn` during your evaluateAccess check via the
// context object
const context = {
user: {
id: "UserId",
roles: ["authenticated user"],
},
};
// Now lets check if our user has access to our test resource
const hasAccess = wahn.evaluateAccess({
context,
resource: "test::resource",
});
// hasAccess === true
// Now lets check if our user has access to a different resource
const hasAccess = wahn.evaluateAccess({
context,
resource: "AResourceTheUserCannotAccess",
});
// throws AuthorizationDeniedError
You can view the Policy
type detailed type information, below is the plain JSON version.
{
// A action represents the object an action is being performed on.
// In the example below we have two GraphQL paths.
// The resource and verb are both combined here.
actions: [
"query::User:*",
"query::Posts:*",
"mutation::createPost"
]
// There are only two types of effects on the policy Allow and Deny
effect: "Allow",
// Conditions allow futher refinement of the policy, and final outcome.
conditions: [
{
// A dot path to the value on the context object
field: "${user.id}",
operator: "match", // or "notMatch"
expected: ["user.id"],
}
],
// Resources are whats being actioned on
resources: [
// User and all it's fields
"User:*",
// Or a wildcard for everything
"*"
]
// Roles are what the policy is attached to
roles: [
"authenticated user",
"subscriber"
]
}
Conditions provide a powerful way to refine access on a Policy. wahn
makes no assumptions about
your implementation, and so the implementation of conditions is partially dependent on your impelmenation.
A condition scopes a Policy to values you provide in the context object.
When you define a Condition
there are 3 parameters:
field
: a dot path to the key on yourcontext
object.expected
: the value you expect to see (hard coded into the policy)expectedOnContext
: a dot path to the expected value on yourcontext
object. value on the context objectoperator
:match
,notMatch
,lessThan
,greaterThan
You can now add a denyType
string to your deny policy to provide information to your client,
about how to resolve the deny.
The following simple example shows a policy denying with a denyType
of mfa-required
which
can instruct the client to prompt for a MFA token.
// Assemble our policy
const policy: Policy = {
id: "uuid",
resources: ["resource"],
actions: ["action"],
effect: PolicyEffect.Deny,
denyType: "mfa-required",
conditions: [
{
field: "user.sessionAge",
operator: PolicyOperator.greaterThan,
expected: [600],
},
],
roles: ["authenticated user"],
};
Use yarn tests
or npm run tests
.
Tests are written with ava
, and we would strongly like tests with any new functionality.
wahn
needs to be as performant as possible. We use wahn
to keep track of performance
changes. Any new functionality cannot increase the performance beyond resonable limits.
Wahn could either be integrated into your application, or setup as a standalone server.
Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.
We use SemVer for versioning. For the versions available, see the tags on this repository.
- Owen Kelly - ojkelly
This project is licensed under the MIT License - see the LICENSE.md file for details
- Inspired in part by AWS IAM, NIST RBAC
- Behind the name