@nebutra/permissions 2.0.0 → 3.0.0

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/README.md CHANGED
@@ -1,19 +1,17 @@
1
- > **Status: Foundation** — CASL in-process evaluation supports deterministic role inheritance, ABAC conditions, and field-level rules. OpenFGA support now uses the store-scoped REST API, but model and tuple lifecycle management remain external to this package.
1
+ > **Status: Foundation** — CASL in-process evaluation supports deterministic role inheritance, ABAC conditions, and field-level rules.
2
2
 
3
3
  # @nebutra/permissions
4
4
 
5
- RBAC (Role-Based Access Control) and ABAC (Attribute-Based Access Control) permissions engine for the Nebutra-Sailor monorepo. Built on CASL with optional OpenFGA support for relationship-based access at scale.
5
+ RBAC (Role-Based Access Control) and ABAC (Attribute-Based Access Control) permissions engine for the Nebutra-Sailor monorepo. Built on CASL — the only supported provider.
6
6
 
7
7
  ## Features
8
8
 
9
9
  - **CASL-based in-process evaluation** — Fast, no network calls, great for UI + API middleware
10
- - **OpenFGA integration** — Store-scoped REST checks, writes, deletes, and list-objects calls for managed/self-hosted Zanzibar relationship graphs
11
10
  - **Role hierarchy** — Roles inherit permissions from parent roles; child rules override inherited grants deterministically
12
11
  - **ABAC conditions** — Dynamic field resolution at evaluation time
13
12
  - **Field-level permissions** — Restrict access to specific fields through CASL-backed checks
14
13
  - **React hooks & components** — `usePermission()`, `<Can>` component for UI gate-keeping
15
14
  - **Hono middleware** — Automatic permission checks in API routes
16
- - **Provider auto-detection** — Automatically picks CASL or OpenFGA based on env vars
17
15
 
18
16
  ## Installation
19
17
 
@@ -172,7 +170,7 @@ permissions.can(context, "read", "Document", undefined, "internalNotes"); // fal
172
170
 
173
171
  ## Provider Configuration
174
172
 
175
- ### CASL (Default, In-Process)
173
+ ### CASL (In-Process, only supported provider)
176
174
 
177
175
  Fast, no network calls. Great for UI and API middleware.
178
176
 
@@ -182,50 +180,11 @@ import { createCASLProvider } from "@nebutra/permissions/casl";
182
180
  const provider = createCASLProvider();
183
181
  ```
184
182
 
185
- **Auto-detection:** Uses CASL if no `OPENFGA_API_URL` env var is set.
186
-
187
- ### OpenFGA (Relationship-Based)
188
-
189
- OpenFGA uses the store-scoped REST API and fails closed when configuration or network calls are invalid. This package does not manage OpenFGA authorization models or tuple migrations.
190
-
191
- ```typescript
192
- import { createOpenFGAProvider } from "@nebutra/permissions/openfga";
193
-
194
- const provider = createOpenFGAProvider(
195
- {
196
- apiUrl: "http://openfga.internal:8080",
197
- storeId: "store_abc123",
198
- authToken: process.env.OPENFGA_AUTH_TOKEN,
199
- },
200
- roles
201
- );
202
-
203
- // Write relationship tuples
204
- await provider.write([
205
- { user: "user_123", relation: "member", object: "team:acme" },
206
- { user: "team:acme", relation: "parent", object: "org:company" },
207
- ]);
208
-
209
- // Check access
210
- const allowed = await provider.check(
211
- "user_123",
212
- "can_edit",
213
- "document:doc_456"
214
- );
215
- ```
216
-
217
- **Auto-detection:** Uses OpenFGA if `OPENFGA_API_URL` env var is set.
218
-
219
183
  ## Environment Variables
220
184
 
221
185
  ```env
