@contractspec/lib.identity-rbac 3.7.25 → 3.8.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 +20 -0
- package/dist/browser/contracts/index.js +1 -1
- package/dist/browser/contracts/rbac.js +1 -1
- package/dist/browser/entities/index.js +1 -1
- package/dist/browser/entities/rbac.js +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/policies/engine.js +1 -1
- package/dist/browser/policies/index.js +1 -1
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/rbac.d.ts +344 -0
- package/dist/contracts/rbac.js +1 -1
- package/dist/entities/index.d.ts +10 -0
- package/dist/entities/index.js +1 -1
- package/dist/entities/rbac.d.ts +14 -0
- package/dist/entities/rbac.js +1 -1
- package/dist/index.js +1 -1
- package/dist/node/contracts/index.js +1 -1
- package/dist/node/contracts/rbac.js +1 -1
- package/dist/node/entities/index.js +1 -1
- package/dist/node/entities/rbac.js +1 -1
- package/dist/node/index.js +1 -1
- package/dist/node/policies/engine.js +1 -1
- package/dist/node/policies/index.js +1 -1
- package/dist/policies/engine.d.ts +73 -3
- package/dist/policies/engine.js +1 -1
- package/dist/policies/engine.test.d.ts +1 -0
- package/dist/policies/index.d.ts +1 -1
- package/dist/policies/index.js +1 -1
- package/package.json +4 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
import{checkCombinedPolicy as V,createPolicyContext as X,PolicyEngine as Y}from"@contractspec/lib.contracts-spec/policy";var E={USER_CREATE:"user.create",USER_READ:"user.read",USER_UPDATE:"user.update",USER_DELETE:"user.delete",USER_LIST:"user.list",USER_MANAGE:"user.manage",ORG_CREATE:"org.create",ORG_READ:"org.read",ORG_UPDATE:"org.update",ORG_DELETE:"org.delete",ORG_LIST:"org.list",MEMBER_INVITE:"member.invite",MEMBER_REMOVE:"member.remove",MEMBER_UPDATE_ROLE:"member.update_role",MEMBER_LIST:"member.list",MANAGE_MEMBERS:"org.manage_members",TEAM_CREATE:"team.create",TEAM_UPDATE:"team.update",TEAM_DELETE:"team.delete",TEAM_MANAGE:"team.manage",ROLE_CREATE:"role.create",ROLE_UPDATE:"role.update",ROLE_DELETE:"role.delete",ROLE_ASSIGN:"role.assign",ROLE_REVOKE:"role.revoke",BILLING_VIEW:"billing.view",BILLING_MANAGE:"billing.manage",PROJECT_CREATE:"project.create",PROJECT_READ:"project.read",PROJECT_UPDATE:"project.update",PROJECT_DELETE:"project.delete",PROJECT_MANAGE:"project.manage",ADMIN_ACCESS:"admin.access",ADMIN_IMPERSONATE:"admin.impersonate"},Z={OWNER:{name:"owner",description:"Organization owner with full access",permissions:Object.values(E)},ADMIN:{name:"admin",description:"Administrator with most permissions",permissions:[E.USER_READ,E.USER_LIST,E.ORG_READ,E.ORG_UPDATE,E.MEMBER_INVITE,E.MEMBER_REMOVE,E.MEMBER_UPDATE_ROLE,E.MEMBER_LIST,E.MANAGE_MEMBERS,E.TEAM_CREATE,E.TEAM_UPDATE,E.TEAM_DELETE,E.TEAM_MANAGE,E.PROJECT_CREATE,E.PROJECT_READ,E.PROJECT_UPDATE,E.PROJECT_DELETE,E.PROJECT_MANAGE,E.BILLING_VIEW]},MEMBER:{name:"member",description:"Regular organization member",permissions:[E.USER_READ,E.ORG_READ,E.MEMBER_LIST,E.PROJECT_READ,E.PROJECT_CREATE]},VIEWER:{name:"viewer",description:"Read-only access",permissions:[E.USER_READ,E.ORG_READ,E.MEMBER_LIST,E.PROJECT_READ]}};class U{bindings;constructor(h=[]){this.bindings=h}resolveEffectiveAccess(h){return J(h,this.bindings,"static")}}class O{async checkPermission(h,S){let{userId:z,orgId:k,permission:y}=h,T=J({userId:z,orgId:k},S,"static");if(T.deniedPermissions.has(y))return{allowed:!1,reason:`Explicit deny for the "${y}" permission`};if(T.permissions.has(y))return{allowed:!0,matchedRole:T.roles.find((W)=>W.permissions.includes(y))?.name};return{allowed:!1,reason:T.roles.length?`No role grants the "${y}" permission`:"No active role bindings found"}}async getPermissions(h,S,z){let k=J({userId:h,orgId:S},z,"static");return{permissions:k.permissions,roles:k.roles}}async hasAnyPermission(h,S,z,k){let{permissions:y}=await this.getPermissions(h,S,k);return z.some((T)=>y.has(T))}async hasAllPermissions(h,S,z,k){let{permissions:y}=await this.getPermissions(h,S,k);return z.every((T)=>y.has(T))}async evaluateRequirement(h){let S=h.mode??(h.source?"dynamic":"static"),z;try{z=h.source?await h.source.resolveEffectiveAccess(h.subject):J(h.subject,h.bindings??[],S)}catch(K){if(h.failClosedOnSourceUnavailable??!0)return{effect:"deny",mode:S,reason:"source_unavailable",source:S,roles:[],permissions:[],missing:Q(h.requirement)};throw K}if(z.sourceUnavailable&&(h.failClosedOnSourceUnavailable??!0))return{effect:"deny",mode:S,reason:"source_unavailable",source:S,roles:z.roles,permissions:[...z.permissions],missing:Q(h.requirement)};z=_(z);let k=(h.subject.roles??[]).filter((K)=>!z.deniedRoles.has(K)),y=G(h.requirement,z,k);if(y.permissions.length||y.roles.length)return{effect:"deny",mode:S,reason:P(y),source:S,roles:z.roles,permissions:[...z.permissions],deniedPermissions:y.permissions,deniedRoles:y.roles,missing:{permissions:y.permissions,roles:y.roles}};let T=X({id:h.subject.userId,tenantId:h.subject.tenantId,roles:[...k,...z.roles.map((K)=>K.name)],permissions:[...h.subject.permissions??[],...z.permissions],attributes:h.subject.attributes??{}}),W=V(T,h.requirement,h.subject.flags??[]);if(!W.allowed)return{effect:"deny",mode:S,reason:W.reason,source:S,roles:z.roles,permissions:[...z.permissions],missing:W.missing};if(h.requirement.policies?.length&&h.policyRegistry){let K=new Y(h.policyRegistry).decide({action:"access",subject:{roles:[...T.roles],attributes:h.subject.attributes},resource:{type:h.requirement.resource?.type??"contractspec.surface",fields:h.requirement.resource?.fields},policies:h.requirement.policies});if(K.effect==="deny")return{...K,mode:S,source:S,roles:z.roles,permissions:[...z.permissions]}}return{effect:"allow",mode:S,reason:z.reasons[0],source:S,roles:z.roles,permissions:[...z.permissions],matched:B(h.requirement,z,k)}}}function J(h,S,z){let k=new Set(h.permissions??[]),y=new Set,T=new Set,W=[],K=[],L=new Date;for(let C of S){if(!$(C,h))continue;if(!w(C,h))continue;if(C.expiresAt&&C.expiresAt<=L)continue;if(C.disabledAt||C.role.disabledAt){for(let H of C.role.permissions)y.add(H);T.add(C.role.name),K.push(C.reason??`Disabled role ${C.role.name}`);continue}if(C.effect==="deny"){for(let H of C.role.permissions)y.add(H);T.add(C.role.name),K.push(C.reason??`Denied role ${C.role.name}`);continue}W.push(C.role);for(let H of C.role.permissions)k.add(H)}for(let C of y)k.delete(C);if(T.size)W=W.filter((C)=>!T.has(C.name));return{permissions:k,roles:W,deniedPermissions:y,deniedRoles:T,source:z,reasons:K}}function _(h){if(!h.deniedPermissions.size&&!h.deniedRoles.size)return h;let S=new Set(h.permissions),z=h.roles.filter((k)=>h.deniedRoles.has(k.name)).flatMap((k)=>k.permissions);for(let k of[...h.deniedPermissions,...z])S.delete(k);return{...h,permissions:S,roles:h.roles.filter((k)=>!h.deniedRoles.has(k.name))}}function $(h,S){if(h.targetType==="user")return h.targetId===S.userId;if(h.targetType==="organization")return h.targetId===(S.organizationId??S.orgId);if(h.targetType==="workspace")return h.targetId===S.workspaceId;if(h.targetType==="tenant")return h.targetId===S.tenantId;return!1}function w(h,S){if(h.tenantId&&h.tenantId!==S.tenantId)return!1;if(h.workspaceId&&h.workspaceId!==S.workspaceId)return!1;if(!h.scopeType||!h.scopeId||h.scopeType==="global")return!0;if(h.scopeType==="tenant")return h.scopeId===S.tenantId;if(h.scopeType==="workspace")return h.scopeId===S.workspaceId;if(h.scopeType==="organization")return h.scopeId===(S.organizationId??S.orgId);if(h.scopeType==="user")return h.scopeId===S.userId;return!1}function G(h,S,z){return{permissions:M(h,S),roles:N(h,S,z)}}function M(h,S){let z=h.permissions??[],k=h.anyPermission??[],y=z.filter((K)=>S.deniedPermissions.has(K)),W=k.some((K)=>S.permissions.has(K))?[]:k.filter((K)=>S.deniedPermissions.has(K));return[...y,...W]}function N(h,S,z){let k=h.roles??[],y=h.anyRole??[],T=new Set([...z,...S.roles.map((C)=>C.name)]),W=k.filter((C)=>S.deniedRoles.has(C)),L=y.some((C)=>T.has(C))?[]:y.filter((C)=>S.deniedRoles.has(C));return[...W,...L]}function P(h){let S=[];if(h.permissions.length)S.push(`permissions: ${h.permissions.join(", ")}`);if(h.roles.length)S.push(`roles: ${h.roles.join(", ")}`);return`Explicit deny for ${S.join("; ")}`}function Q(h){return{roles:[...h.roles??[],...h.anyRole??[]],permissions:[...h.permissions??[],...h.anyPermission??[]],flags:h.flags,policies:h.policies?.map((S)=>`${S.key}.v${S.version}`)}}function B(h,S,z=[]){let k=[...h.roles??[],...h.anyRole??[]],y=S.roles.find((K)=>k.includes(K.name))?.name??z.find((K)=>k.includes(K)),T=[...h.permissions??[],...h.anyPermission??[]].find((K)=>S.permissions.has(K)),W=h.policies?.[0];return{role:y,permission:T,policy:W?`${W.key}.v${W.version}`:void 0}}function F(){return new O}export{F as createRBACEngine,U as StaticRolePermissionSource,Z as StandardRole,O as RBACPolicyEngine,E as Permission};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { PolicyDecision } from '@contractspec/lib.contracts-spec';
|
|
2
|
+
import { PolicyRegistry, type PolicyRequirement } from '@contractspec/lib.contracts-spec/policy';
|
|
1
3
|
/**
|
|
2
4
|
* Standard permissions for identity-rbac module.
|
|
3
5
|
*/
|
|
@@ -79,6 +81,10 @@ export interface PermissionCheckResult {
|
|
|
79
81
|
reason?: string;
|
|
80
82
|
matchedRole?: string;
|
|
81
83
|
}
|
|
84
|
+
export type AuthorizationMode = 'static' | 'dynamic' | 'hybrid';
|
|
85
|
+
export type AuthorizationSource = 'static' | 'dynamic' | 'template';
|
|
86
|
+
export type AuthorizationEffect = 'grant' | 'deny';
|
|
87
|
+
export type AuthorizationScopeType = 'global' | 'tenant' | 'workspace' | 'organization' | 'user';
|
|
82
88
|
/**
|
|
83
89
|
* Role with permissions.
|
|
84
90
|
*/
|
|
@@ -86,6 +92,11 @@ export interface RoleWithPermissions {
|
|
|
86
92
|
id: string;
|
|
87
93
|
name: string;
|
|
88
94
|
permissions: string[];
|
|
95
|
+
description?: string;
|
|
96
|
+
source?: AuthorizationSource;
|
|
97
|
+
templateKey?: string;
|
|
98
|
+
templateVersion?: string;
|
|
99
|
+
disabledAt?: Date | null;
|
|
89
100
|
}
|
|
90
101
|
/**
|
|
91
102
|
* Policy binding for permission evaluation.
|
|
@@ -93,16 +104,69 @@ export interface RoleWithPermissions {
|
|
|
93
104
|
export interface PolicyBindingForEval {
|
|
94
105
|
roleId: string;
|
|
95
106
|
role: RoleWithPermissions;
|
|
96
|
-
targetType: 'user' | 'organization';
|
|
107
|
+
targetType: 'user' | 'organization' | 'workspace' | 'tenant';
|
|
97
108
|
targetId: string;
|
|
98
109
|
expiresAt?: Date | null;
|
|
110
|
+
scopeType?: AuthorizationScopeType;
|
|
111
|
+
scopeId?: string;
|
|
112
|
+
tenantId?: string;
|
|
113
|
+
workspaceId?: string;
|
|
114
|
+
source?: AuthorizationSource;
|
|
115
|
+
templateKey?: string;
|
|
116
|
+
templateVersion?: string;
|
|
117
|
+
effect?: AuthorizationEffect;
|
|
118
|
+
disabledAt?: Date | null;
|
|
119
|
+
reason?: string;
|
|
120
|
+
}
|
|
121
|
+
export interface AuthorizationSubjectContext {
|
|
122
|
+
userId: string;
|
|
123
|
+
orgId?: string;
|
|
124
|
+
organizationId?: string;
|
|
125
|
+
tenantId?: string;
|
|
126
|
+
workspaceId?: string;
|
|
127
|
+
roles?: string[];
|
|
128
|
+
permissions?: string[];
|
|
129
|
+
attributes?: Record<string, unknown>;
|
|
130
|
+
flags?: string[];
|
|
131
|
+
}
|
|
132
|
+
export interface EffectiveAccess {
|
|
133
|
+
permissions: Set<string>;
|
|
134
|
+
roles: RoleWithPermissions[];
|
|
135
|
+
deniedPermissions: Set<string>;
|
|
136
|
+
deniedRoles: Set<string>;
|
|
137
|
+
source: AuthorizationMode | AuthorizationSource;
|
|
138
|
+
reasons: string[];
|
|
139
|
+
sourceUnavailable?: boolean;
|
|
140
|
+
}
|
|
141
|
+
export interface RolePermissionSource {
|
|
142
|
+
resolveEffectiveAccess(context: AuthorizationSubjectContext): Promise<EffectiveAccess> | EffectiveAccess;
|
|
143
|
+
}
|
|
144
|
+
export interface RequirementEvaluationInput {
|
|
145
|
+
requirement: PolicyRequirement;
|
|
146
|
+
subject: AuthorizationSubjectContext;
|
|
147
|
+
bindings?: PolicyBindingForEval[];
|
|
148
|
+
mode?: AuthorizationMode;
|
|
149
|
+
policyRegistry?: PolicyRegistry;
|
|
150
|
+
source?: RolePermissionSource;
|
|
151
|
+
failClosedOnSourceUnavailable?: boolean;
|
|
152
|
+
}
|
|
153
|
+
export interface RequirementEvaluationResult extends PolicyDecision {
|
|
154
|
+
effect: 'allow' | 'deny';
|
|
155
|
+
mode: AuthorizationMode;
|
|
156
|
+
roles: RoleWithPermissions[];
|
|
157
|
+
permissions: string[];
|
|
158
|
+
deniedPermissions?: string[];
|
|
159
|
+
deniedRoles?: string[];
|
|
160
|
+
}
|
|
161
|
+
export declare class StaticRolePermissionSource implements RolePermissionSource {
|
|
162
|
+
private readonly bindings;
|
|
163
|
+
constructor(bindings?: PolicyBindingForEval[]);
|
|
164
|
+
resolveEffectiveAccess(context: AuthorizationSubjectContext): EffectiveAccess;
|
|
99
165
|
}
|
|
100
166
|
/**
|
|
101
167
|
* RBAC Policy Engine for permission checks.
|
|
102
168
|
*/
|
|
103
169
|
export declare class RBACPolicyEngine {
|
|
104
|
-
private roleCache;
|
|
105
|
-
private bindingCache;
|
|
106
170
|
/**
|
|
107
171
|
* Check if a user has a specific permission.
|
|
108
172
|
*/
|
|
@@ -122,6 +186,12 @@ export declare class RBACPolicyEngine {
|
|
|
122
186
|
* Check if user has all of the specified permissions.
|
|
123
187
|
*/
|
|
124
188
|
hasAllPermissions(userId: string, orgId: string | undefined, permissions: string[], bindings: PolicyBindingForEval[]): Promise<boolean>;
|
|
189
|
+
/**
|
|
190
|
+
* Evaluate a shared ContractSpec policy requirement against RBAC bindings or a
|
|
191
|
+
* caller-provided source. Uses contracts-spec PolicyContext/combined guards
|
|
192
|
+
* for role/permission/flag checks instead of duplicating guard semantics.
|
|
193
|
+
*/
|
|
194
|
+
evaluateRequirement(input: RequirementEvaluationInput): Promise<RequirementEvaluationResult>;
|
|
125
195
|
}
|
|
126
196
|
/**
|
|
127
197
|
* Create a new RBAC policy engine instance.
|
package/dist/policies/engine.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var
|
|
2
|
+
import{checkCombinedPolicy as _,createPolicyContext as $,PolicyEngine as h}from"@contractspec/lib.contracts-spec/policy";var O={USER_CREATE:"user.create",USER_READ:"user.read",USER_UPDATE:"user.update",USER_DELETE:"user.delete",USER_LIST:"user.list",USER_MANAGE:"user.manage",ORG_CREATE:"org.create",ORG_READ:"org.read",ORG_UPDATE:"org.update",ORG_DELETE:"org.delete",ORG_LIST:"org.list",MEMBER_INVITE:"member.invite",MEMBER_REMOVE:"member.remove",MEMBER_UPDATE_ROLE:"member.update_role",MEMBER_LIST:"member.list",MANAGE_MEMBERS:"org.manage_members",TEAM_CREATE:"team.create",TEAM_UPDATE:"team.update",TEAM_DELETE:"team.delete",TEAM_MANAGE:"team.manage",ROLE_CREATE:"role.create",ROLE_UPDATE:"role.update",ROLE_DELETE:"role.delete",ROLE_ASSIGN:"role.assign",ROLE_REVOKE:"role.revoke",BILLING_VIEW:"billing.view",BILLING_MANAGE:"billing.manage",PROJECT_CREATE:"project.create",PROJECT_READ:"project.read",PROJECT_UPDATE:"project.update",PROJECT_DELETE:"project.delete",PROJECT_MANAGE:"project.manage",ADMIN_ACCESS:"admin.access",ADMIN_IMPERSONATE:"admin.impersonate"},f={OWNER:{name:"owner",description:"Organization owner with full access",permissions:Object.values(O)},ADMIN:{name:"admin",description:"Administrator with most permissions",permissions:[O.USER_READ,O.USER_LIST,O.ORG_READ,O.ORG_UPDATE,O.MEMBER_INVITE,O.MEMBER_REMOVE,O.MEMBER_UPDATE_ROLE,O.MEMBER_LIST,O.MANAGE_MEMBERS,O.TEAM_CREATE,O.TEAM_UPDATE,O.TEAM_DELETE,O.TEAM_MANAGE,O.PROJECT_CREATE,O.PROJECT_READ,O.PROJECT_UPDATE,O.PROJECT_DELETE,O.PROJECT_MANAGE,O.BILLING_VIEW]},MEMBER:{name:"member",description:"Regular organization member",permissions:[O.USER_READ,O.ORG_READ,O.MEMBER_LIST,O.PROJECT_READ,O.PROJECT_CREATE]},VIEWER:{name:"viewer",description:"Read-only access",permissions:[O.USER_READ,O.ORG_READ,O.MEMBER_LIST,O.PROJECT_READ]}};class w{bindings;constructor(k=[]){this.bindings=k}resolveEffectiveAccess(k){return W(k,this.bindings,"static")}}class Z{async checkPermission(k,z){let{userId:H,orgId:J,permission:K}=k,T=W({userId:H,orgId:J},z,"static");if(T.deniedPermissions.has(K))return{allowed:!1,reason:`Explicit deny for the "${K}" permission`};if(T.permissions.has(K))return{allowed:!0,matchedRole:T.roles.find((U)=>U.permissions.includes(K))?.name};return{allowed:!1,reason:T.roles.length?`No role grants the "${K}" permission`:"No active role bindings found"}}async getPermissions(k,z,H){let J=W({userId:k,orgId:z},H,"static");return{permissions:J.permissions,roles:J.roles}}async hasAnyPermission(k,z,H,J){let{permissions:K}=await this.getPermissions(k,z,J);return H.some((T)=>K.has(T))}async hasAllPermissions(k,z,H,J){let{permissions:K}=await this.getPermissions(k,z,J);return H.every((T)=>K.has(T))}async evaluateRequirement(k){let z=k.mode??(k.source?"dynamic":"static"),H;try{H=k.source?await k.source.resolveEffectiveAccess(k.subject):W(k.subject,k.bindings??[],z)}catch(Q){if(k.failClosedOnSourceUnavailable??!0)return{effect:"deny",mode:z,reason:"source_unavailable",source:z,roles:[],permissions:[],missing:Y(k.requirement)};throw Q}if(H.sourceUnavailable&&(k.failClosedOnSourceUnavailable??!0))return{effect:"deny",mode:z,reason:"source_unavailable",source:z,roles:H.roles,permissions:[...H.permissions],missing:Y(k.requirement)};H=G(H);let J=(k.subject.roles??[]).filter((Q)=>!H.deniedRoles.has(Q)),K=M(k.requirement,H,J);if(K.permissions.length||K.roles.length)return{effect:"deny",mode:z,reason:B(K),source:z,roles:H.roles,permissions:[...H.permissions],deniedPermissions:K.permissions,deniedRoles:K.roles,missing:{permissions:K.permissions,roles:K.roles}};let T=$({id:k.subject.userId,tenantId:k.subject.tenantId,roles:[...J,...H.roles.map((Q)=>Q.name)],permissions:[...k.subject.permissions??[],...H.permissions],attributes:k.subject.attributes??{}}),U=_(T,k.requirement,k.subject.flags??[]);if(!U.allowed)return{effect:"deny",mode:z,reason:U.reason,source:z,roles:H.roles,permissions:[...H.permissions],missing:U.missing};if(k.requirement.policies?.length&&k.policyRegistry){let Q=new h(k.policyRegistry).decide({action:"access",subject:{roles:[...T.roles],attributes:k.subject.attributes},resource:{type:k.requirement.resource?.type??"contractspec.surface",fields:k.requirement.resource?.fields},policies:k.requirement.policies});if(Q.effect==="deny")return{...Q,mode:z,source:z,roles:H.roles,permissions:[...H.permissions]}}return{effect:"allow",mode:z,reason:H.reasons[0],source:z,roles:H.roles,permissions:[...H.permissions],matched:E(k.requirement,H,J)}}}function W(k,z,H){let J=new Set(k.permissions??[]),K=new Set,T=new Set,U=[],Q=[],X=new Date;for(let L of z){if(!N(L,k))continue;if(!S(L,k))continue;if(L.expiresAt&&L.expiresAt<=X)continue;if(L.disabledAt||L.role.disabledAt){for(let V of L.role.permissions)K.add(V);T.add(L.role.name),Q.push(L.reason??`Disabled role ${L.role.name}`);continue}if(L.effect==="deny"){for(let V of L.role.permissions)K.add(V);T.add(L.role.name),Q.push(L.reason??`Denied role ${L.role.name}`);continue}U.push(L.role);for(let V of L.role.permissions)J.add(V)}for(let L of K)J.delete(L);if(T.size)U=U.filter((L)=>!T.has(L.name));return{permissions:J,roles:U,deniedPermissions:K,deniedRoles:T,source:H,reasons:Q}}function G(k){if(!k.deniedPermissions.size&&!k.deniedRoles.size)return k;let z=new Set(k.permissions),H=k.roles.filter((J)=>k.deniedRoles.has(J.name)).flatMap((J)=>J.permissions);for(let J of[...k.deniedPermissions,...H])z.delete(J);return{...k,permissions:z,roles:k.roles.filter((J)=>!k.deniedRoles.has(J.name))}}function N(k,z){if(k.targetType==="user")return k.targetId===z.userId;if(k.targetType==="organization")return k.targetId===(z.organizationId??z.orgId);if(k.targetType==="workspace")return k.targetId===z.workspaceId;if(k.targetType==="tenant")return k.targetId===z.tenantId;return!1}function S(k,z){if(k.tenantId&&k.tenantId!==z.tenantId)return!1;if(k.workspaceId&&k.workspaceId!==z.workspaceId)return!1;if(!k.scopeType||!k.scopeId||k.scopeType==="global")return!0;if(k.scopeType==="tenant")return k.scopeId===z.tenantId;if(k.scopeType==="workspace")return k.scopeId===z.workspaceId;if(k.scopeType==="organization")return k.scopeId===(z.organizationId??z.orgId);if(k.scopeType==="user")return k.scopeId===z.userId;return!1}function M(k,z,H){return{permissions:C(k,z),roles:F(k,z,H)}}function C(k,z){let H=k.permissions??[],J=k.anyPermission??[],K=H.filter((Q)=>z.deniedPermissions.has(Q)),U=J.some((Q)=>z.permissions.has(Q))?[]:J.filter((Q)=>z.deniedPermissions.has(Q));return[...K,...U]}function F(k,z,H){let J=k.roles??[],K=k.anyRole??[],T=new Set([...H,...z.roles.map((L)=>L.name)]),U=J.filter((L)=>z.deniedRoles.has(L)),X=K.some((L)=>T.has(L))?[]:K.filter((L)=>z.deniedRoles.has(L));return[...U,...X]}function B(k){let z=[];if(k.permissions.length)z.push(`permissions: ${k.permissions.join(", ")}`);if(k.roles.length)z.push(`roles: ${k.roles.join(", ")}`);return`Explicit deny for ${z.join("; ")}`}function Y(k){return{roles:[...k.roles??[],...k.anyRole??[]],permissions:[...k.permissions??[],...k.anyPermission??[]],flags:k.flags,policies:k.policies?.map((z)=>`${z.key}.v${z.version}`)}}function E(k,z,H=[]){let J=[...k.roles??[],...k.anyRole??[]],K=z.roles.find((Q)=>J.includes(Q.name))?.name??H.find((Q)=>J.includes(Q)),T=[...k.permissions??[],...k.anyPermission??[]].find((Q)=>z.permissions.has(Q)),U=k.policies?.[0];return{role:K,permission:T,policy:U?`${U.key}.v${U.version}`:void 0}}function j(){return new Z}export{j as createRBACEngine,w as StaticRolePermissionSource,f as StandardRole,Z as RBACPolicyEngine,O as Permission};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/policies/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { createRBACEngine, Permission, type PermissionCheckInput, type PermissionCheckResult, type PermissionKey, type PolicyBindingForEval, RBACPolicyEngine, type RoleWithPermissions, StandardRole, } from './engine';
|
|
1
|
+
export { type AuthorizationEffect, type AuthorizationMode, type AuthorizationScopeType, type AuthorizationSource, type AuthorizationSubjectContext, createRBACEngine, type EffectiveAccess, Permission, type PermissionCheckInput, type PermissionCheckResult, type PermissionKey, type PolicyBindingForEval, RBACPolicyEngine, type RequirementEvaluationInput, type RequirementEvaluationResult, type RolePermissionSource, type RoleWithPermissions, StandardRole, StaticRolePermissionSource, } from './engine';
|
package/dist/policies/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var
|
|
2
|
+
import{checkCombinedPolicy as V,createPolicyContext as X,PolicyEngine as Y}from"@contractspec/lib.contracts-spec/policy";var E={USER_CREATE:"user.create",USER_READ:"user.read",USER_UPDATE:"user.update",USER_DELETE:"user.delete",USER_LIST:"user.list",USER_MANAGE:"user.manage",ORG_CREATE:"org.create",ORG_READ:"org.read",ORG_UPDATE:"org.update",ORG_DELETE:"org.delete",ORG_LIST:"org.list",MEMBER_INVITE:"member.invite",MEMBER_REMOVE:"member.remove",MEMBER_UPDATE_ROLE:"member.update_role",MEMBER_LIST:"member.list",MANAGE_MEMBERS:"org.manage_members",TEAM_CREATE:"team.create",TEAM_UPDATE:"team.update",TEAM_DELETE:"team.delete",TEAM_MANAGE:"team.manage",ROLE_CREATE:"role.create",ROLE_UPDATE:"role.update",ROLE_DELETE:"role.delete",ROLE_ASSIGN:"role.assign",ROLE_REVOKE:"role.revoke",BILLING_VIEW:"billing.view",BILLING_MANAGE:"billing.manage",PROJECT_CREATE:"project.create",PROJECT_READ:"project.read",PROJECT_UPDATE:"project.update",PROJECT_DELETE:"project.delete",PROJECT_MANAGE:"project.manage",ADMIN_ACCESS:"admin.access",ADMIN_IMPERSONATE:"admin.impersonate"},Z={OWNER:{name:"owner",description:"Organization owner with full access",permissions:Object.values(E)},ADMIN:{name:"admin",description:"Administrator with most permissions",permissions:[E.USER_READ,E.USER_LIST,E.ORG_READ,E.ORG_UPDATE,E.MEMBER_INVITE,E.MEMBER_REMOVE,E.MEMBER_UPDATE_ROLE,E.MEMBER_LIST,E.MANAGE_MEMBERS,E.TEAM_CREATE,E.TEAM_UPDATE,E.TEAM_DELETE,E.TEAM_MANAGE,E.PROJECT_CREATE,E.PROJECT_READ,E.PROJECT_UPDATE,E.PROJECT_DELETE,E.PROJECT_MANAGE,E.BILLING_VIEW]},MEMBER:{name:"member",description:"Regular organization member",permissions:[E.USER_READ,E.ORG_READ,E.MEMBER_LIST,E.PROJECT_READ,E.PROJECT_CREATE]},VIEWER:{name:"viewer",description:"Read-only access",permissions:[E.USER_READ,E.ORG_READ,E.MEMBER_LIST,E.PROJECT_READ]}};class U{bindings;constructor(h=[]){this.bindings=h}resolveEffectiveAccess(h){return J(h,this.bindings,"static")}}class O{async checkPermission(h,S){let{userId:z,orgId:k,permission:y}=h,T=J({userId:z,orgId:k},S,"static");if(T.deniedPermissions.has(y))return{allowed:!1,reason:`Explicit deny for the "${y}" permission`};if(T.permissions.has(y))return{allowed:!0,matchedRole:T.roles.find((W)=>W.permissions.includes(y))?.name};return{allowed:!1,reason:T.roles.length?`No role grants the "${y}" permission`:"No active role bindings found"}}async getPermissions(h,S,z){let k=J({userId:h,orgId:S},z,"static");return{permissions:k.permissions,roles:k.roles}}async hasAnyPermission(h,S,z,k){let{permissions:y}=await this.getPermissions(h,S,k);return z.some((T)=>y.has(T))}async hasAllPermissions(h,S,z,k){let{permissions:y}=await this.getPermissions(h,S,k);return z.every((T)=>y.has(T))}async evaluateRequirement(h){let S=h.mode??(h.source?"dynamic":"static"),z;try{z=h.source?await h.source.resolveEffectiveAccess(h.subject):J(h.subject,h.bindings??[],S)}catch(K){if(h.failClosedOnSourceUnavailable??!0)return{effect:"deny",mode:S,reason:"source_unavailable",source:S,roles:[],permissions:[],missing:Q(h.requirement)};throw K}if(z.sourceUnavailable&&(h.failClosedOnSourceUnavailable??!0))return{effect:"deny",mode:S,reason:"source_unavailable",source:S,roles:z.roles,permissions:[...z.permissions],missing:Q(h.requirement)};z=_(z);let k=(h.subject.roles??[]).filter((K)=>!z.deniedRoles.has(K)),y=G(h.requirement,z,k);if(y.permissions.length||y.roles.length)return{effect:"deny",mode:S,reason:P(y),source:S,roles:z.roles,permissions:[...z.permissions],deniedPermissions:y.permissions,deniedRoles:y.roles,missing:{permissions:y.permissions,roles:y.roles}};let T=X({id:h.subject.userId,tenantId:h.subject.tenantId,roles:[...k,...z.roles.map((K)=>K.name)],permissions:[...h.subject.permissions??[],...z.permissions],attributes:h.subject.attributes??{}}),W=V(T,h.requirement,h.subject.flags??[]);if(!W.allowed)return{effect:"deny",mode:S,reason:W.reason,source:S,roles:z.roles,permissions:[...z.permissions],missing:W.missing};if(h.requirement.policies?.length&&h.policyRegistry){let K=new Y(h.policyRegistry).decide({action:"access",subject:{roles:[...T.roles],attributes:h.subject.attributes},resource:{type:h.requirement.resource?.type??"contractspec.surface",fields:h.requirement.resource?.fields},policies:h.requirement.policies});if(K.effect==="deny")return{...K,mode:S,source:S,roles:z.roles,permissions:[...z.permissions]}}return{effect:"allow",mode:S,reason:z.reasons[0],source:S,roles:z.roles,permissions:[...z.permissions],matched:B(h.requirement,z,k)}}}function J(h,S,z){let k=new Set(h.permissions??[]),y=new Set,T=new Set,W=[],K=[],L=new Date;for(let C of S){if(!$(C,h))continue;if(!w(C,h))continue;if(C.expiresAt&&C.expiresAt<=L)continue;if(C.disabledAt||C.role.disabledAt){for(let H of C.role.permissions)y.add(H);T.add(C.role.name),K.push(C.reason??`Disabled role ${C.role.name}`);continue}if(C.effect==="deny"){for(let H of C.role.permissions)y.add(H);T.add(C.role.name),K.push(C.reason??`Denied role ${C.role.name}`);continue}W.push(C.role);for(let H of C.role.permissions)k.add(H)}for(let C of y)k.delete(C);if(T.size)W=W.filter((C)=>!T.has(C.name));return{permissions:k,roles:W,deniedPermissions:y,deniedRoles:T,source:z,reasons:K}}function _(h){if(!h.deniedPermissions.size&&!h.deniedRoles.size)return h;let S=new Set(h.permissions),z=h.roles.filter((k)=>h.deniedRoles.has(k.name)).flatMap((k)=>k.permissions);for(let k of[...h.deniedPermissions,...z])S.delete(k);return{...h,permissions:S,roles:h.roles.filter((k)=>!h.deniedRoles.has(k.name))}}function $(h,S){if(h.targetType==="user")return h.targetId===S.userId;if(h.targetType==="organization")return h.targetId===(S.organizationId??S.orgId);if(h.targetType==="workspace")return h.targetId===S.workspaceId;if(h.targetType==="tenant")return h.targetId===S.tenantId;return!1}function w(h,S){if(h.tenantId&&h.tenantId!==S.tenantId)return!1;if(h.workspaceId&&h.workspaceId!==S.workspaceId)return!1;if(!h.scopeType||!h.scopeId||h.scopeType==="global")return!0;if(h.scopeType==="tenant")return h.scopeId===S.tenantId;if(h.scopeType==="workspace")return h.scopeId===S.workspaceId;if(h.scopeType==="organization")return h.scopeId===(S.organizationId??S.orgId);if(h.scopeType==="user")return h.scopeId===S.userId;return!1}function G(h,S,z){return{permissions:M(h,S),roles:N(h,S,z)}}function M(h,S){let z=h.permissions??[],k=h.anyPermission??[],y=z.filter((K)=>S.deniedPermissions.has(K)),W=k.some((K)=>S.permissions.has(K))?[]:k.filter((K)=>S.deniedPermissions.has(K));return[...y,...W]}function N(h,S,z){let k=h.roles??[],y=h.anyRole??[],T=new Set([...z,...S.roles.map((C)=>C.name)]),W=k.filter((C)=>S.deniedRoles.has(C)),L=y.some((C)=>T.has(C))?[]:y.filter((C)=>S.deniedRoles.has(C));return[...W,...L]}function P(h){let S=[];if(h.permissions.length)S.push(`permissions: ${h.permissions.join(", ")}`);if(h.roles.length)S.push(`roles: ${h.roles.join(", ")}`);return`Explicit deny for ${S.join("; ")}`}function Q(h){return{roles:[...h.roles??[],...h.anyRole??[]],permissions:[...h.permissions??[],...h.anyPermission??[]],flags:h.flags,policies:h.policies?.map((S)=>`${S.key}.v${S.version}`)}}function B(h,S,z=[]){let k=[...h.roles??[],...h.anyRole??[]],y=S.roles.find((K)=>k.includes(K.name))?.name??z.find((K)=>k.includes(K)),T=[...h.permissions??[],...h.anyPermission??[]].find((K)=>S.permissions.has(K)),W=h.policies?.[0];return{role:y,permission:T,policy:W?`${W.key}.v${W.version}`:void 0}}function F(){return new O}export{F as createRBACEngine,U as StaticRolePermissionSource,Z as StandardRole,O as RBACPolicyEngine,E as Permission};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contractspec/lib.identity-rbac",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Identity, Organizations, and RBAC module for ContractSpec applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"contractspec",
|
|
@@ -24,11 +24,12 @@
|
|
|
24
24
|
"lint:fix": "node ../../../scripts/biome.cjs check --write --unsafe --only=nursery/useSortedClasses . && node ../../../scripts/biome.cjs check --write .",
|
|
25
25
|
"lint:check": "node ../../../scripts/biome.cjs check .",
|
|
26
26
|
"prebuild": "contractspec-bun-build prebuild",
|
|
27
|
-
"typecheck": "tsc --noEmit"
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"test": "bun test"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
30
31
|
"@contractspec/lib.schema": "3.7.14",
|
|
31
|
-
"@contractspec/lib.contracts-spec": "6.
|
|
32
|
+
"@contractspec/lib.contracts-spec": "6.2.0",
|
|
32
33
|
"zod": "^4.3.5"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|