@nebutra/permissions 0.1.3 → 2.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/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { createPermissions, getPermissions, PermissionsManager, resetPermissions, } from "./factory";
2
2
  export { attachPermissionContext, type RequirePermissionOptions, requirePermission, } from "./middleware";
3
+ export { canPlatform, isPlatformStaffRole, normalizePlatformStaffRole, PLATFORM_STAFF_ROLES, type PlatformAbility, type PlatformAction, type PlatformResource, type PlatformStaffRole, platformAbilityFor, platformRoleHierarchy, } from "./platform";
3
4
  export { buildAbilityFor, CASLProvider, createCASLProvider, createPrismaAbility, getPrismaQuery, } from "./providers/casl";
4
5
  export { createOpenFGAProvider, type OpenFGACheckRequest, type OpenFGACheckResponse, type OpenFGAListObjectsRequest, OpenFGAProvider, type OpenFGAProviderConfig, type OpenFGATuple, type OpenFGAWriteRequest, } from "./providers/openfga";
5
6
  export { Can, Cannot, type CannotProps, type CanProps, PermissionProvider as PermissionProviderComponent, type PermissionProviderProps, type UsePermissionOptions, usePermission, usePermissionContext, } from "./react";
@@ -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;AAEtB,OAAO,EACL,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAE1B,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,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"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { createPermissions, getPermissions, PermissionsManager, resetPermissions, } from "./factory";
2
2
  export { attachPermissionContext, requirePermission, } from "./middleware";
3
+ export { canPlatform, isPlatformStaffRole, normalizePlatformStaffRole, PLATFORM_STAFF_ROLES, platformAbilityFor, platformRoleHierarchy, } from "./platform";
3
4
  export { buildAbilityFor, CASLProvider, createCASLProvider, createPrismaAbility, getPrismaQuery, } from "./providers/casl";
4
5
  export { createOpenFGAProvider, OpenFGAProvider, } from "./providers/openfga";