222
- # Provider selection (auto-detects if empty)
223
- PERMISSIONS_PROVIDER=casl # "casl" | "openfga"
224
-
225
- # OpenFGA configuration
226
- OPENFGA_API_URL=http://openfga:8080 # Triggers OpenFGA provider
227
- OPENFGA_STORE_ID=abc123 # Required for OpenFGA REST calls
228
- OPENFGA_AUTH_TOKEN=secret # Optional for managed OpenFGA
186
+ # Provider selection (optional — casl is the only supported provider)
187
+ PERMISSIONS_PROVIDER=casl
229
188
  ```
230
189
 
231
190
  ## API Reference
@@ -236,11 +195,8 @@ Initialize the global permissions manager (singleton).
236
195
 
237
196
  ```typescript
238
197
  const permissions = createPermissions({
239
- provider: "openfga",
198
+ provider: "casl",
240
199
  roles: customRoles,
241
- openFgaApiUrl: "http://openfga:8080",
242
- openFgaStoreId: "abc123",
243
- openFgaAuthToken: process.env.OPENFGA_AUTH_TOKEN,
244
200
  });
245
201
  ```
246
202
 
@@ -406,16 +362,6 @@ const provider = new CASLProvider();
406
362
  provider.clearCache();
407
363
  ```
408
364
 
409
- ### OpenFGA connection errors
410
-
411
- Verify `OPENFGA_API_URL` is reachable:
412
-
413
- ```bash
414
- curl http://openfga:8080/health
415
- ```
416
-
417
- Check logs for `OpenFGA check error` messages.
418
-
419
365
  ## License
420
366
 
421
367
  MIT
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,MAAM,EACN,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,cAAc,EACd,QAAQ,EACR,cAAc,EACf,MAAM,SAAS,CAAC;AAIjB,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,YAAY,CAAyB;gBAEjC,MAAM,CAAC,EAAE,gBAAgB;IAyBrC,OAAO,CAAC,cAAc;IAmBtB,eAAe,IAAI,sBAAsB;IAIzC,GAAG,CACD,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,OAAO,EACjB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO;IAIV,MAAM,CACJ,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,OAAO,EACjB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO;IAIV,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE;IAI/C,UAAU,CAAC,UAAU,EAAE,cAAc,GAAG,IAAI;IAI5C,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO;IAIpD,WAAW,IAAI,kBAAkB;CAGlC;AAED,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,kBAAkB,CAG/E;AAED,wBAAgB,cAAc,IAAI,kBAAkB,CAKnD;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,MAAM,EACN,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,cAAc,EACd,QAAQ,EACR,cAAc,EACf,MAAM,SAAS,CAAC;AAIjB,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,YAAY,CAAyB;gBAEjC,MAAM,CAAC,EAAE,gBAAgB;IAUrC,OAAO,CAAC,cAAc;IAetB,eAAe,IAAI,sBAAsB;IAIzC,GAAG,CACD,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,OAAO,EACjB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO;IAIV,MAAM,CACJ,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,OAAO,EACjB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO;IAIV,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE;IAI/C,UAAU,CAAC,UAAU,EAAE,cAAc,GAAG,IAAI;IAI5C,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO;IAIpD,WAAW,IAAI,kBAAkB;CAGlC;AAED,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,kBAAkB,CAG/E;AAED,wBAAgB,cAAc,IAAI,kBAAkB,CAKnD;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
package/dist/factory.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { logger } from "@nebutra/logger";
2
2
  import { CASLProvider } from "./providers/casl";
3
- import { OpenFGAProvider } from "./providers/openfga";
4
3
  import { getDefaultRoles } from "./roles";
5
4
  let permissionsInstance = null;
