@zap-studio/permit 0.3.2 → 0.3.4
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/CHANGELOG.md +16 -4
- package/LICENSE +1 -1
- package/README.md +18 -6
- package/dist/conditions.d.ts +177 -0
- package/dist/conditions.d.ts.map +1 -0
- package/dist/conditions.js +180 -0
- package/dist/conditions.js.map +1 -0
- package/dist/errors.d.ts +21 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/{errors.mjs → errors.js} +11 -1
- package/dist/errors.js.map +1 -0
- package/dist/helpers.d.ts +32 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/{helpers.mjs → helpers.js} +8 -3
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/policy.d.ts +89 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +153 -0
- package/dist/policy.js.map +1 -0
- package/dist/types.d.ts +142 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +0 -0
- package/package.json +14 -16
- package/dist/errors.d.mts +0 -11
- package/dist/errors.d.mts.map +0 -1
- package/dist/errors.mjs.map +0 -1
- package/dist/helpers.d.mts +0 -27
- package/dist/helpers.d.mts.map +0 -1
- package/dist/helpers.mjs.map +0 -1
- package/dist/index.d.mts +0 -256
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs +0 -319
- package/dist/index.mjs.map +0 -1
- package/dist/types.d.mts +0 -143
- package/dist/types.d.mts.map +0 -1
- package/dist/types.mjs +0 -1
package/dist/index.d.mts
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import { Actions, ConditionFn, Context, PermitConfig, Policy, PolicyFn, Resources, Role, RoleHierarchy } from "./types.mjs";
|
|
2
|
-
|
|
3
|
-
//#region src/index.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Returns a policy function that always allows the action.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```ts
|
|
9
|
-
* const policy = createPolicy({
|
|
10
|
-
* resources,
|
|
11
|
-
* actions,
|
|
12
|
-
* rules: {
|
|
13
|
-
* post: {
|
|
14
|
-
* read: allow(), // Always allow reading posts
|
|
15
|
-
* },
|
|
16
|
-
* },
|
|
17
|
-
* });
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
declare function allow<TContext extends Context, TAction extends string = string, TResource = unknown>(): PolicyFn<TContext, TAction, TResource>;
|
|
21
|
-
/**
|
|
22
|
-
* Returns a policy function that always denies the action.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```ts
|
|
26
|
-
* const policy = createPolicy({
|
|
27
|
-
* resources,
|
|
28
|
-
* actions,
|
|
29
|
-
* rules: {
|
|
30
|
-
* post: {
|
|
31
|
-
* delete: deny(), // Never allow deleting posts
|
|
32
|
-
* },
|
|
33
|
-
* },
|
|
34
|
-
* });
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
declare function deny<TContext extends Context, TAction extends string = string, TResource = unknown>(): PolicyFn<TContext, TAction, TResource>;
|
|
38
|
-
/**
|
|
39
|
-
* Returns a policy function that allows or denies based on a condition.
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* ```ts
|
|
43
|
-
* const policy = createPolicy({
|
|
44
|
-
* resources,
|
|
45
|
-
* actions,
|
|
46
|
-
* rules: {
|
|
47
|
-
* post: {
|
|
48
|
-
* write: when((ctx, action, resource) => ctx.user.id === resource.authorId),
|
|
49
|
-
* },
|
|
50
|
-
* },
|
|
51
|
-
* });
|
|
52
|
-
* ```
|
|
53
|
-
*/
|
|
54
|
-
declare function when<TContext extends Context, TAction extends string = string, TResource = unknown>(condition: ConditionFn<TContext, TAction, TResource>): PolicyFn<TContext, TAction, TResource>;
|
|
55
|
-
/**
|
|
56
|
-
* Returns a condition function that returns `true` if all conditions are met.
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* ```ts
|
|
60
|
-
* const isOwnerAndPublished = and(
|
|
61
|
-
* (ctx, action, resource) => ctx.user.id === resource.authorId,
|
|
62
|
-
* (ctx, action, resource) => resource.status === "published"
|
|
63
|
-
* );
|
|
64
|
-
*
|
|
65
|
-
* rules: {
|
|
66
|
-
* post: {
|
|
67
|
-
* delete: when(isOwnerAndPublished),
|
|
68
|
-
* },
|
|
69
|
-
* }
|
|
70
|
-
* ```
|
|
71
|
-
*/
|
|
72
|
-
declare function and<TContext extends Context, TAction extends string = string, TResource = unknown>(...conditions: ConditionFn<TContext, TAction, TResource>[]): ConditionFn<TContext, TAction, TResource>;
|
|
73
|
-
/**
|
|
74
|
-
* Returns a condition function that returns `true` if any condition is met.
|
|
75
|
-
*
|
|
76
|
-
* @example
|
|
77
|
-
* ```ts
|
|
78
|
-
* const isOwnerOrAdmin = or(
|
|
79
|
-
* (ctx, action, resource) => ctx.user.id === resource.authorId,
|
|
80
|
-
* (ctx, action, resource) => ctx.user.role === "admin"
|
|
81
|
-
* );
|
|
82
|
-
*
|
|
83
|
-
* rules: {
|
|
84
|
-
* post: {
|
|
85
|
-
* write: when(isOwnerOrAdmin),
|
|
86
|
-
* },
|
|
87
|
-
* }
|
|
88
|
-
* ```
|
|
89
|
-
*/
|
|
90
|
-
declare function or<TContext extends Context, TAction extends string = string, TResource = unknown>(...conditions: ConditionFn<TContext, TAction, TResource>[]): ConditionFn<TContext, TAction, TResource>;
|
|
91
|
-
/**
|
|
92
|
-
* Returns a condition function that negates another condition.
|
|
93
|
-
*
|
|
94
|
-
* @example
|
|
95
|
-
* ```ts
|
|
96
|
-
* const isNotOwner = not((ctx, action, resource) => ctx.user.id === resource.authorId);
|
|
97
|
-
*
|
|
98
|
-
* rules: {
|
|
99
|
-
* post: {
|
|
100
|
-
* like: when(isNotOwner), // Can only like posts you don't own
|
|
101
|
-
* },
|
|
102
|
-
* }
|
|
103
|
-
* ```
|
|
104
|
-
*/
|
|
105
|
-
declare function not<TContext extends Context, TAction extends string = string, TResource = unknown>(condition: ConditionFn<TContext, TAction, TResource>): ConditionFn<TContext, TAction, TResource>;
|
|
106
|
-
/**
|
|
107
|
-
* Returns a condition function that checks if a context property equals a value.
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
* ```ts
|
|
111
|
-
* rules: {
|
|
112
|
-
* post: {
|
|
113
|
-
* write: when(has("role", "admin")), // Only admins can write
|
|
114
|
-
* },
|
|
115
|
-
* }
|
|
116
|
-
* ```
|
|
117
|
-
*/
|
|
118
|
-
declare function has<TContext extends Context, K extends keyof TContext>(key: K, value: TContext[K]): ConditionFn<TContext>;
|
|
119
|
-
/**
|
|
120
|
-
* Collects all roles including inherited ones from a role hierarchy.
|
|
121
|
-
*
|
|
122
|
-
* @example
|
|
123
|
-
* ```ts
|
|
124
|
-
* type Role = "guest" | "user" | "admin";
|
|
125
|
-
*
|
|
126
|
-
* const hierarchy: RoleHierarchy<Role> = {
|
|
127
|
-
* guest: [],
|
|
128
|
-
* user: ["guest"],
|
|
129
|
-
* admin: ["user"],
|
|
130
|
-
* };
|
|
131
|
-
*
|
|
132
|
-
* collectInheritedRoles(["admin"], hierarchy);
|
|
133
|
-
* // Returns: Set { "admin", "user", "guest" }
|
|
134
|
-
* ```
|
|
135
|
-
*/
|
|
136
|
-
declare function collectInheritedRoles<TRole extends Role = Role>(roles: TRole[], hierarchy: RoleHierarchy<TRole>): Set<TRole>;
|
|
137
|
-
/**
|
|
138
|
-
* Returns a condition function that checks if the user has a specific role.
|
|
139
|
-
* Supports role hierarchy for inherited permissions.
|
|
140
|
-
*
|
|
141
|
-
* @example
|
|
142
|
-
* ```ts
|
|
143
|
-
* // Without hierarchy
|
|
144
|
-
* rules: {
|
|
145
|
-
* post: {
|
|
146
|
-
* delete: when(hasRole("admin")),
|
|
147
|
-
* },
|
|
148
|
-
* }
|
|
149
|
-
*
|
|
150
|
-
* // With hierarchy
|
|
151
|
-
* const hierarchy = {
|
|
152
|
-
* guest: [],
|
|
153
|
-
* user: ["guest"],
|
|
154
|
-
* admin: ["user"],
|
|
155
|
-
* };
|
|
156
|
-
*
|
|
157
|
-
* rules: {
|
|
158
|
-
* post: {
|
|
159
|
-
* read: when(hasRole("guest", hierarchy)), // Admins and users can also read
|
|
160
|
-
* },
|
|
161
|
-
* }
|
|
162
|
-
* ```
|
|
163
|
-
*/
|
|
164
|
-
declare function hasRole<TContext extends {
|
|
165
|
-
role: Role | Role[];
|
|
166
|
-
}, TAction extends string = string, TResource = unknown>(role: Role): ConditionFn<TContext, TAction, TResource>;
|
|
167
|
-
declare function hasRole<TContext extends {
|
|
168
|
-
role: TRole | TRole[];
|
|
169
|
-
}, TAction extends string = string, TResource = unknown, TRole extends Role = Role>(role: TRole, hierarchy: RoleHierarchy<TRole>): ConditionFn<TContext, TAction, TResource>;
|
|
170
|
-
/**
|
|
171
|
-
* Creates a type-safe policy from resource schemas, actions, and rules.
|
|
172
|
-
*
|
|
173
|
-
* @example
|
|
174
|
-
* ```ts
|
|
175
|
-
* import { z } from "zod";
|
|
176
|
-
* import { createPolicy, allow, deny, when } from "@zap-studio/permit";
|
|
177
|
-
* import type { Resources, Actions } from "@zap-studio/permit/types";
|
|
178
|
-
*
|
|
179
|
-
* // Define resource schemas
|
|
180
|
-
* const resources = {
|
|
181
|
-
* post: z.object({
|
|
182
|
-
* id: z.string(),
|
|
183
|
-
* authorId: z.string(),
|
|
184
|
-
* visibility: z.enum(["public", "private"]),
|
|
185
|
-
* }),
|
|
186
|
-
* comment: z.object({
|
|
187
|
-
* id: z.string(),
|
|
188
|
-
* postId: z.string(),
|
|
189
|
-
* authorId: z.string(),
|
|
190
|
-
* }),
|
|
191
|
-
* } satisfies Resources;
|
|
192
|
-
*
|
|
193
|
-
* // Define actions per resource
|
|
194
|
-
* const actions = {
|
|
195
|
-
* post: ["read", "write", "delete"],
|
|
196
|
-
* comment: ["read", "write"],
|
|
197
|
-
* } as const satisfies Actions<typeof resources>;
|
|
198
|
-
*
|
|
199
|
-
* // Define context type
|
|
200
|
-
* type AppContext = { user: { id: string; role: string } };
|
|
201
|
-
*
|
|
202
|
-
* // Create the policy
|
|
203
|
-
* const policy = createPolicy<AppContext>({
|
|
204
|
-
* resources,
|
|
205
|
-
* actions,
|
|
206
|
-
* rules: {
|
|
207
|
-
* post: {
|
|
208
|
-
* read: when((ctx, action, resource) => resource.visibility === "public"),
|
|
209
|
-
* write: when((ctx, action, resource) => ctx.user.id === resource.authorId),
|
|
210
|
-
* delete: deny(),
|
|
211
|
-
* },
|
|
212
|
-
* comment: {
|
|
213
|
-
* read: allow(),
|
|
214
|
-
* write: when((ctx, action, resource) => ctx.user.id === resource.authorId),
|
|
215
|
-
* },
|
|
216
|
-
* },
|
|
217
|
-
* });
|
|
218
|
-
*
|
|
219
|
-
* // Check permissions
|
|
220
|
-
* const post = { id: "1", authorId: "user-1", visibility: "public" as const };
|
|
221
|
-
* await policy.can(ctx, "post:read", post); // true
|
|
222
|
-
* await policy.can(ctx, "post:write", post); // depends on ctx.user.id
|
|
223
|
-
* ```
|
|
224
|
-
*/
|
|
225
|
-
declare function createPolicy<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>>(config: PermitConfig<TContext, TResources, TActions>): Policy<TContext, TResources, TActions>;
|
|
226
|
-
/**
|
|
227
|
-
* Merges multiple policies into one using "deny-overrides" strategy.
|
|
228
|
-
* If any policy denies, the merged policy denies. All must allow for the result to allow.
|
|
229
|
-
*
|
|
230
|
-
* @example
|
|
231
|
-
* ```ts
|
|
232
|
-
* const basePolicy = createPolicy({ ... });
|
|
233
|
-
* const adminPolicy = createPolicy({ ... });
|
|
234
|
-
*
|
|
235
|
-
* const merged = mergePolicies(basePolicy, adminPolicy);
|
|
236
|
-
* // Both policies must allow for the action to be permitted
|
|
237
|
-
* ```
|
|
238
|
-
*/
|
|
239
|
-
declare function mergePolicies<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>>(...policies: Policy<TContext, TResources, TActions>[]): Policy<TContext, TResources, TActions>;
|
|
240
|
-
/**
|
|
241
|
-
* Merges multiple policies into one using "allow-overrides" strategy.
|
|
242
|
-
* If any policy allows, the merged policy allows. All must deny for the result to deny.
|
|
243
|
-
*
|
|
244
|
-
* @example
|
|
245
|
-
* ```ts
|
|
246
|
-
* const guestPolicy = createPolicy({ ... });
|
|
247
|
-
* const memberPolicy = createPolicy({ ... });
|
|
248
|
-
*
|
|
249
|
-
* const merged = mergePoliciesAny(guestPolicy, memberPolicy);
|
|
250
|
-
* // If either policy allows, the action is permitted
|
|
251
|
-
* ```
|
|
252
|
-
*/
|
|
253
|
-
declare function mergePoliciesAny<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>>(...policies: Policy<TContext, TResources, TActions>[]): Policy<TContext, TResources, TActions>;
|
|
254
|
-
//#endregion
|
|
255
|
-
export { allow, and, collectInheritedRoles, createPolicy, deny, has, hasRole, mergePolicies, mergePoliciesAny, not, or, when };
|
|
256
|
-
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":"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"}
|
package/dist/index.mjs
DELETED
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
import { PolicyError } from "./errors.mjs";
|
|
2
|
-
import { createStandardValidator } from "@zap-studio/validation";
|
|
3
|
-
//#region src/index.ts
|
|
4
|
-
/**
|
|
5
|
-
* Returns a policy function that always allows the action.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```ts
|
|
9
|
-
* const policy = createPolicy({
|
|
10
|
-
* resources,
|
|
11
|
-
* actions,
|
|
12
|
-
* rules: {
|
|
13
|
-
* post: {
|
|
14
|
-
* read: allow(), // Always allow reading posts
|
|
15
|
-
* },
|
|
16
|
-
* },
|
|
17
|
-
* });
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
function allow() {
|
|
21
|
-
return () => "allow";
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Returns a policy function that always denies the action.
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```ts
|
|
28
|
-
* const policy = createPolicy({
|
|
29
|
-
* resources,
|
|
30
|
-
* actions,
|
|
31
|
-
* rules: {
|
|
32
|
-
* post: {
|
|
33
|
-
* delete: deny(), // Never allow deleting posts
|
|
34
|
-
* },
|
|
35
|
-
* },
|
|
36
|
-
* });
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
function deny() {
|
|
40
|
-
return () => "deny";
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Returns a policy function that allows or denies based on a condition.
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```ts
|
|
47
|
-
* const policy = createPolicy({
|
|
48
|
-
* resources,
|
|
49
|
-
* actions,
|
|
50
|
-
* rules: {
|
|
51
|
-
* post: {
|
|
52
|
-
* write: when((ctx, action, resource) => ctx.user.id === resource.authorId),
|
|
53
|
-
* },
|
|
54
|
-
* },
|
|
55
|
-
* });
|
|
56
|
-
* ```
|
|
57
|
-
*/
|
|
58
|
-
function when(condition) {
|
|
59
|
-
return (context, action, resource) => condition(context, action, resource) ? "allow" : "deny";
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Returns a condition function that returns `true` if all conditions are met.
|
|
63
|
-
*
|
|
64
|
-
* @example
|
|
65
|
-
* ```ts
|
|
66
|
-
* const isOwnerAndPublished = and(
|
|
67
|
-
* (ctx, action, resource) => ctx.user.id === resource.authorId,
|
|
68
|
-
* (ctx, action, resource) => resource.status === "published"
|
|
69
|
-
* );
|
|
70
|
-
*
|
|
71
|
-
* rules: {
|
|
72
|
-
* post: {
|
|
73
|
-
* delete: when(isOwnerAndPublished),
|
|
74
|
-
* },
|
|
75
|
-
* }
|
|
76
|
-
* ```
|
|
77
|
-
*/
|
|
78
|
-
function and(...conditions) {
|
|
79
|
-
return (context, action, resource) => conditions.every((condition) => condition(context, action, resource));
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Returns a condition function that returns `true` if any condition is met.
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* ```ts
|
|
86
|
-
* const isOwnerOrAdmin = or(
|
|
87
|
-
* (ctx, action, resource) => ctx.user.id === resource.authorId,
|
|
88
|
-
* (ctx, action, resource) => ctx.user.role === "admin"
|
|
89
|
-
* );
|
|
90
|
-
*
|
|
91
|
-
* rules: {
|
|
92
|
-
* post: {
|
|
93
|
-
* write: when(isOwnerOrAdmin),
|
|
94
|
-
* },
|
|
95
|
-
* }
|
|
96
|
-
* ```
|
|
97
|
-
*/
|
|
98
|
-
function or(...conditions) {
|
|
99
|
-
return (context, action, resource) => conditions.some((condition) => condition(context, action, resource));
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Returns a condition function that negates another condition.
|
|
103
|
-
*
|
|
104
|
-
* @example
|
|
105
|
-
* ```ts
|
|
106
|
-
* const isNotOwner = not((ctx, action, resource) => ctx.user.id === resource.authorId);
|
|
107
|
-
*
|
|
108
|
-
* rules: {
|
|
109
|
-
* post: {
|
|
110
|
-
* like: when(isNotOwner), // Can only like posts you don't own
|
|
111
|
-
* },
|
|
112
|
-
* }
|
|
113
|
-
* ```
|
|
114
|
-
*/
|
|
115
|
-
function not(condition) {
|
|
116
|
-
return (context, action, resource) => !condition(context, action, resource);
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Returns a condition function that checks if a context property equals a value.
|
|
120
|
-
*
|
|
121
|
-
* @example
|
|
122
|
-
* ```ts
|
|
123
|
-
* rules: {
|
|
124
|
-
* post: {
|
|
125
|
-
* write: when(has("role", "admin")), // Only admins can write
|
|
126
|
-
* },
|
|
127
|
-
* }
|
|
128
|
-
* ```
|
|
129
|
-
*/
|
|
130
|
-
function has(key, value) {
|
|
131
|
-
return (context) => context[key] === value;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Collects all roles including inherited ones from a role hierarchy.
|
|
135
|
-
*
|
|
136
|
-
* @example
|
|
137
|
-
* ```ts
|
|
138
|
-
* type Role = "guest" | "user" | "admin";
|
|
139
|
-
*
|
|
140
|
-
* const hierarchy: RoleHierarchy<Role> = {
|
|
141
|
-
* guest: [],
|
|
142
|
-
* user: ["guest"],
|
|
143
|
-
* admin: ["user"],
|
|
144
|
-
* };
|
|
145
|
-
*
|
|
146
|
-
* collectInheritedRoles(["admin"], hierarchy);
|
|
147
|
-
* // Returns: Set { "admin", "user", "guest" }
|
|
148
|
-
* ```
|
|
149
|
-
*/
|
|
150
|
-
function collectInheritedRoles(roles, hierarchy) {
|
|
151
|
-
const inherited = /* @__PURE__ */ new Set();
|
|
152
|
-
function add(role) {
|
|
153
|
-
if (!inherited.has(role)) {
|
|
154
|
-
inherited.add(role);
|
|
155
|
-
(hierarchy[role] ?? []).forEach(add);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
roles.forEach(add);
|
|
159
|
-
return inherited;
|
|
160
|
-
}
|
|
161
|
-
function hasRole(role, hierarchy) {
|
|
162
|
-
return (context) => {
|
|
163
|
-
const userRoles = Array.isArray(context.role) ? context.role : [context.role];
|
|
164
|
-
if (!hierarchy) return userRoles.includes(role);
|
|
165
|
-
return collectInheritedRoles(userRoles, hierarchy).has(role);
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Creates a type-safe policy from resource schemas, actions, and rules.
|
|
170
|
-
*
|
|
171
|
-
* @example
|
|
172
|
-
* ```ts
|
|
173
|
-
* import { z } from "zod";
|
|
174
|
-
* import { createPolicy, allow, deny, when } from "@zap-studio/permit";
|
|
175
|
-
* import type { Resources, Actions } from "@zap-studio/permit/types";
|
|
176
|
-
*
|
|
177
|
-
* // Define resource schemas
|
|
178
|
-
* const resources = {
|
|
179
|
-
* post: z.object({
|
|
180
|
-
* id: z.string(),
|
|
181
|
-
* authorId: z.string(),
|
|
182
|
-
* visibility: z.enum(["public", "private"]),
|
|
183
|
-
* }),
|
|
184
|
-
* comment: z.object({
|
|
185
|
-
* id: z.string(),
|
|
186
|
-
* postId: z.string(),
|
|
187
|
-
* authorId: z.string(),
|
|
188
|
-
* }),
|
|
189
|
-
* } satisfies Resources;
|
|
190
|
-
*
|
|
191
|
-
* // Define actions per resource
|
|
192
|
-
* const actions = {
|
|
193
|
-
* post: ["read", "write", "delete"],
|
|
194
|
-
* comment: ["read", "write"],
|
|
195
|
-
* } as const satisfies Actions<typeof resources>;
|
|
196
|
-
*
|
|
197
|
-
* // Define context type
|
|
198
|
-
* type AppContext = { user: { id: string; role: string } };
|
|
199
|
-
*
|
|
200
|
-
* // Create the policy
|
|
201
|
-
* const policy = createPolicy<AppContext>({
|
|
202
|
-
* resources,
|
|
203
|
-
* actions,
|
|
204
|
-
* rules: {
|
|
205
|
-
* post: {
|
|
206
|
-
* read: when((ctx, action, resource) => resource.visibility === "public"),
|
|
207
|
-
* write: when((ctx, action, resource) => ctx.user.id === resource.authorId),
|
|
208
|
-
* delete: deny(),
|
|
209
|
-
* },
|
|
210
|
-
* comment: {
|
|
211
|
-
* read: allow(),
|
|
212
|
-
* write: when((ctx, action, resource) => ctx.user.id === resource.authorId),
|
|
213
|
-
* },
|
|
214
|
-
* },
|
|
215
|
-
* });
|
|
216
|
-
*
|
|
217
|
-
* // Check permissions
|
|
218
|
-
* const post = { id: "1", authorId: "user-1", visibility: "public" as const };
|
|
219
|
-
* await policy.can(ctx, "post:read", post); // true
|
|
220
|
-
* await policy.can(ctx, "post:write", post); // depends on ctx.user.id
|
|
221
|
-
* ```
|
|
222
|
-
*/
|
|
223
|
-
function createPolicy(config) {
|
|
224
|
-
const { rules, resources, actions } = config;
|
|
225
|
-
const validators = /* @__PURE__ */ new Map();
|
|
226
|
-
const getValidatedResource = async (resourceType, resource) => {
|
|
227
|
-
const validator = validators.get(resourceType);
|
|
228
|
-
if (!validator) return null;
|
|
229
|
-
try {
|
|
230
|
-
const result = await validator(resource);
|
|
231
|
-
if (result.issues) return null;
|
|
232
|
-
return result.value;
|
|
233
|
-
} catch (error) {
|
|
234
|
-
console.warn(`Resource validation failed for ${String(resourceType)}: ${String(error)}`);
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
const parsePermission = (permission) => {
|
|
239
|
-
const [resourceTypeValue, actionValue, ...rest] = permission.split(":");
|
|
240
|
-
if (!resourceTypeValue || !actionValue || rest.length > 0) return null;
|
|
241
|
-
return {
|
|
242
|
-
action: actionValue,
|
|
243
|
-
resourceType: resourceTypeValue
|
|
244
|
-
};
|
|
245
|
-
};
|
|
246
|
-
const hasAllowedAction = (resourceType, action) => actions[resourceType]?.includes(action) ?? false;
|
|
247
|
-
const evaluatePolicy = (context, resourceType, action, resource) => {
|
|
248
|
-
const policyFn = rules[resourceType]?.[action];
|
|
249
|
-
if (!policyFn) return false;
|
|
250
|
-
try {
|
|
251
|
-
return policyFn(context, action, resource) === "allow";
|
|
252
|
-
} catch (error) {
|
|
253
|
-
console.warn(`Policy evaluation error for ${String(resourceType)}.${String(action)}: ${String(error)}`);
|
|
254
|
-
return false;
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
for (const key of Object.keys(resources)) {
|
|
258
|
-
const schema = resources[key];
|
|
259
|
-
if (!schema) throw new PolicyError(`Missing schema for resource: ${String(key)}`);
|
|
260
|
-
const validator = createStandardValidator(schema);
|
|
261
|
-
validators.set(key, async (input) => validator(input));
|
|
262
|
-
}
|
|
263
|
-
return { async can(context, permission, resource) {
|
|
264
|
-
const parsedPermission = parsePermission(permission);
|
|
265
|
-
if (!parsedPermission) return false;
|
|
266
|
-
const { action, resourceType } = parsedPermission;
|
|
267
|
-
if (!hasAllowedAction(resourceType, action)) return false;
|
|
268
|
-
const validatedResource = await getValidatedResource(resourceType, resource);
|
|
269
|
-
if (!validatedResource) return false;
|
|
270
|
-
return evaluatePolicy(context, resourceType, action, validatedResource);
|
|
271
|
-
} };
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Merges multiple policies into one using "deny-overrides" strategy.
|
|
275
|
-
* If any policy denies, the merged policy denies. All must allow for the result to allow.
|
|
276
|
-
*
|
|
277
|
-
* @example
|
|
278
|
-
* ```ts
|
|
279
|
-
* const basePolicy = createPolicy({ ... });
|
|
280
|
-
* const adminPolicy = createPolicy({ ... });
|
|
281
|
-
*
|
|
282
|
-
* const merged = mergePolicies(basePolicy, adminPolicy);
|
|
283
|
-
* // Both policies must allow for the action to be permitted
|
|
284
|
-
* ```
|
|
285
|
-
*/
|
|
286
|
-
function mergePolicies(...policies) {
|
|
287
|
-
return mergePoliciesWithStrategy(policies, "deny-overrides");
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Merges multiple policies into one using "allow-overrides" strategy.
|
|
291
|
-
* If any policy allows, the merged policy allows. All must deny for the result to deny.
|
|
292
|
-
*
|
|
293
|
-
* @example
|
|
294
|
-
* ```ts
|
|
295
|
-
* const guestPolicy = createPolicy({ ... });
|
|
296
|
-
* const memberPolicy = createPolicy({ ... });
|
|
297
|
-
*
|
|
298
|
-
* const merged = mergePoliciesAny(guestPolicy, memberPolicy);
|
|
299
|
-
* // If either policy allows, the action is permitted
|
|
300
|
-
* ```
|
|
301
|
-
*/
|
|
302
|
-
function mergePoliciesAny(...policies) {
|
|
303
|
-
return mergePoliciesWithStrategy(policies, "allow-overrides");
|
|
304
|
-
}
|
|
305
|
-
function mergePoliciesWithStrategy(policies, strategy) {
|
|
306
|
-
return { async can(context, permission, resource) {
|
|
307
|
-
if (!policies.length) return false;
|
|
308
|
-
for (const policy of policies) {
|
|
309
|
-
const allowed = await policy.can(context, permission, resource);
|
|
310
|
-
if (strategy === "allow-overrides" && allowed) return true;
|
|
311
|
-
if (strategy === "deny-overrides" && !allowed) return false;
|
|
312
|
-
}
|
|
313
|
-
return strategy === "deny-overrides";
|
|
314
|
-
} };
|
|
315
|
-
}
|
|
316
|
-
//#endregion
|
|
317
|
-
export { allow, and, collectInheritedRoles, createPolicy, deny, has, hasRole, mergePolicies, mergePoliciesAny, not, or, when };
|
|
318
|
-
|
|
319
|
-
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@zap-studio/validation\";\nimport { createStandardValidator } from \"@zap-studio/validation\";\n\nimport { PolicyError } from \"./errors.js\";\nimport type {\n Actions,\n ConditionFn,\n Context,\n InferAction,\n InferResource,\n PermitConfig,\n Policy,\n PolicyFn,\n Resources,\n Role,\n RoleHierarchy,\n} from \"./types.js\";\n\n/**\n * Returns a policy function that always allows the action.\n *\n * @example\n * ```ts\n * const policy = createPolicy({\n * resources,\n * actions,\n * rules: {\n * post: {\n * read: allow(), // Always allow reading posts\n * },\n * },\n * });\n * ```\n */\nexport function allow<\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n>(): PolicyFn<TContext, TAction, TResource> {\n return () => \"allow\";\n}\n\n/**\n * Returns a policy function that always denies the action.\n *\n * @example\n * ```ts\n * const policy = createPolicy({\n * resources,\n * actions,\n * rules: {\n * post: {\n * delete: deny(), // Never allow deleting posts\n * },\n * },\n * });\n * ```\n */\nexport function deny<\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n>(): PolicyFn<TContext, TAction, TResource> {\n return () => \"deny\";\n}\n\n/**\n * Returns a policy function that allows or denies based on a condition.\n *\n * @example\n * ```ts\n * const policy = createPolicy({\n * resources,\n * actions,\n * rules: {\n * post: {\n * write: when((ctx, action, resource) => ctx.user.id === resource.authorId),\n * },\n * },\n * });\n * ```\n */\nexport function when<\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n>(condition: ConditionFn<TContext, TAction, TResource>): PolicyFn<TContext, TAction, TResource> {\n return (context, action, resource) => (condition(context, action, resource) ? \"allow\" : \"deny\");\n}\n\n/**\n * Returns a condition function that returns `true` if all conditions are met.\n *\n * @example\n * ```ts\n * const isOwnerAndPublished = and(\n * (ctx, action, resource) => ctx.user.id === resource.authorId,\n * (ctx, action, resource) => resource.status === \"published\"\n * );\n *\n * rules: {\n * post: {\n * delete: when(isOwnerAndPublished),\n * },\n * }\n * ```\n */\nexport function and<TContext extends Context, TAction extends string = string, TResource = unknown>(\n ...conditions: ConditionFn<TContext, TAction, TResource>[]\n): ConditionFn<TContext, TAction, TResource> {\n return (context, action, resource) =>\n conditions.every((condition) => condition(context, action, resource));\n}\n\n/**\n * Returns a condition function that returns `true` if any condition is met.\n *\n * @example\n * ```ts\n * const isOwnerOrAdmin = or(\n * (ctx, action, resource) => ctx.user.id === resource.authorId,\n * (ctx, action, resource) => ctx.user.role === \"admin\"\n * );\n *\n * rules: {\n * post: {\n * write: when(isOwnerOrAdmin),\n * },\n * }\n * ```\n */\nexport function or<TContext extends Context, TAction extends string = string, TResource = unknown>(\n ...conditions: ConditionFn<TContext, TAction, TResource>[]\n): ConditionFn<TContext, TAction, TResource> {\n return (context, action, resource) =>\n conditions.some((condition) => condition(context, action, resource));\n}\n\n/**\n * Returns a condition function that negates another condition.\n *\n * @example\n * ```ts\n * const isNotOwner = not((ctx, action, resource) => ctx.user.id === resource.authorId);\n *\n * rules: {\n * post: {\n * like: when(isNotOwner), // Can only like posts you don't own\n * },\n * }\n * ```\n */\nexport function not<TContext extends Context, TAction extends string = string, TResource = unknown>(\n condition: ConditionFn<TContext, TAction, TResource>,\n): ConditionFn<TContext, TAction, TResource> {\n return (context, action, resource) => !condition(context, action, resource);\n}\n\n/**\n * Returns a condition function that checks if a context property equals a value.\n *\n * @example\n * ```ts\n * rules: {\n * post: {\n * write: when(has(\"role\", \"admin\")), // Only admins can write\n * },\n * }\n * ```\n */\nexport function has<TContext extends Context, K extends keyof TContext>(\n key: K,\n value: TContext[K],\n): ConditionFn<TContext> {\n return (context) => context[key] === value;\n}\n\n/**\n * Collects all roles including inherited ones from a role hierarchy.\n *\n * @example\n * ```ts\n * type Role = \"guest\" | \"user\" | \"admin\";\n *\n * const hierarchy: RoleHierarchy<Role> = {\n * guest: [],\n * user: [\"guest\"],\n * admin: [\"user\"],\n * };\n *\n * collectInheritedRoles([\"admin\"], hierarchy);\n * // Returns: Set { \"admin\", \"user\", \"guest\" }\n * ```\n */\nexport function collectInheritedRoles<TRole extends Role = Role>(\n roles: TRole[],\n hierarchy: RoleHierarchy<TRole>,\n): Set<TRole> {\n const inherited = new Set<TRole>();\n\n function add(role: TRole) {\n if (!inherited.has(role)) {\n inherited.add(role);\n const parents = hierarchy[role] ?? [];\n parents.forEach(add); // recursively add parent roles\n }\n }\n\n roles.forEach(add);\n return inherited;\n}\n\n/**\n * Returns a condition function that checks if the user has a specific role.\n * Supports role hierarchy for inherited permissions.\n *\n * @example\n * ```ts\n * // Without hierarchy\n * rules: {\n * post: {\n * delete: when(hasRole(\"admin\")),\n * },\n * }\n *\n * // With hierarchy\n * const hierarchy = {\n * guest: [],\n * user: [\"guest\"],\n * admin: [\"user\"],\n * };\n *\n * rules: {\n * post: {\n * read: when(hasRole(\"guest\", hierarchy)), // Admins and users can also read\n * },\n * }\n * ```\n */\nexport function hasRole<\n TContext extends { role: Role | Role[] },\n TAction extends string = string,\n TResource = unknown,\n>(role: Role): ConditionFn<TContext, TAction, TResource>;\n\nexport function hasRole<\n TContext extends { role: TRole | TRole[] },\n TAction extends string = string,\n TResource = unknown,\n TRole extends Role = Role,\n>(role: TRole, hierarchy: RoleHierarchy<TRole>): ConditionFn<TContext, TAction, TResource>;\n\nexport function hasRole<\n TContext extends { role: Role | Role[] },\n TAction extends string = string,\n TResource = unknown,\n>(role: Role, hierarchy?: RoleHierarchy<Role>): ConditionFn<TContext, TAction, TResource> {\n return (context) => {\n const userRoles = Array.isArray(context.role) ? context.role : [context.role];\n\n if (!hierarchy) {\n return userRoles.includes(role);\n }\n\n const inherited = collectInheritedRoles(userRoles, hierarchy);\n return inherited.has(role);\n };\n}\n\n/**\n * Creates a type-safe policy from resource schemas, actions, and rules.\n *\n * @example\n * ```ts\n * import { z } from \"zod\";\n * import { createPolicy, allow, deny, when } from \"@zap-studio/permit\";\n * import type { Resources, Actions } from \"@zap-studio/permit/types\";\n *\n * // Define resource schemas\n * const resources = {\n * post: z.object({\n * id: z.string(),\n * authorId: z.string(),\n * visibility: z.enum([\"public\", \"private\"]),\n * }),\n * comment: z.object({\n * id: z.string(),\n * postId: z.string(),\n * authorId: z.string(),\n * }),\n * } satisfies Resources;\n *\n * // Define actions per resource\n * const actions = {\n * post: [\"read\", \"write\", \"delete\"],\n * comment: [\"read\", \"write\"],\n * } as const satisfies Actions<typeof resources>;\n *\n * // Define context type\n * type AppContext = { user: { id: string; role: string } };\n *\n * // Create the policy\n * const policy = createPolicy<AppContext>({\n * resources,\n * actions,\n * rules: {\n * post: {\n * read: when((ctx, action, resource) => resource.visibility === \"public\"),\n * write: when((ctx, action, resource) => ctx.user.id === resource.authorId),\n * delete: deny(),\n * },\n * comment: {\n * read: allow(),\n * write: when((ctx, action, resource) => ctx.user.id === resource.authorId),\n * },\n * },\n * });\n *\n * // Check permissions\n * const post = { id: \"1\", authorId: \"user-1\", visibility: \"public\" as const };\n * await policy.can(ctx, \"post:read\", post); // true\n * await policy.can(ctx, \"post:write\", post); // depends on ctx.user.id\n * ```\n */\nexport function createPolicy<\n TContext extends Context,\n TResources extends Resources = Resources,\n TActions extends Actions<TResources> = Actions<TResources>,\n>(config: PermitConfig<TContext, TResources, TActions>): Policy<TContext, TResources, TActions> {\n const { rules, resources, actions } = config;\n const validators = new Map<\n keyof TResources,\n (input: unknown) => Promise<StandardSchemaV1.Result<unknown>>\n >();\n\n const getValidatedResource = async <K extends keyof TResources>(\n resourceType: K,\n resource: InferResource<TResources, K>,\n ): Promise<InferResource<TResources, K> | null> => {\n const validator = validators.get(resourceType);\n if (!validator) {\n return null;\n }\n try {\n const result = await validator(resource);\n if (result.issues) {\n return null;\n }\n return result.value as InferResource<TResources, K>;\n } catch (error) {\n console.warn(`Resource validation failed for ${String(resourceType)}: ${String(error)}`);\n return null;\n }\n };\n\n const parsePermission = <K extends keyof TResources & keyof TActions>(\n permission: `${K & string}:${InferAction<TActions, K> & string}`,\n ): { action: InferAction<TActions, K>; resourceType: K } | null => {\n const [resourceTypeValue, actionValue, ...rest] = permission.split(\":\");\n if (!resourceTypeValue || !actionValue || rest.length > 0) {\n return null;\n }\n\n return {\n action: actionValue as InferAction<TActions, K>,\n resourceType: resourceTypeValue as K,\n };\n };\n\n const hasAllowedAction = <K extends keyof TResources & keyof TActions>(\n resourceType: K,\n action: InferAction<TActions, K>,\n ): boolean => actions[resourceType]?.includes(action) ?? false;\n\n const evaluatePolicy = <K extends keyof TResources & keyof TActions>(\n context: TContext,\n resourceType: K,\n action: InferAction<TActions, K>,\n resource: InferResource<TResources, K>,\n ): boolean => {\n const policyFn = rules[resourceType]?.[action];\n if (!policyFn) {\n return false;\n }\n\n try {\n return policyFn(context, action, resource) === \"allow\";\n } catch (error) {\n console.warn(\n `Policy evaluation error for ${String(resourceType)}.${String(action)}: ${String(error)}`,\n );\n return false;\n }\n };\n\n for (const key of Object.keys(resources) as Array<keyof TResources>) {\n const schema = resources[key];\n if (!schema) {\n throw new PolicyError(`Missing schema for resource: ${String(key)}`);\n }\n const validator = createStandardValidator(schema);\n validators.set(key, async (input: unknown) => validator(input));\n }\n\n return {\n async can<K extends keyof TResources & keyof TActions>(\n context: TContext,\n permission: `${K & string}:${InferAction<TActions, K> & string}`,\n resource: InferResource<TResources, K>,\n ): Promise<boolean> {\n const parsedPermission = parsePermission(permission);\n if (!parsedPermission) {\n return false;\n }\n\n const { action, resourceType } = parsedPermission;\n if (!hasAllowedAction(resourceType, action)) {\n return false;\n }\n\n const validatedResource = await getValidatedResource(resourceType, resource);\n if (!validatedResource) {\n return false;\n }\n\n return evaluatePolicy(context, resourceType, action, validatedResource);\n },\n };\n}\n\n/**\n * Merges multiple policies into one using \"deny-overrides\" strategy.\n * If any policy denies, the merged policy denies. All must allow for the result to allow.\n *\n * @example\n * ```ts\n * const basePolicy = createPolicy({ ... });\n * const adminPolicy = createPolicy({ ... });\n *\n * const merged = mergePolicies(basePolicy, adminPolicy);\n * // Both policies must allow for the action to be permitted\n * ```\n */\nexport function mergePolicies<\n TContext extends Context,\n TResources extends Resources = Resources,\n TActions extends Actions<TResources> = Actions<TResources>,\n>(...policies: Policy<TContext, TResources, TActions>[]): Policy<TContext, TResources, TActions> {\n return mergePoliciesWithStrategy(policies, \"deny-overrides\");\n}\n\n/**\n * Merges multiple policies into one using \"allow-overrides\" strategy.\n * If any policy allows, the merged policy allows. All must deny for the result to deny.\n *\n * @example\n * ```ts\n * const guestPolicy = createPolicy({ ... });\n * const memberPolicy = createPolicy({ ... });\n *\n * const merged = mergePoliciesAny(guestPolicy, memberPolicy);\n * // If either policy allows, the action is permitted\n * ```\n */\nexport function mergePoliciesAny<\n TContext extends Context,\n TResources extends Resources = Resources,\n TActions extends Actions<TResources> = Actions<TResources>,\n>(...policies: Policy<TContext, TResources, TActions>[]): Policy<TContext, TResources, TActions> {\n return mergePoliciesWithStrategy(policies, \"allow-overrides\");\n}\n\nfunction mergePoliciesWithStrategy<\n TContext extends Context,\n TResources extends Resources = Resources,\n TActions extends Actions<TResources> = Actions<TResources>,\n>(\n policies: Policy<TContext, TResources, TActions>[],\n strategy: \"allow-overrides\" | \"deny-overrides\",\n): Policy<TContext, TResources, TActions> {\n return {\n async can<K extends keyof TResources & keyof TActions>(\n context: TContext,\n permission: `${K & string}:${InferAction<TActions, K> & string}`,\n resource: InferResource<TResources, K>,\n ): Promise<boolean> {\n if (!policies.length) {\n return false;\n }\n for (const policy of policies) {\n const allowed = await policy.can(context, permission, resource);\n\n if (strategy === \"allow-overrides\" && allowed) {\n return true;\n }\n if (strategy === \"deny-overrides\" && !allowed) {\n return false;\n }\n }\n return strategy === \"deny-overrides\";\n },\n };\n}\n"],"mappings":"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"}
|