5
6
  export { Can, Cannot, PermissionProvider as PermissionProviderComponent, usePermission, usePermissionContext, } from "./react";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Platform staff authorization — for the ecosystem control plane (the `admin`
3
+ * host in the brand domain SSOT).
4
+ *
5
+ * Deliberately separate from `DEFAULT_ROLES` in `./roles`. Tenant roles
6
+ * (owner / admin / member / viewer / billing_admin) answer "what may this
7
+ * member do inside their workspace"; platform staff roles answer "what may a
8
+ * platform operator do ACROSS every tenant". Merging the two maps is how a
9
+ * staff capability leaks into a tenant session, so they never share a registry:
10
+ * `platformAbilityFor` builds its ability from scratch instead of going through
11
+ * `CASLProvider`, which always seeds itself with the tenant roles.
12
+ *
13
+ * See docs/plans/2026-07-28-nebutra-admin-control-plane-design.md §6.
14
+ */
15
+ import { type Ability } from "@casl/ability";
16
+ /** Ordered least → most senior. Seniority is linear and inclusive. */
17
+ export declare const PLATFORM_STAFF_ROLES: readonly ["platform_readonly", "platform_support", "platform_operator", "platform_owner"];
18
+ export type PlatformStaffRole = (typeof PLATFORM_STAFF_ROLES)[number];
19
+ export type PlatformAction = "read" | "impersonate" | "invite" | "suspend" | "unsuspend" | "replay" | "override" | "grant" | "revoke";
20
+ export type PlatformResource = "Fleet" | "Tenant" | "RouterSupply" | "ForgeJob" | "FeatureFlag" | "PlatformStaff" | "AuditLog";
21
+ export type PlatformAbility = Ability<[PlatformAction, PlatformResource]>;
22
+ export declare function isPlatformStaffRole(role: string): role is PlatformStaffRole;
23
+ /**
24
+ * Accepts either the TypeScript role string (`platform_owner`) or the Prisma
25
+ * enum spelling (`PLATFORM_OWNER`). Returns null for anything unrecognized —
26
+ * an unknown role must degrade to "no access", never to a default.
27
+ */
28
+ export declare function normalizePlatformStaffRole(role: string | null | undefined): PlatformStaffRole | null;
29
+ /** Roles at or below `role` in seniority, including `role` itself. */
30
+ export declare function platformRoleHierarchy(role: PlatformStaffRole): PlatformStaffRole[];
31
+ export declare function platformAbilityFor(role: PlatformStaffRole): PlatformAbility;
32
+ /**
33
+ * Convenience guard for route handlers. Takes the raw role so callers can pass
34
+ * whatever came out of the `PlatformStaff` row without pre-validating it; an
35
+ * unknown or revoked role simply cannot do anything.
36
+ */
37
+ export declare function canPlatform(role: string | null | undefined, action: PlatformAction, resource: PlatformResource): boolean;
38
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAsC,MAAM,eAAe,CAAC;AAEjF,sEAAsE;AACtE,eAAO,MAAM,oBAAoB,2FAKvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,MAAM,MAAM,cAAc,GACtB,MAAM,GACN,aAAa,GACb,QAAQ,GACR,SAAS,GACT,WAAW,GACX,QAAQ,GACR,UAAU,GACV,OAAO,GACP,QAAQ,CAAC;AAEb,MAAM,MAAM,gBAAgB,GACxB,OAAO,GACP,QAAQ,GACR,cAAc,GACd,UAAU,GACV,aAAa,GACb,eAAe,GACf,UAAU,CAAC;AAEf,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAmC1E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,iBAAiB,CAE3E;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC9B,iBAAiB,GAAG,IAAI,CAI1B;AAED,sEAAsE;AACtE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,EAAE,CAGlF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,eAAe,CAU3E;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAIT"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Platform staff authorization — for the ecosystem control plane (the `admin`
3
+ * host in the brand domain SSOT).
4
+ *
5
+ * Deliberately separate from `DEFAULT_ROLES` in `./roles`. Tenant roles
6
+ * (owner / admin / member / viewer / billing_admin) answer "what may this
7
+ * member do inside their workspace"; platform staff roles answer "what may a
8
+ * platform operator do ACROSS every tenant". Merging the two maps is how a
9
+ * staff capability leaks into a tenant session, so they never share a registry:
10
+ * `platformAbilityFor` builds its ability from scratch instead of going through
11
+ * `CASLProvider`, which always seeds itself with the tenant roles.
12
+ *
13
+ * See docs/plans/2026-07-28-nebutra-admin-control-plane-design.md §6.
14
+ */
15
+ import { AbilityBuilder, createMongoAbility } from "@casl/ability";
16
+ /** Ordered least → most senior. Seniority is linear and inclusive. */
17
+ export const PLATFORM_STAFF_ROLES = [
18
+ "platform_readonly",
19
+ "platform_support",
20
+ "platform_operator",
21
+ "platform_owner",
22
+ ];
23
+ const ALL_RESOURCES = [
24
+ "Fleet",
25
+ "Tenant",
26
+ "RouterSupply",
27
+ "ForgeJob",
28
+ "FeatureFlag",
29
+ "PlatformStaff",
30
+ "AuditLog",
31
+ ];
32
+ /**
33
+ * Capabilities a role adds on top of everything the role below it already has.
34
+ * `platform_readonly` is the floor: read everything, change nothing.
35
+ */
36
+ const CAPABILITIES = {
37
+ platform_readonly: ALL_RESOURCES.map((resource) => ["read", resource]),
38
+ platform_support: [
39
+ ["impersonate", "Tenant"],
40
+ ["invite", "Tenant"],
41
+ ],
42
+ platform_operator: [
43
+ ["suspend", "Tenant"],
44
+ ["unsuspend", "Tenant"],
45
+ ["replay", "ForgeJob"],
46
+ ["override", "FeatureFlag"],
47
+ ],
48
+ // Staff grants are the privilege-escalation edge: only the owner tier moves it.
49
+ platform_owner: [
50
+ ["grant", "PlatformStaff"],
51
+ ["revoke", "PlatformStaff"],
52
+ ],
53
+ };
54
+ export function isPlatformStaffRole(role) {
55
+ return PLATFORM_STAFF_ROLES.includes(role);
56
+ }
57
+ /**
58
+ * Accepts either the TypeScript role string (`platform_owner`) or the Prisma
59
+ * enum spelling (`PLATFORM_OWNER`). Returns null for anything unrecognized —
60
+ * an unknown role must degrade to "no access", never to a default.
61
+ */
62
+ export function normalizePlatformStaffRole(role) {
63
+ if (!role)
64
+ return null;
65
+ const normalized = role.toLowerCase();
66
+ return isPlatformStaffRole(normalized) ? normalized : null;
67
+ }
68
+ /** Roles at or below `role` in seniority, including `role` itself. */
69
+ export function platformRoleHierarchy(role) {
70
+ const ceiling = PLATFORM_STAFF_ROLES.indexOf(role);
71
+ return PLATFORM_STAFF_ROLES.slice(0, ceiling + 1);
72
+ }
73
+ export function platformAbilityFor(role) {
74
+ const { can, build } = new AbilityBuilder(createMongoAbility);
75
+ for (const inherited of platformRoleHierarchy(role)) {
76
+ for (const [action, resource] of CAPABILITIES[inherited]) {
77
+ can(action, resource);
78
+ }
79
+ }
80
+ return build();
81
+ }
82
+ /**
83
+ * Convenience guard for route handlers. Takes the raw role so callers can pass
84
+ * whatever came out of the `PlatformStaff` row without pre-validating it; an
85
+ * unknown or revoked role simply cannot do anything.
86
+ */
87
+ export function canPlatform(role, action, resource) {
88
+ const normalized = normalizePlatformStaffRole(role);
89
+ if (!normalized)
90
+ return false;
91
+ return platformAbilityFor(normalized).can(action, resource);
92
+ }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@nebutra/permissions",
3
- "version": "0.1.3",
3
+ "version": "2.0.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "nebutra": {
8
+ "graph": "core",
8
9
  "status": "foundation",
9
10
  "productionReady": false,
10
11
  "requires": [
@@ -54,13 +55,14 @@
54
55
  "@casl/ability": "^6.7.0",
55
56
  "@casl/prisma": "^1.5.1",
56
57
  "zod": "^4.3.6",
57
- "@nebutra/logger": "0.1.2"
58
+ "@nebutra/logger": "2.0.0"
58
59
  },
59
60
  "peerDependencies": {
60
61
  "react": "^18.0.0 || ^19.0.0",
61
62
  "hono": "^4.0.0"
62
63
  },
63
64
  "devDependencies": {
65
+ "@types/react": "^19.0.0",
64
66
  "react": "^19.0.0",
65
67
  "typescript": "^5.9.3",
66
68
  "vitest": "^4.1.4"