6
5
  export class PermissionsManager {
@@ -10,23 +9,8 @@ export class PermissionsManager {
10
9
  const detectedType = this.detectProvider(config?.provider);
11
10
  this.providerType = detectedType;
12
11
  const roles = config?.roles || getDefaultRoles();
13
- if (detectedType === "openfga") {
14
- // exactOptionalPropertyTypes: only set keys that have defined values so
15
- // the optional `?:` modifier on OpenFGAProviderConfig stays satisfied.
16
- const openFgaConfig = {};
17
- if (config?.openFgaApiUrl !== undefined)
18
- openFgaConfig.apiUrl = config.openFgaApiUrl;
19
- if (config?.openFgaAuthToken !== undefined)
20
- openFgaConfig.authToken = config.openFgaAuthToken;
21
- if (config?.openFgaStoreId !== undefined)
22
- openFgaConfig.storeId = config.openFgaStoreId;
23
- this.provider = new OpenFGAProvider(openFgaConfig, roles);
24
- logger.info("Initialized OpenFGA permissions provider");
25
- }
26
- else {
27
- this.provider = new CASLProvider(roles);
28
- logger.info("Initialized CASL permissions provider");
29
- }
12
+ this.provider = new CASLProvider(roles);
13
+ logger.info("Initialized CASL permissions provider");
30
14
  }
31
15
  detectProvider(explicit) {
32
16
  if (explicit) {
@@ -34,13 +18,10 @@ export class PermissionsManager {
34
18
  }
35
19
  if (process.env.PERMISSIONS_PROVIDER) {
36
20
  const provider = process.env.PERMISSIONS_PROVIDER;
37
- if (["casl", "openfga"].includes(provider)) {
21
+ if (provider === "casl") {
38
22
  return provider;
39
23
  }
40
24
  }
41
- if (process.env.OPENFGA_API_URL) {
42
- return "openfga";
43
- }
44
25
  return "casl";
45
26
  }
46
27
  getProviderType() {
package/dist/index.d.ts CHANGED
@@ -2,7 +2,6 @@ export { createPermissions, getPermissions, PermissionsManager, resetPermissions
2
2
  export { attachPermissionContext, type RequirePermissionOptions, requirePermission, } from "./middleware";
3
3
  export { canPlatform, isPlatformStaffRole, normalizePlatformStaffRole, PLATFORM_STAFF_ROLES, type PlatformAbility, type PlatformAction, type PlatformResource, type PlatformStaffRole, platformAbilityFor, platformRoleHierarchy, } from "./platform";
4
4
  export { buildAbilityFor, CASLProvider, createCASLProvider, createPrismaAbility, getPrismaQuery, } from "./providers/casl";
5
- export { createOpenFGAProvider, type OpenFGACheckRequest, type OpenFGACheckResponse, type OpenFGAListObjectsRequest, OpenFGAProvider, type OpenFGAProviderConfig, type OpenFGATuple, type OpenFGAWriteRequest, } from "./providers/openfga";
6
5
  export { Can, Cannot, type CannotProps, type CanProps, PermissionProvider as PermissionProviderComponent, type PermissionProviderProps, type UsePermissionOptions, usePermission, usePermissionContext, } from "./react";
7
6
  export { DEFAULT_ROLES, getDefaultRoles, getRoleDefinition, getRoleHierarchy, } from "./roles";
8
7
  export { type AbilityOptions, type Action, ForbiddenError, type PermissionConfig, type PermissionContext, type PermissionProvider, type PermissionProviderType, type PermissionRule, type Resource, type Role, type RoleDefinition, } from "./types";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,uBAAuB,EACvB,KAAK,wBAAwB,EAC7B,iBAAiB,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,0BAA0B,EAC1B,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,GAAG,EACH,MAAM,EACN,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,kBAAkB,IAAI,2BAA2B,EACjD,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,aAAa,EACb,oBAAoB,GACrB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,MAAM,EACX,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,IAAI,EACT,KAAK,cAAc,GACpB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,uBAAuB,EACvB,KAAK,wBAAwB,EAC7B,iBAAiB,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,0BAA0B,EAC1B,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,GAAG,EACH,MAAM,EACN,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,kBAAkB,IAAI,2BAA2B,EACjD,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,aAAa,EACb,oBAAoB,GACrB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,MAAM,EACX,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,IAAI,EACT,KAAK,cAAc,GACpB,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -2,7 +2,6 @@ export { createPermissions, getPermissions, PermissionsManager, resetPermissions
2
2
  export { attachPermissionContext, requirePermission, } from "./middleware";
3
3
  export { canPlatform, isPlatformStaffRole, normalizePlatformStaffRole, PLATFORM_STAFF_ROLES, platformAbilityFor, platformRoleHierarchy, } from "./platform";
4
4
  export { buildAbilityFor, CASLProvider, createCASLProvider, createPrismaAbility, getPrismaQuery, } from "./providers/casl";
5
- export { createOpenFGAProvider, OpenFGAProvider, } from "./providers/openfga";
6
5
  export { Can, Cannot, PermissionProvider as PermissionProviderComponent, usePermission, usePermissionContext, } from "./react";
7
6
  export { DEFAULT_ROLES, getDefaultRoles, getRoleDefinition, getRoleHierarchy, } from "./roles";
8
7
  export { ForbiddenError, } from "./types";
package/dist/types.d.ts CHANGED
@@ -28,13 +28,10 @@ export interface PermissionProvider {
28
28
  defineRole(definition: RoleDefinition): void;
29
29
  buildAbilityFor(context: PermissionContext): unknown;
30
30
  }
31
- export type PermissionProviderType = "casl" | "openfga";
31
+ export type PermissionProviderType = "casl";
32
32
  export interface PermissionConfig {
33
33
  provider?: PermissionProviderType;
34
34
  roles?: RoleDefinition[];
35
- openFgaApiUrl?: string;
36
- openFgaStoreId?: string | undefined;
37
- openFgaAuthToken?: string | undefined;
38
35
  }
39
36
  export interface AbilityOptions {
40
37
  resolveAction?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GACd,QAAQ,GACR,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,OAAO,GACP,MAAM,CAAC;AAEX,MAAM,MAAM,QAAQ,GAChB,UAAU,GACV,SAAS,GACT,WAAW,GACX,MAAM,GACN,SAAS,GACT,QAAQ,GACR,MAAM,GACN,QAAQ,GACR,aAAa,GACb,eAAe,GACf,YAAY,GACZ,UAAU,GACV,MAAM,CAAC;AAEX,MAAM,MAAM,IAAI,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,eAAe,GAAG,OAAO,GAAG,MAAM,CAAC;AAEhG,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,QAAQ,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC;IACzB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,CACD,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,OAAO,EACjB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC;IACX,MAAM,CACJ,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,OAAO,EACjB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC;IACX,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9C,UAAU,CAAC,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;IAC7C,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC;CACtD;AAED,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,SAAS,CAAC;AAExD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,qBAAa,cAAe,SAAQ,KAAK;IAE9B,MAAM,EAAE,MAAM;IACd,QAAQ,EAAE,QAAQ;IAClB,OAAO,CAAC,EAAE,iBAAiB;gBAF3B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,iBAAiB,YAAA;CAKrC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GACd,QAAQ,GACR,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,OAAO,GACP,MAAM,CAAC;AAEX,MAAM,MAAM,QAAQ,GAChB,UAAU,GACV,SAAS,GACT,WAAW,GACX,MAAM,GACN,SAAS,GACT,QAAQ,GACR,MAAM,GACN,QAAQ,GACR,aAAa,GACb,eAAe,GACf,YAAY,GACZ,UAAU,GACV,MAAM,CAAC;AAEX,MAAM,MAAM,IAAI,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,eAAe,GAAG,OAAO,GAAG,MAAM,CAAC;AAEhG,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,QAAQ,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC;IACzB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,CACD,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,OAAO,EACjB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC;IACX,MAAM,CACJ,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,OAAO,EACjB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC;IACX,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9C,UAAU,CAAC,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;IAC7C,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC;CACtD;AAED,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE5C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,qBAAa,cAAe,SAAQ,KAAK;IAE9B,MAAM,EAAE,MAAM;IACd,QAAQ,EAAE,QAAQ;IAClB,OAAO,CAAC,EAAE,iBAAiB;gBAF3B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,iBAAiB,YAAA;CAKrC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nebutra/permissions",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,15 +9,14 @@
9
9
  "status": "foundation",
10
10
  "productionReady": false,
11
11
  "requires": [
12
- "CASL (in-process, no external creds)",
13
- "or OPENFGA_API_URL + OPENFGA_STORE_ID for Zanzibar-style"
12
+ "CASL (in-process, no external creds)"
14
13
  ],
15
14
  "gaps": [
16
- "OpenFGA model and tuple lifecycle management remain external"
15
+ "Policies are evaluated in-process only; there is no external relationship store for cross-service authorization"
17
16
  ],
18
17
  "featureId": "permissions",
19
18
  "category": "iam",
20
- "summary": "RBAC/ABAC engine — CASL (in-process) + OpenFGA (Zanzibar)"
19
+ "summary": "RBAC/ABAC engine — CASL (in-process)"
21
20
  },
22
21
  "main": "./dist/index.js",
23
22
  "types": "./dist/index.d.ts",
@@ -30,10 +29,6 @@
30
29
  "types": "./dist/providers/casl.d.ts",
31
30
  "import": "./dist/providers/casl.js"
32
31
  },
33
- "./openfga": {
34
- "types": "./dist/providers/openfga.d.ts",
35
- "import": "./dist/providers/openfga.js"
36
- },
37
32
  "./react": {
38
33
  "types": "./dist/react.d.ts",
39
34
  "import": "./dist/react.js"
@@ -55,7 +50,7 @@
55
50
  "@casl/ability": "^6.7.0",
56
51
  "@casl/prisma": "^1.5.1",
57
52
  "zod": "^4.3.6",
58
- "@nebutra/logger": "2.0.0"
53
+ "@nebutra/logger": "3.0.0"
59
54
  },
60
55
  "peerDependencies": {
61
56
  "react": "^18.0.0 || ^19.0.0",
@@ -1,58 +0,0 @@
1
- import type { Action, PermissionContext, PermissionProvider, PermissionRule, Resource, RoleDefinition } from "../types";
2
- export interface OpenFGATuple {
3
- user: string;
4
- relation: string;
5
- object: string;
6
- }
7
- export interface OpenFGAProviderConfig {
8
- apiUrl?: string | undefined;
9
- storeId?: string | undefined;
10
- authToken?: string | undefined;
11
- }
12
- export interface OpenFGACheckRequest {
13
- tuple_key: {
14
- user: string;
15
- relation: string;
16
- object: string;
17
- };
18
- }
19
- export interface OpenFGACheckResponse {
20
- allowed: boolean;
21
- }
22
- export interface OpenFGAWriteRequest {
23
- writes?: {
24
- tuple_keys: OpenFGATuple[];
25
- };
26
- deletes?: {
27
- tuple_keys: OpenFGATuple[];
28
- };
29
- }
30
- export interface OpenFGAListObjectsRequest {
31
- user: string;
32
- relation: string;
33
- type: string;
34
- }
35
- export declare class OpenFGAProvider implements PermissionProvider {
36
- private apiUrl;
37
- private storeId;
38
- private authToken;
39
- private roleDefinitions;
40
- constructor(config?: string | OpenFGAProviderConfig, roles?: RoleDefinition[]);
41
- defineRole(definition: RoleDefinition): void;
42
- getRulesForRole(role: string): PermissionRule[];
43
- private endpoint;
44
- private headers;
45
- check(user: string, relation: string, object: string): Promise<boolean>;
46
- write(tuples: OpenFGATuple[]): Promise<void>;
47
- delete(tuples: OpenFGATuple[]): Promise<void>;
48
- listObjects(user: string, relation: string, type: string): Promise<string[]>;
49
- can(_context: PermissionContext, _action: Action, _resource: Resource, _subject?: unknown): boolean;
50
- cannot(context: PermissionContext, action: Action, resource: Resource, subject?: unknown): boolean;
51
- canAsync(context: PermissionContext, action: Action, resource: Resource, subject?: unknown): Promise<boolean>;
52
- private formatUser;
53
- private formatObject;
54
- private extractSubjectId;
55
- buildAbilityFor(_context: PermissionContext): unknown;
56
- }
57
- export declare function createOpenFGAProvider(config?: string | OpenFGAProviderConfig, roles?: RoleDefinition[]): OpenFGAProvider;
58
- //# sourceMappingURL=openfga.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"openfga.d.ts","sourceRoot":"","sources":["../../src/providers/openfga.ts"],"names":[],"mappings":"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"}
@@ -1,212 +0,0 @@
1
- import { logger } from "@nebutra/logger";
2
- export class OpenFGAProvider {
3
- apiUrl;
4
- storeId;
5
- authToken;
6
- roleDefinitions = new Map();
7
- constructor(config, roles) {
8
- const resolvedConfig = typeof config === "string" ? { apiUrl: config } : config;
9
- this.apiUrl = resolvedConfig?.apiUrl || process.env.OPENFGA_API_URL || "http://localhost:8080";
10
- const resolvedStoreId = resolvedConfig?.storeId || process.env.OPENFGA_STORE_ID;
11
- if (resolvedStoreId !== undefined)
12
- this.storeId = resolvedStoreId;
13
- const resolvedAuthToken = resolvedConfig?.authToken || process.env.OPENFGA_AUTH_TOKEN;
14
- if (resolvedAuthToken !== undefined)
15
- this.authToken = resolvedAuthToken;
16
- if (roles) {
17
- for (const role of roles) {
18
- this.roleDefinitions.set(role.role, role);
19
- }
20
- }
21
- }
22
- defineRole(definition) {
23
- this.roleDefinitions.set(definition.role, definition);
24
- }
25
- getRulesForRole(role) {
26
- const definition = this.roleDefinitions.get(role);
27
- return definition?.rules || [];
28
- }
29
- endpoint(path) {
30
- if (!this.storeId) {
31
- logger.error("OpenFGA store id is required", { path });
32
- return null;
33
- }
34
- const apiUrl = this.apiUrl.replace(/\/+$/, "");
35
- const storeId = encodeURIComponent(this.storeId);
36
- return `${apiUrl}/stores/${storeId}/${path}`;
37
- }
38
- headers() {
39
- const headers = { "Content-Type": "application/json" };
40
- if (this.authToken) {
41
- headers.Authorization = `Bearer ${this.authToken}`;
42
- }
43
- return headers;
44
- }
45
- async check(user, relation, object) {
46
- const request = {
47
- tuple_key: {
48
- object,
49
- relation,
50
- user,
51
- },
52
- };
53
- const endpoint = this.endpoint("check");
54
- if (!endpoint) {
55
- return false;
56
- }
57
- try {
58
- const response = await fetch(endpoint, {
59
- method: "POST",
60
- headers: this.headers(),
61
- body: JSON.stringify(request),
62
- });
63
- if (!response.ok) {
64
- logger.error("OpenFGA check failed", { status: response.status, user, relation, object });
65
- return false;
66
- }
67
- const data = await response.json();
68
- return data.allowed === true;
69
- }
70
- catch (error) {
71
- logger.error("OpenFGA check error", { error, user, relation, object });
72
- return false;
73
- }
74
- }
75
- async write(tuples) {
76
- const request = {
77
- writes: {
78
- tuple_keys: tuples,
79
- },
80
- };
81
- const endpoint = this.endpoint("write");
82
- if (!endpoint) {
83
- throw new Error("OpenFGA write failed: missing store id");
84
- }
85
- try {
86
- const response = await fetch(endpoint, {
87
- method: "POST",
88
- headers: this.headers(),
89
- body: JSON.stringify(request),
90
- });
91
- if (!response.ok) {
92
- logger.error("OpenFGA write failed", { status: response.status });
93
- throw new Error(`OpenFGA write failed: ${response.statusText}`);
94
- }
95
- }
96
- catch (error) {
97
- logger.error("OpenFGA write error", { error });
98
- throw error;
99
- }
100
- }
101
- async delete(tuples) {
102
- const request = {
103
- deletes: {
104
- tuple_keys: tuples,
105
- },
106
- };
107
- const endpoint = this.endpoint("write");
108
- if (!endpoint) {
109
- throw new Error("OpenFGA delete failed: missing store id");
110
- }
111
- try {
112
- const response = await fetch(endpoint, {
113
- method: "POST",
114
- headers: this.headers(),
115
- body: JSON.stringify(request),
116
- });
117
- if (!response.ok) {
118
- logger.error("OpenFGA delete failed", { status: response.status });
119
- throw new Error(`OpenFGA delete failed: ${response.statusText}`);
120
- }
121
- }
122
- catch (error) {
123
- logger.error("OpenFGA delete error", { error });
124
- throw error;
125
- }
126
- }
127
- async listObjects(user, relation, type) {
128
- const request = {
129
- relation,
130
- type,
131
- user,
132
- };
133
- const endpoint = this.endpoint("list-objects");
134
- if (!endpoint) {
135
- return [];
136
- }
137
- try {
138
- const response = await fetch(endpoint, {
139
- method: "POST",
140
- headers: this.headers(),
141
- body: JSON.stringify(request),
142
- });
143
- if (!response.ok) {
144
- logger.error("OpenFGA listObjects failed", { status: response.status });
145
- return [];
146
- }
147
- const data = await response.json();
148
- return data.objects || [];
149
- }
150
- catch (error) {
151
- logger.error("OpenFGA listObjects error", { error });
152
- return [];
153
- }
154
- }
155
- can(_context, _action, _resource, _subject) {
156
- logger.warn("OpenFGAProvider.can() is synchronous wrapper - use checkAsync() for actual checks");
157
- return false;
158
- }
159
- cannot(context, action, resource, subject) {
160
- return !this.can(context, action, resource, subject);
161
- }
162
- async canAsync(context, action, resource, subject) {
163
- const user = this.formatUser(context.userId);
164
- const object = this.formatObject(resource, subject);
165
- if (!user || !object) {
166
- return false;
167
- }
168
- return this.check(user, action, object);
169
- }
170
- formatUser(userId) {
171
- const trimmed = userId.trim();
172
- if (!trimmed) {
173
- return null;
174
- }
175
- return trimmed.includes(":") ? trimmed : `user:${trimmed}`;
176
- }
177
- formatObject(resource, subject) {
178
- const objectId = this.extractSubjectId(subject);
179
- if (!objectId) {
180
- return null;
181
- }
182
- if (objectId.includes(":")) {
183
- return objectId;
184
- }
185
- const objectType = String(resource).trim();
186
- if (!objectType) {
187
- return null;
188
- }
189
- return `${objectType}:${objectId}`;
190
- }
191
- extractSubjectId(subject) {
192
- if (typeof subject === "string") {
193
- const trimmed = subject.trim();
194
- return trimmed || null;
195
- }
196
- if (subject === null || typeof subject !== "object" || Array.isArray(subject)) {
197
- return null;
198
- }
199
- const id = subject.id;
200
- if (typeof id !== "string") {
201
- return null;
202
- }
203
- const trimmed = id.trim();
204
- return trimmed || null;
205
- }
206
- buildAbilityFor(_context) {
207
- return this;
208
- }
209
- }
210
- export function createOpenFGAProvider(config, roles) {
211
- return new OpenFGAProvider(config, roles);
212
- }