@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.
@@ -1 +1 @@
1
- var y={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"},q={OWNER:{name:"owner",description:"Organization owner with full access",permissions:Object.values(y)},ADMIN:{name:"admin",description:"Administrator with most permissions",permissions:[y.USER_READ,y.USER_LIST,y.ORG_READ,y.ORG_UPDATE,y.MEMBER_INVITE,y.MEMBER_REMOVE,y.MEMBER_UPDATE_ROLE,y.MEMBER_LIST,y.MANAGE_MEMBERS,y.TEAM_CREATE,y.TEAM_UPDATE,y.TEAM_DELETE,y.TEAM_MANAGE,y.PROJECT_CREATE,y.PROJECT_READ,y.PROJECT_UPDATE,y.PROJECT_DELETE,y.PROJECT_MANAGE,y.BILLING_VIEW]},MEMBER:{name:"member",description:"Regular organization member",permissions:[y.USER_READ,y.ORG_READ,y.MEMBER_LIST,y.PROJECT_READ,y.PROJECT_CREATE]},VIEWER:{name:"viewer",description:"Read-only access",permissions:[y.USER_READ,y.ORG_READ,y.MEMBER_LIST,y.PROJECT_READ]}};class S{roleCache=new Map;bindingCache=new Map;async checkPermission(l,h){let{userId:k,orgId:R,permission:f}=l,x=new Date,W=h.filter((t)=>t.targetType==="user"&&t.targetId===k),F=R?h.filter((t)=>t.targetType==="organization"&&t.targetId===R):[],E=[...W,...F].filter((t)=>!t.expiresAt||t.expiresAt>x);if(E.length===0)return{allowed:!1,reason:"No active role bindings found"};for(let t of E)if(t.role.permissions.includes(f))return{allowed:!0,matchedRole:t.role.name};return{allowed:!1,reason:`No role grants the "${f}" permission`}}async getPermissions(l,h,k){let R=new Date,f=k.filter((t)=>t.targetType==="user"&&t.targetId===l),x=h?k.filter((t)=>t.targetType==="organization"&&t.targetId===h):[],F=[...f,...x].filter((t)=>!t.expiresAt||t.expiresAt>R),K=new Set,E=[];for(let t of F){E.push(t.role);for(let j of t.role.permissions)K.add(j)}return{permissions:K,roles:E}}async hasAnyPermission(l,h,k,R){let{permissions:f}=await this.getPermissions(l,h,R);return k.some((x)=>f.has(x))}async hasAllPermissions(l,h,k,R){let{permissions:f}=await this.getPermissions(l,h,R);return k.every((x)=>f.has(x))}}function z(){return new S}export{z as createRBACEngine,q as StandardRole,S as RBACPolicyEngine,y as Permission};
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.
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- var f={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"},M={OWNER:{name:"owner",description:"Organization owner with full access",permissions:Object.values(f)},ADMIN:{name:"admin",description:"Administrator with most permissions",permissions:[f.USER_READ,f.USER_LIST,f.ORG_READ,f.ORG_UPDATE,f.MEMBER_INVITE,f.MEMBER_REMOVE,f.MEMBER_UPDATE_ROLE,f.MEMBER_LIST,f.MANAGE_MEMBERS,f.TEAM_CREATE,f.TEAM_UPDATE,f.TEAM_DELETE,f.TEAM_MANAGE,f.PROJECT_CREATE,f.PROJECT_READ,f.PROJECT_UPDATE,f.PROJECT_DELETE,f.PROJECT_MANAGE,f.BILLING_VIEW]},MEMBER:{name:"member",description:"Regular organization member",permissions:[f.USER_READ,f.ORG_READ,f.MEMBER_LIST,f.PROJECT_READ,f.PROJECT_CREATE]},VIEWER:{name:"viewer",description:"Read-only access",permissions:[f.USER_READ,f.ORG_READ,f.MEMBER_LIST,f.PROJECT_READ]}};class K{roleCache=new Map;bindingCache=new Map;async checkPermission(z,j){let{userId:k,orgId:q,permission:x}=z,D=new Date,J=j.filter((h)=>h.targetType==="user"&&h.targetId===k),G=q?j.filter((h)=>h.targetType==="organization"&&h.targetId===q):[],F=[...J,...G].filter((h)=>!h.expiresAt||h.expiresAt>D);if(F.length===0)return{allowed:!1,reason:"No active role bindings found"};for(let h of F)if(h.role.permissions.includes(x))return{allowed:!0,matchedRole:h.role.name};return{allowed:!1,reason:`No role grants the "${x}" permission`}}async getPermissions(z,j,k){let q=new Date,x=k.filter((h)=>h.targetType==="user"&&h.targetId===z),D=j?k.filter((h)=>h.targetType==="organization"&&h.targetId===j):[],G=[...x,...D].filter((h)=>!h.expiresAt||h.expiresAt>q),H=new Set,F=[];for(let h of G){F.push(h.role);for(let L of h.role.permissions)H.add(L)}return{permissions:H,roles:F}}async hasAnyPermission(z,j,k,q){let{permissions:x}=await this.getPermissions(z,j,q);return k.some((D)=>x.has(D))}async hasAllPermissions(z,j,k,q){let{permissions:x}=await this.getPermissions(z,j,q);return k.every((D)=>x.has(D))}}function N(){return new K}export{N as createRBACEngine,M as StandardRole,K as RBACPolicyEngine,f as Permission};
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 {};
@@ -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';
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- var y={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"},q={OWNER:{name:"owner",description:"Organization owner with full access",permissions:Object.values(y)},ADMIN:{name:"admin",description:"Administrator with most permissions",permissions:[y.USER_READ,y.USER_LIST,y.ORG_READ,y.ORG_UPDATE,y.MEMBER_INVITE,y.MEMBER_REMOVE,y.MEMBER_UPDATE_ROLE,y.MEMBER_LIST,y.MANAGE_MEMBERS,y.TEAM_CREATE,y.TEAM_UPDATE,y.TEAM_DELETE,y.TEAM_MANAGE,y.PROJECT_CREATE,y.PROJECT_READ,y.PROJECT_UPDATE,y.PROJECT_DELETE,y.PROJECT_MANAGE,y.BILLING_VIEW]},MEMBER:{name:"member",description:"Regular organization member",permissions:[y.USER_READ,y.ORG_READ,y.MEMBER_LIST,y.PROJECT_READ,y.PROJECT_CREATE]},VIEWER:{name:"viewer",description:"Read-only access",permissions:[y.USER_READ,y.ORG_READ,y.MEMBER_LIST,y.PROJECT_READ]}};class S{roleCache=new Map;bindingCache=new Map;async checkPermission(l,h){let{userId:k,orgId:R,permission:f}=l,x=new Date,W=h.filter((t)=>t.targetType==="user"&&t.targetId===k),F=R?h.filter((t)=>t.targetType==="organization"&&t.targetId===R):[],E=[...W,...F].filter((t)=>!t.expiresAt||t.expiresAt>x);if(E.length===0)return{allowed:!1,reason:"No active role bindings found"};for(let t of E)if(t.role.permissions.includes(f))return{allowed:!0,matchedRole:t.role.name};return{allowed:!1,reason:`No role grants the "${f}" permission`}}async getPermissions(l,h,k){let R=new Date,f=k.filter((t)=>t.targetType==="user"&&t.targetId===l),x=h?k.filter((t)=>t.targetType==="organization"&&t.targetId===h):[],F=[...f,...x].filter((t)=>!t.expiresAt||t.expiresAt>R),K=new Set,E=[];for(let t of F){E.push(t.role);for(let j of t.role.permissions)K.add(j)}return{permissions:K,roles:E}}async hasAnyPermission(l,h,k,R){let{permissions:f}=await this.getPermissions(l,h,R);return k.some((x)=>f.has(x))}async hasAllPermissions(l,h,k,R){let{permissions:f}=await this.getPermissions(l,h,R);return k.every((x)=>f.has(x))}}function z(){return new S}export{z as createRBACEngine,q as StandardRole,S as RBACPolicyEngine,y as Permission};
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.7.25",
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.0.0",
32
+ "@contractspec/lib.contracts-spec": "6.2.0",
32
33
  "zod": "^4.3.5"
33
34
  },
34
35
  "devDependencies": {