@iamjs/koa
Advanced tools
+3
-6
| const n = { | ||
| merge: (s, t, r) => { | ||
| if (!s && !t) | ||
| return {}; | ||
| if (!s) | ||
| return t; | ||
| if (!t) | ||
| return s; | ||
| if (!s && !t) return {}; | ||
| if (!s) return t; | ||
| if (!t) return s; | ||
| if (r != null && r.preserve) { | ||
@@ -10,0 +7,0 @@ const e = n.overlaps(s, t), i = n.omit(t, e); |
| import { AuthError, AuthManager, Roles, Schema, TAutorizeOptions } from '@iamjs/core'; | ||
| import { Context, Next } from 'koa'; | ||
| import { ActivityCallbackOptions, IKoaRoleManager, IKoaRoleManagerOptions, TKoaCheckOptions } from '../types'; | ||
| declare class KoaRoleManager<T extends Roles<T>> extends AuthManager<T> implements IKoaRoleManager<T> { | ||
@@ -5,0 +6,0 @@ schema: Schema<T>; |
| import { AuthError, IAuthManager, Roles, Schema, TAutorizeOptions } from '@iamjs/core'; | ||
| import { Context, Next } from 'koa'; | ||
| /** | ||
@@ -4,0 +5,0 @@ * The options for the `onActivity` method |
| import { Role } from '@iamjs/core'; | ||
| import { KoaRoleManager } from '@iamjs/koa'; | ||
| import request from 'supertest'; | ||
| import { default as request } from 'supertest'; | ||
| declare const roleManager: KoaRoleManager<{ | ||
@@ -5,0 +6,0 @@ role: Role<{ |
+3
-3
| { | ||
| "name": "@iamjs/koa", | ||
| "version": "2.0.14", | ||
| "version": "2.0.16", | ||
| "description": "This package contains middleware for integrating the authorization library into a Koa application. ", | ||
@@ -61,6 +61,6 @@ "repository": "https://github.com/triyanox/iamjs", | ||
| }, | ||
| "gitHead": "b83105dd9180ae853e68d16f95f64515e8cc3f34", | ||
| "gitHead": "0f41b633cbd0755524d6ed99252922ee3c2efac0", | ||
| "dependencies": { | ||
| "@iamjs/core": "^2.0.13" | ||
| "@iamjs/core": "^2.0.15" | ||
| } | ||
| } |
+198
-203
@@ -6,6 +6,26 @@ # `@iamjs/koa` | ||
| This package contains the koa middleware for iamjs a library for easy role and permissions management for your koa application. | ||
| ## Overview | ||
| The @iamjs/koa package provides Koa middleware for the iamjs library, enabling seamless integration of role-based access control (RBAC) into your Koa applications. This middleware simplifies the process of managing permissions and authorizing requests based on user roles in a Koa environment. | ||
| ## Table of Contents | ||
| - [Installation](#installation) | ||
| - [Key Features](#key-features) | ||
| - [Usage](#usage) | ||
| - [Basic Authorization](#basic-authorization) | ||
| - [Advanced Usage with Dynamic Role Construction](#advanced-usage-with-dynamic-role-construction) | ||
| - [Custom Success and Error Handling](#custom-success-and-error-handling) | ||
| - [TypeScript Support](#typescript-support) | ||
| - [Logging User Activity](#logging-user-activity) | ||
| - [API Reference](#api-reference) | ||
| - [Best Practices](#best-practices) | ||
| - [Troubleshooting](#troubleshooting) | ||
| - [Contributing](#contributing) | ||
| - [License](#license) | ||
| ## Installation | ||
| To use @iamjs/koa, you need to install both the core library and the Koa middleware: | ||
| ```bash | ||
@@ -21,24 +41,37 @@ npm install @iamjs/core @iamjs/koa | ||
| ## Key Features | ||
| - Seamless integration with Koa applications | ||
| - Flexible role-based access control | ||
| - Support for custom success and error handling | ||
| - TypeScript support for improved type safety | ||
| - Activity logging for auditing and monitoring | ||
| ## Usage | ||
| ### Authorization | ||
| ### Basic Authorization | ||
| You can use the `KoaRoleManager` to authorize a request in your koa application by creating a new instance and using the `check` method. | ||
| Here's a basic example of how to use the @iamjs/koa middleware for authorization: | ||
| Example: | ||
| ```ts | ||
| import { Role, Schema } from '@iamjs/core'; | ||
| import { ExpressRoleManager } from '@iamjs/koa'; | ||
| ```typescript | ||
| import Koa from 'koa'; | ||
| import Router from 'koa-router'; | ||
| import { Role, Schema } from '@iamjs/core'; | ||
| import { KoaRoleManager } from '@iamjs/koa'; | ||
| const role = new Role({ | ||
| name: 'role', | ||
| const app = new Koa(); | ||
| const router = new Router(); | ||
| // Define a role | ||
| const userRole = new Role({ | ||
| name: 'user', | ||
| config: { | ||
| resource1: { | ||
| base: 'crudl' | ||
| posts: { | ||
| base: 'crudl', | ||
| custom: { | ||
| publish: true | ||
| } | ||
| }, | ||
| resource2: { | ||
| base: 'cr-dl' | ||
| comments: { | ||
| base: 'crud-' | ||
| } | ||
@@ -48,6 +81,8 @@ } | ||
| // Create a schema with roles | ||
| const schema = new Schema({ | ||
| roles : { role } | ||
| roles: { user: userRole } | ||
| }); | ||
| // Initialize the KoaRoleManager | ||
| const roleManager = new KoaRoleManager({ | ||
@@ -57,8 +92,6 @@ schema, | ||
| ctx.status = 403; | ||
| ctx.body = 'Forbidden'; | ||
| ctx.body = { error: 'Access denied' }; | ||
| await next(); | ||
| }, | ||
| async onSuccess(ctx, next) { | ||
| ctx.status = 200; | ||
| ctx.body = 'Hello World from the success handler!'; | ||
| await next(); | ||
@@ -68,43 +101,46 @@ } | ||
| router.get( | ||
| '/resource1', | ||
| // Use the middleware to protect a route | ||
| router.get('/posts', | ||
| roleManager.check({ | ||
| resources: 'resource1', | ||
| actions: ['create', 'update'], | ||
| role: 'role', | ||
| strict: true | ||
| resources: 'posts', | ||
| actions: ['read', 'list'], | ||
| role: 'user' | ||
| }), | ||
| (_req, res) => { | ||
| res.send('Hello World!'); | ||
| async (ctx) => { | ||
| ctx.body = { message: 'Posts retrieved successfully' }; | ||
| } | ||
| ); | ||
| app.use(router.routes()); | ||
| app.use(router.routes()).use(router.allowedMethods()); | ||
| app.listen(3000, () => { | ||
| console.log('Server running on http://localhost:3000'); | ||
| }); | ||
| ``` | ||
| ### Advanced Usage | ||
| By using the `construct` option you can use the data from the request to build the role from its own permissions. | ||
| In this example, we define a `user` role with specific permissions for `posts` and `comments` resources. The `KoaRoleManager` is then used to check if the user has the required permissions to access the `/posts` route. | ||
| ```ts | ||
| import { Role, Schema } from '@iamjs/core'; | ||
| import { ExpressRoleManager } from '@iamjs/koa'; | ||
| ### Advanced Usage with Dynamic Role Construction | ||
| For more complex scenarios, you can dynamically construct roles based on request data: | ||
| ```typescript | ||
| import Koa from 'koa'; | ||
| import Router from 'koa-router'; | ||
| import { Role, Schema } from '@iamjs/core'; | ||
| import { KoaRoleManager } from '@iamjs/koa'; | ||
| const role = new Role({ | ||
| name: 'role', | ||
| config: { | ||
| resource1: { | ||
| base: 'crudl' | ||
| }, | ||
| resource2: { | ||
| base: 'cr-dl' | ||
| } | ||
| } | ||
| }); | ||
| // ... (previous role and schema setup) | ||
| const schema = new Schema({ | ||
| roles : { role } | ||
| const app = new Koa(); | ||
| const router = new Router(); | ||
| // Middleware to attach user permissions to the context | ||
| app.use(async (ctx, next) => { | ||
| // In a real app, you'd fetch this from a database or JWT | ||
| ctx.state.userPermissions = userRole.toObject(); | ||
| await next(); | ||
| }); | ||
| // Initialize the KoaRoleManager | ||
| const roleManager = new KoaRoleManager({ | ||
@@ -114,118 +150,63 @@ schema, | ||
| ctx.status = 403; | ||
| ctx.body = 'Forbidden'; | ||
| ctx.body = { error: 'Access denied' }; | ||
| await next(); | ||
| }, | ||
| async onSuccess(ctx, next) { | ||
| ctx.status = 200; | ||
| ctx.body = 'Hello World from the success handler!'; | ||
| await next(); | ||
| } | ||
| }); | ||
| router.get( | ||
| '/resource1', | ||
| router.get('/posts', | ||
| roleManager.check({ | ||
| resources: 'resource1', | ||
| actions: ['create', 'update'], | ||
| resources: 'posts', | ||
| actions: ['read', 'list'], | ||
| strict: true, | ||
| // get the role json or object from the request | ||
| data: async (ctx) => { | ||
| return ctx.permissions | ||
| } | ||
| construct: true, | ||
| data: async (ctx) => ctx.state.userPermissions | ||
| }), | ||
| (_req, res) => { | ||
| res.send('Hello World!'); | ||
| async (ctx) => { | ||
| ctx.body = { message: 'Posts retrieved successfully' }; | ||
| } | ||
| ); | ||
| app.use(router.routes()); | ||
| app.use(router.routes()).use(router.allowedMethods()); | ||
| ``` | ||
| ### Success and Error Handling | ||
| You can pass `onSuccess` and `onError` handlers to the `KoaRoleManager` constructor to handle the success and error cases. | ||
| This approach allows you to construct the role dynamically based on the user's actual permissions, which could be stored in a database or included in a JWT. | ||
| Example: | ||
| ### Custom Success and Error Handling | ||
| You can customize how the middleware handles successful and failed authorization attempts: | ||
| ```typescript | ||
| import { Role, Schema } from '@iamjs/core'; | ||
| import { ExpressRoleManager } from '@iamjs/koa'; | ||
| import Koa from 'koa'; | ||
| import Router from 'koa-router'; | ||
| const role = new Role({ | ||
| name: 'role', | ||
| config: { | ||
| resource1: { | ||
| base: 'crudl' | ||
| }, | ||
| resource2: { | ||
| base: 'cr-dl' | ||
| } | ||
| } | ||
| }); | ||
| const schema = new Schema({ | ||
| roles : { role } | ||
| }); | ||
| const roleManager = new KoaRoleManager({ | ||
| schema, | ||
| async onError(_err, ctx, next) { | ||
| async onError(err, ctx, next) { | ||
| console.error('Authorization failed:', err); | ||
| ctx.status = 403; | ||
| ctx.body = 'Forbidden'; | ||
| ctx.body = { | ||
| error: 'Access denied', | ||
| details: err.message | ||
| }; | ||
| await next(); | ||
| }, | ||
| async onSuccess(ctx, next) { | ||
| ctx.status = 200; | ||
| ctx.body = 'Hello World from the success handler!'; | ||
| console.log('Authorization successful for user:', ctx.state.userId); | ||
| await next(); | ||
| } | ||
| }); | ||
| router.get( | ||
| '/resource1', | ||
| roleManager.check({ | ||
| resources: 'resource1', | ||
| actions: ['create', 'update'], | ||
| role: 'role', | ||
| strict: true | ||
| }), | ||
| (_req, res) => { | ||
| res.send('Hello World!'); | ||
| } | ||
| ); | ||
| app.use(router.routes()); | ||
| ``` | ||
| ### Typescript Support | ||
| These handlers give you fine-grained control over the response sent to the client and allow for custom logging or other actions. | ||
| This package is written in typescript and has type definitions for all the exported types also the `check` method accepts a generic type which can be used to define the type of the context object. | ||
| ### TypeScript Support | ||
| Example: | ||
| The @iamjs/koa package provides strong TypeScript support. You can use generics to specify the types of your context object: | ||
| ```ts | ||
| import { Role, Schema } from '@iamjs/core'; | ||
| import { ExpressRoleManager } from '@iamjs/koa'; | ||
| import Koa from 'koa'; | ||
| ```typescript | ||
| import Koa, { Context } from 'koa'; | ||
| import Router from 'koa-router'; | ||
| const role = new Role({ | ||
| name: 'role', | ||
| config: { | ||
| resource1: { | ||
| base: 'crudl' | ||
| }, | ||
| resource2: { | ||
| base: 'cr-dl' | ||
| } | ||
| } | ||
| }); | ||
| const schema = new Schema({ | ||
| roles : { role } | ||
| }); | ||
| interface CustomContext extends Context { | ||
| somekey: any | ||
| state: { | ||
| userId: string; | ||
| userPermissions: object; | ||
| }; | ||
| } | ||
@@ -235,10 +216,10 @@ | ||
| schema, | ||
| async onError<CustomContext>(_err, ctx, next) { | ||
| ctx.status = 403; | ||
| ctx.body = 'Forbidden'; | ||
| async onSuccess<CustomContext>(ctx, next) { | ||
| console.log('Authorized user:', ctx.state.userId); | ||
| await next(); | ||
| }, | ||
| async onSuccess<CustomContext>(ctx, next) { | ||
| ctx.status = 200; | ||
| ctx.body = 'Hello World from the success handler!'; | ||
| async onError<CustomContext>(err, ctx, next) { | ||
| console.error(`User ${ctx.state.userId} unauthorized:`, err); | ||
| ctx.status = 403; | ||
| ctx.body = { error: 'Access denied' }; | ||
| await next(); | ||
@@ -248,90 +229,104 @@ } | ||
| router.get( | ||
| '/resource1', | ||
| router.get('/posts', | ||
| roleManager.check<CustomContext>({ | ||
| resources: 'resource1', | ||
| actions: ['create', 'update'], | ||
| role: 'role', | ||
| strict: true | ||
| resources: 'posts', | ||
| actions: ['read'], | ||
| role: 'user' | ||
| }), | ||
| (_req, res) => { | ||
| res.send('Hello World!'); | ||
| async (ctx: CustomContext) => { | ||
| ctx.body = { message: `Posts retrieved for user ${ctx.state.userId}` }; | ||
| } | ||
| ); | ||
| app.use(router.routes()); | ||
| ``` | ||
| ### Save users activity | ||
| This ensures type safety throughout your application, reducing the likelihood of runtime errors. | ||
| You can save user activity using the `onActivity` method on the **KoaRoleManager** | ||
| ### Logging User Activity | ||
| The `KoaRoleManager` allows you to log user activity, which can be useful for auditing and monitoring: | ||
| ```typescript | ||
| import { Role, Schema } from "@iamjs/core"; | ||
| import { ExpressRoleManager } from "@iamjs/koa"; | ||
| import Koa from "koa"; | ||
| import Router from "koa-router"; | ||
| const role = new Role({ | ||
| name: "role", | ||
| config: { | ||
| resource1: { | ||
| base: "crudl", | ||
| }, | ||
| resource2: { | ||
| base: "cr-dl", | ||
| }, | ||
| }, | ||
| }); | ||
| const schema = new Schema({ | ||
| roles: { role }, | ||
| }); | ||
| interface CustomContext extends Context { | ||
| somekey: any; | ||
| } | ||
| const roleManager = new KoaRoleManager({ | ||
| schema, | ||
| async onError<CustomContext>(_err, ctx, next) { | ||
| async onError(_err, ctx, next) { | ||
| ctx.status = 403; | ||
| ctx.body = "Forbidden"; | ||
| ctx.body = { error: 'Access denied' }; | ||
| await next(); | ||
| }, | ||
| async onSuccess<CustomContext>(ctx, next) { | ||
| ctx.status = 200; | ||
| ctx.body = "Hello World from the success handler!"; | ||
| await next(); | ||
| }, | ||
| async onActivity(data) { | ||
| // save the activity object | ||
| }, | ||
| console.log('User activity:', data); | ||
| // In a real application, you might want to save this to a database | ||
| await saveActivityLog(data); | ||
| } | ||
| }); | ||
| ``` | ||
| router.get( | ||
| "/resource1", | ||
| roleManager.check<CustomContext>({ | ||
| resources: "resource1", | ||
| actions: ["create", "update"], | ||
| role: "role", | ||
| strict: true, | ||
| }), | ||
| (_req, res) => { | ||
| res.send("Hello World!"); | ||
| } | ||
| ); | ||
| The `onActivity` handler receives an object with the following properties: | ||
| app.use(router.routes()); | ||
| ``` | ||
| | Property | Description | | ||
| |------------|-----------------------------------------------------------| | ||
| | actions | The action(s) that were authorized | | ||
| | resources | The resource(s) that were accessed | | ||
| | role | The role used for authorization | | ||
| | success | Whether the authorization was successful | | ||
| | ctx | The Koa context object (for additional context) | | ||
| The data object contains: | ||
| ## API Reference | ||
| | Name | Description | | ||
| | ---------- | ------------------------------------------------------------------------ | | ||
| | actions? | The action or actions that are authorized to be executed on the resource | | ||
| | resources? | The resource or resources that are authorized to be accessed | | ||
| | role? | The role that is used to authorize the request | | ||
| | success? | The status of the request | | ||
| | ctx? | The context object | | ||
| ### KoaRoleManager | ||
| - `constructor(options: KoaRoleManagerOptions)` | ||
| - `check(options: CheckOptions): Koa.Middleware` | ||
| ### KoaRoleManagerOptions | ||
| - `schema: Schema` - The iamjs Schema containing role definitions | ||
| - `onError?: (err: Error, ctx: Koa.Context, next: Koa.Next) => Promise<void>` | ||
| - `onSuccess?: (ctx: Koa.Context, next: Koa.Next) => Promise<void>` | ||
| - `onActivity?: (data: ActivityData) => Promise<void> | void` | ||
| ### CheckOptions | ||
| - `resources: string | string[]` - The resource(s) being accessed | ||
| - `actions: string[]` - The action(s) being performed | ||
| - `role?: string` - The role to check against (if not using `construct`) | ||
| - `strict?: boolean` - Whether to require all specified permissions | ||
| - `construct?: boolean` - Whether to construct the role dynamically | ||
| - `data?: (ctx: Koa.Context) => Promise<object> | object` - Function to retrieve role data (if `construct` is true) | ||
| ## Best Practices | ||
| 1. **Use Environment-Specific Schemas**: Create different schemas for different environments (development, staging, production) to manage permissions effectively across your deployment pipeline. | ||
| 2. **Implement Role Hierarchies**: Utilize role inheritance to create a hierarchy, reducing duplication and simplifying management. | ||
| 3. **Granular Permissions**: Define permissions at a granular level for fine-tuned access control. | ||
| 4. **Cache Role Data**: For improved performance, consider caching role data, especially if you're constructing roles dynamically. | ||
| 5. **Audit Logs**: Implement comprehensive logging using the `onActivity` handler to maintain an audit trail of all authorization decisions. | ||
| 6. **Error Handling**: Provide clear, informative error messages in your `onError` handler to aid in debugging and improve user experience. | ||
| 7. **Regular Reviews**: Periodically review and update your role definitions and permissions to ensure they align with your application's evolving security requirements. | ||
| ## Troubleshooting | ||
| - **Authorization Always Fails**: Ensure that the role name in `check()` matches the role defined in your schema. | ||
| - **TypeScript Errors**: Make sure you're using the correct types for your context object. | ||
| - **Performance Issues**: If you're seeing slow response times, consider caching role data or optimizing your `data` function in dynamic role construction. | ||
| ## Contributing | ||
| We welcome contributions to @iamjs/koa! If you'd like to contribute, please: | ||
| 1. Fork the repository | ||
| 2. Create a new branch for your feature or bug fix | ||
| 3. Make your changes and write tests if applicable | ||
| 4. Submit a pull request with a clear description of your changes | ||
| Please see our [Contributing Guide](CONTRIBUTING.md) for more detailed information. | ||
| ## License | ||
| @iamjs/koa is released under the [MIT License](LICENSE). See the LICENSE file for more details. |
33193
10.84%534
-0.56%324
-1.82%Updated