@krutai/rbac 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI_REFERENCE.md +140 -0
- package/README.md +216 -0
- package/dist/index.d.mts +390 -0
- package/dist/index.d.ts +390 -0
- package/dist/index.js +440 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +400 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
package/AI_REFERENCE.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# AI Reference — @krutai/rbac
|
|
2
|
+
|
|
3
|
+
## Package Overview
|
|
4
|
+
- **Name**: `@krutai/rbac`
|
|
5
|
+
- **Version**: `0.1.1`
|
|
6
|
+
- **Purpose**: Role-Based Access Control (RBAC) library for KrutAI
|
|
7
|
+
- **Entry**: `src/index.ts` → `dist/index.{js,mjs,d.ts}`
|
|
8
|
+
- **Build**: `tsup` (CJS + ESM dual output, `krutai` is external peer dep)
|
|
9
|
+
|
|
10
|
+
## Dependency Architecture
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
@krutai/rbac@0.1.1
|
|
14
|
+
├── peerDependency: krutai >=0.1.2 ← auto-installed, provides API validation
|
|
15
|
+
└── peerDependency: @krutai/auth >=0.1.0 ← optional
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
> **Important for AI**: The validator (`validateApiKeyFormat`, `ApiKeyValidationError`, etc.) is NOT defined in this package. It is imported from `krutai` and re-exported. Do NOT add a local `validator.ts` here.
|
|
19
|
+
|
|
20
|
+
## File Structure
|
|
21
|
+
```
|
|
22
|
+
packages/rbac/
|
|
23
|
+
├── src/
|
|
24
|
+
│ ├── index.ts # Barrel export (all public API)
|
|
25
|
+
│ ├── types.ts # Core TypeScript interfaces and types
|
|
26
|
+
│ ├── errors.ts # Custom error classes
|
|
27
|
+
│ ├── role.ts # Role/permission helpers + pre-built roles
|
|
28
|
+
│ ├── rbac.ts # RBACManager class (core engine)
|
|
29
|
+
│ └── guards.ts # Guard factories + Express/Next.js middleware
|
|
30
|
+
├── package.json
|
|
31
|
+
├── tsconfig.json
|
|
32
|
+
└── tsup.config.ts
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Key Exports
|
|
36
|
+
|
|
37
|
+
### Classes
|
|
38
|
+
- `RBACManager` — main RBAC engine
|
|
39
|
+
|
|
40
|
+
### Types
|
|
41
|
+
- `Permission` — `string` alias for `"resource:action"` format
|
|
42
|
+
- `Role` — `{ name, permissions, inherits?, description? }`
|
|
43
|
+
- `RBACConfig` — `{ roles, defaultRole? }`
|
|
44
|
+
- `RBACContext` — `{ userId?, roles, metadata? }`
|
|
45
|
+
- `CheckOptions` — `{ requireAll?: boolean }`
|
|
46
|
+
- `PermissionCheckResult` — `{ granted, permissions, roles, missing? }`
|
|
47
|
+
- `GuardFn` — `(context: RBACContext) => boolean`
|
|
48
|
+
|
|
49
|
+
### Helpers
|
|
50
|
+
- `defineRole(config)` — type-safe role definition
|
|
51
|
+
- `definePermission(resource, action)` — creates `"resource:action"` string
|
|
52
|
+
- `crudPermissions(resource)` — creates `[create, read, update, delete]` permissions
|
|
53
|
+
- `wildcardPermission(resource)` — creates `"resource:*"`
|
|
54
|
+
|
|
55
|
+
### Pre-built Roles
|
|
56
|
+
- `GUEST_ROLE`, `USER_ROLE`, `MODERATOR_ROLE`, `ADMIN_ROLE`, `SUPER_ADMIN_ROLE`
|
|
57
|
+
- `DEFAULT_ROLES` — array of all five in hierarchy order
|
|
58
|
+
|
|
59
|
+
### Guard Factories
|
|
60
|
+
- `createPermissionGuard(rbac, permission)` → `GuardFn`
|
|
61
|
+
- `createRoleGuard(rbac, roleName)` → `GuardFn`
|
|
62
|
+
- `createAllPermissionsGuard(rbac, permissions[])` → `GuardFn`
|
|
63
|
+
- `createAnyPermissionGuard(rbac, permissions[])` → `GuardFn`
|
|
64
|
+
|
|
65
|
+
### Middleware
|
|
66
|
+
- `requirePermission(rbac, permission)` — Express/Next.js middleware
|
|
67
|
+
- `requireRole(rbac, roleName)` — Express/Next.js middleware
|
|
68
|
+
- `withPermission(rbac, permission, handler, onDenied?)` — handler wrapper
|
|
69
|
+
|
|
70
|
+
### Errors
|
|
71
|
+
- `RBACError` — base class
|
|
72
|
+
- `PermissionDeniedError(permission, roles)` — access denied
|
|
73
|
+
- `RoleNotFoundError(roleName)` — role not in registry
|
|
74
|
+
- `CircularInheritanceError(chain)` — circular role inheritance
|
|
75
|
+
|
|
76
|
+
### Validator Re-exports (from `krutai`)
|
|
77
|
+
```typescript
|
|
78
|
+
// These are re-exported from krutai — NOT defined here
|
|
79
|
+
export { validateApiKeyFormat, validateApiKeyWithService, createApiKeyChecker, ApiKeyValidationError } from 'krutai';
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## RBACManager API Summary
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Construction
|
|
86
|
+
new RBACManager({ roles: Role[], defaultRole?: string })
|
|
87
|
+
|
|
88
|
+
// Role management
|
|
89
|
+
.addRole(role: Role): void
|
|
90
|
+
.removeRole(name: string): void // throws RoleNotFoundError
|
|
91
|
+
.getRole(name: string): Role | undefined
|
|
92
|
+
.getAllRoles(): Role[]
|
|
93
|
+
|
|
94
|
+
// Permission resolution
|
|
95
|
+
.getPermissionsForRole(name: string): Set<Permission> // throws RoleNotFoundError
|
|
96
|
+
.getPermissionsForRoles(names: string[]): Set<Permission>
|
|
97
|
+
|
|
98
|
+
// Checks
|
|
99
|
+
.can(ctx, permission): boolean
|
|
100
|
+
.cannot(ctx, permission): boolean
|
|
101
|
+
.hasPermission(ctx, permission, opts?): boolean
|
|
102
|
+
.hasAnyPermission(ctx, permissions[]): boolean
|
|
103
|
+
.hasAllPermissions(ctx, permissions[]): boolean
|
|
104
|
+
.hasRole(ctx, roleName): boolean
|
|
105
|
+
.hasAnyRole(ctx, roleNames[]): boolean
|
|
106
|
+
.check(ctx, permissions[], opts?): PermissionCheckResult
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Wildcard Permission Rules
|
|
110
|
+
- `*:*` or `*` → matches any permission
|
|
111
|
+
- `posts:*` → matches any action on `posts` resource
|
|
112
|
+
- `*:read` → matches `read` on any resource
|
|
113
|
+
|
|
114
|
+
## Role Inheritance
|
|
115
|
+
Permissions are resolved recursively. Cycles throw `CircularInheritanceError`.
|
|
116
|
+
Results are cached per role name for performance.
|
|
117
|
+
|
|
118
|
+
## Middleware Contract
|
|
119
|
+
`requirePermission` / `requireRole` expect `req.rbacContext: RBACContext` to be set
|
|
120
|
+
by a preceding auth middleware. Returns 401 if missing, 403 if denied.
|
|
121
|
+
|
|
122
|
+
## tsup Configuration Notes
|
|
123
|
+
- `krutai` → external (peer dep, NOT bundled — do NOT add to `noExternal`)
|
|
124
|
+
- No other special external/noExternal rules
|
|
125
|
+
|
|
126
|
+
## Important Notes
|
|
127
|
+
|
|
128
|
+
1. **Validator lives in `krutai`**: Never add a local `validator.ts` — import from `krutai`
|
|
129
|
+
2. **`krutai` must be external in tsup**: Do NOT add it to `noExternal`
|
|
130
|
+
3. **`krutai` in devDependencies**: Needed for local TypeScript compilation during development
|
|
131
|
+
|
|
132
|
+
## Related Packages
|
|
133
|
+
|
|
134
|
+
- `krutai` — Core utilities and API validation (peer dep)
|
|
135
|
+
- `@krutai/auth` — Authentication with Better Auth (optional peer dep)
|
|
136
|
+
|
|
137
|
+
## Links
|
|
138
|
+
|
|
139
|
+
- GitHub: https://github.com/AccountantAIOrg/krut_packages
|
|
140
|
+
- npm: https://www.npmjs.com/package/@krutai/rbac
|
package/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# @krutai/rbac
|
|
2
|
+
|
|
3
|
+
> Role-Based Access Control (RBAC) for KrutAI — type-safe, inheritance-aware, framework-agnostic.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@krutai/rbac)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- ✅ **Type-safe** — full TypeScript with strict types
|
|
11
|
+
- 🔗 **Role inheritance** — roles can inherit permissions from parent roles
|
|
12
|
+
- 🃏 **Wildcard permissions** — `posts:*` or `*:*` for broad grants
|
|
13
|
+
- 🏗️ **Pre-built roles** — `guest`, `user`, `moderator`, `admin`, `super_admin`
|
|
14
|
+
- 🛡️ **Guard helpers** — framework-agnostic guard factories
|
|
15
|
+
- 🔌 **Middleware** — Express / Next.js-compatible middleware factories
|
|
16
|
+
- 🚨 **Descriptive errors** — `PermissionDeniedError`, `RoleNotFoundError`, `CircularInheritanceError`
|
|
17
|
+
- 🔑 **API Key Validation** — re-exported from `krutai` (auto-installed as peer dep)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @krutai/rbac
|
|
25
|
+
# or
|
|
26
|
+
bun add @krutai/rbac
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
> **Note:** `krutai` is automatically installed as a peer dependency — no extra steps needed.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { RBACManager, defineRole, definePermission } from '@krutai/rbac';
|
|
37
|
+
|
|
38
|
+
// 1. Define your roles
|
|
39
|
+
const rbac = new RBACManager({
|
|
40
|
+
roles: [
|
|
41
|
+
defineRole({
|
|
42
|
+
name: 'user',
|
|
43
|
+
permissions: ['posts:read', 'posts:create'],
|
|
44
|
+
}),
|
|
45
|
+
defineRole({
|
|
46
|
+
name: 'admin',
|
|
47
|
+
permissions: ['posts:delete', 'users:manage'],
|
|
48
|
+
inherits: ['user'], // inherits all user permissions
|
|
49
|
+
}),
|
|
50
|
+
],
|
|
51
|
+
defaultRole: 'user',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// 2. Build a context from your auth session
|
|
55
|
+
const ctx = { userId: 'u_123', roles: ['admin'] };
|
|
56
|
+
|
|
57
|
+
// 3. Check permissions
|
|
58
|
+
rbac.can(ctx, 'posts:read'); // true (inherited from user)
|
|
59
|
+
rbac.can(ctx, 'posts:delete'); // true
|
|
60
|
+
rbac.can(ctx, 'billing:read'); // false
|
|
61
|
+
rbac.cannot(ctx, 'billing:read'); // true
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Core API
|
|
67
|
+
|
|
68
|
+
### `RBACManager`
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const rbac = new RBACManager(config: RBACConfig);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### Role Management
|
|
75
|
+
|
|
76
|
+
| Method | Description |
|
|
77
|
+
|--------|-------------|
|
|
78
|
+
| `addRole(role)` | Register a new role |
|
|
79
|
+
| `removeRole(name)` | Remove a role by name |
|
|
80
|
+
| `getRole(name)` | Get a role definition |
|
|
81
|
+
| `getAllRoles()` | List all registered roles |
|
|
82
|
+
|
|
83
|
+
#### Permission Resolution
|
|
84
|
+
|
|
85
|
+
| Method | Description |
|
|
86
|
+
|--------|-------------|
|
|
87
|
+
| `getPermissionsForRole(name)` | Resolved `Set<Permission>` for a role (includes inherited) |
|
|
88
|
+
| `getPermissionsForRoles(names[])` | Union of permissions across multiple roles |
|
|
89
|
+
|
|
90
|
+
#### Permission Checks
|
|
91
|
+
|
|
92
|
+
| Method | Description |
|
|
93
|
+
|--------|-------------|
|
|
94
|
+
| `can(ctx, permission)` | Returns `true` if context has the permission |
|
|
95
|
+
| `cannot(ctx, permission)` | Inverse of `can` |
|
|
96
|
+
| `hasPermission(ctx, permission)` | Same as `can` |
|
|
97
|
+
| `hasAnyPermission(ctx, permissions[])` | True if context has ≥1 permission |
|
|
98
|
+
| `hasAllPermissions(ctx, permissions[])` | True if context has all permissions |
|
|
99
|
+
| `hasRole(ctx, roleName)` | True if context has the role |
|
|
100
|
+
| `hasAnyRole(ctx, roleNames[])` | True if context has ≥1 role |
|
|
101
|
+
| `check(ctx, permissions[], opts?)` | Detailed result with `granted`, `missing` |
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Permission Strings
|
|
106
|
+
|
|
107
|
+
Permissions follow the `resource:action` convention:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { definePermission, crudPermissions, wildcardPermission } from '@krutai/rbac';
|
|
111
|
+
|
|
112
|
+
definePermission('posts', 'read') // "posts:read"
|
|
113
|
+
crudPermissions('posts') // ["posts:create", "posts:read", "posts:update", "posts:delete"]
|
|
114
|
+
wildcardPermission('posts') // "posts:*"
|
|
115
|
+
wildcardPermission('*') // "*:*" — grants everything
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Role Inheritance
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const rbac = new RBACManager({
|
|
124
|
+
roles: [
|
|
125
|
+
{ name: 'guest', permissions: ['public:read'] },
|
|
126
|
+
{ name: 'user', permissions: ['profile:read'], inherits: ['guest'] },
|
|
127
|
+
{ name: 'moderator', permissions: ['posts:delete'], inherits: ['user'] },
|
|
128
|
+
{ name: 'admin', permissions: ['users:manage'], inherits: ['moderator'] },
|
|
129
|
+
],
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const ctx = { roles: ['moderator'] };
|
|
133
|
+
rbac.can(ctx, 'public:read'); // true (guest → user → moderator)
|
|
134
|
+
rbac.can(ctx, 'profile:read'); // true (user → moderator)
|
|
135
|
+
rbac.can(ctx, 'posts:delete'); // true
|
|
136
|
+
rbac.can(ctx, 'users:manage'); // false (admin only)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Pre-built Roles
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { DEFAULT_ROLES, ADMIN_ROLE, SUPER_ADMIN_ROLE } from '@krutai/rbac';
|
|
145
|
+
|
|
146
|
+
const rbac = new RBACManager({ roles: DEFAULT_ROLES });
|
|
147
|
+
// Includes: guest, user, moderator, admin, super_admin
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Guard Helpers
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { createPermissionGuard, createRoleGuard } from '@krutai/rbac';
|
|
156
|
+
|
|
157
|
+
const canDeletePosts = createPermissionGuard(rbac, 'posts:delete');
|
|
158
|
+
const isAdmin = createRoleGuard(rbac, 'admin');
|
|
159
|
+
|
|
160
|
+
canDeletePosts(ctx); // boolean
|
|
161
|
+
isAdmin(ctx); // boolean
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Express / Next.js Middleware
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { requirePermission, requireRole } from '@krutai/rbac';
|
|
170
|
+
|
|
171
|
+
// Attach rbacContext in your auth middleware first:
|
|
172
|
+
app.use((req, res, next) => {
|
|
173
|
+
req.rbacContext = { userId: req.user.id, roles: req.user.roles };
|
|
174
|
+
next();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Then protect routes:
|
|
178
|
+
app.delete('/posts/:id', requirePermission(rbac, 'posts:delete'), deleteHandler);
|
|
179
|
+
app.get('/admin', requireRole(rbac, 'admin'), adminHandler);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Error Handling
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { PermissionDeniedError, RoleNotFoundError } from '@krutai/rbac';
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
if (rbac.cannot(ctx, 'posts:delete')) {
|
|
191
|
+
throw new PermissionDeniedError('posts:delete', ctx.roles);
|
|
192
|
+
}
|
|
193
|
+
} catch (err) {
|
|
194
|
+
if (err instanceof PermissionDeniedError) {
|
|
195
|
+
console.error(err.message); // "Permission denied: ..."
|
|
196
|
+
console.error(err.permission); // "posts:delete"
|
|
197
|
+
console.error(err.roles); // ["user"]
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## API Key Validation
|
|
205
|
+
|
|
206
|
+
`@krutai/rbac` re-exports the API key validator from `krutai`:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { validateApiKeyFormat, ApiKeyValidationError } from '@krutai/rbac';
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT © KrutAI
|