@zap-studio/permit 0.3.2 → 0.3.3
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 +8 -4
- package/README.md +5 -3
- package/dist/errors.d.mts +13 -3
- package/dist/errors.d.mts.map +1 -1
- package/dist/errors.mjs +10 -0
- package/dist/errors.mjs.map +1 -1
- package/dist/helpers.d.mts +27 -22
- package/dist/helpers.d.mts.map +1 -1
- package/dist/helpers.mjs +7 -2
- package/dist/helpers.mjs.map +1 -1
- package/dist/index.d.mts +244 -239
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +79 -70
- package/dist/index.mjs.map +1 -1
- package/dist/types.d.mts +105 -106
- package/dist/types.d.mts.map +1 -1
- package/package.json +6 -7
package/dist/index.mjs
CHANGED
|
@@ -17,9 +17,7 @@ import { createStandardValidator } from "@zap-studio/validation";
|
|
|
17
17
|
* });
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
|
-
|
|
21
|
-
return () => "allow";
|
|
22
|
-
}
|
|
20
|
+
const allow = () => () => "allow";
|
|
23
21
|
/**
|
|
24
22
|
* Returns a policy function that always denies the action.
|
|
25
23
|
*
|
|
@@ -36,9 +34,7 @@ function allow() {
|
|
|
36
34
|
* });
|
|
37
35
|
* ```
|
|
38
36
|
*/
|
|
39
|
-
|
|
40
|
-
return () => "deny";
|
|
41
|
-
}
|
|
37
|
+
const deny = () => () => "deny";
|
|
42
38
|
/**
|
|
43
39
|
* Returns a policy function that allows or denies based on a condition.
|
|
44
40
|
*
|
|
@@ -55,9 +51,7 @@ function deny() {
|
|
|
55
51
|
* });
|
|
56
52
|
* ```
|
|
57
53
|
*/
|
|
58
|
-
|
|
59
|
-
return (context, action, resource) => condition(context, action, resource) ? "allow" : "deny";
|
|
60
|
-
}
|
|
54
|
+
const when = (condition) => (context, action, resource) => condition(context, action, resource) ? "allow" : "deny";
|
|
61
55
|
/**
|
|
62
56
|
* Returns a condition function that returns `true` if all conditions are met.
|
|
63
57
|
*
|
|
@@ -75,9 +69,7 @@ function when(condition) {
|
|
|
75
69
|
* }
|
|
76
70
|
* ```
|
|
77
71
|
*/
|
|
78
|
-
|
|
79
|
-
return (context, action, resource) => conditions.every((condition) => condition(context, action, resource));
|
|
80
|
-
}
|
|
72
|
+
const and = (...conditions) => (context, action, resource) => conditions.every((condition) => condition(context, action, resource));
|
|
81
73
|
/**
|
|
82
74
|
* Returns a condition function that returns `true` if any condition is met.
|
|
83
75
|
*
|
|
@@ -95,9 +87,7 @@ function and(...conditions) {
|
|
|
95
87
|
* }
|
|
96
88
|
* ```
|
|
97
89
|
*/
|
|
98
|
-
|
|
99
|
-
return (context, action, resource) => conditions.some((condition) => condition(context, action, resource));
|
|
100
|
-
}
|
|
90
|
+
const or = (...conditions) => (context, action, resource) => conditions.some((condition) => condition(context, action, resource));
|
|
101
91
|
/**
|
|
102
92
|
* Returns a condition function that negates another condition.
|
|
103
93
|
*
|
|
@@ -112,9 +102,7 @@ function or(...conditions) {
|
|
|
112
102
|
* }
|
|
113
103
|
* ```
|
|
114
104
|
*/
|
|
115
|
-
|
|
116
|
-
return (context, action, resource) => !condition(context, action, resource);
|
|
117
|
-
}
|
|
105
|
+
const not = (condition) => (context, action, resource) => !condition(context, action, resource);
|
|
118
106
|
/**
|
|
119
107
|
* Returns a condition function that checks if a context property equals a value.
|
|
120
108
|
*
|
|
@@ -127,9 +115,7 @@ function not(condition) {
|
|
|
127
115
|
* }
|
|
128
116
|
* ```
|
|
129
117
|
*/
|
|
130
|
-
|
|
131
|
-
return (context) => context[key] === value;
|
|
132
|
-
}
|
|
118
|
+
const has = (key, value) => (context) => context[key] === value;
|
|
133
119
|
/**
|
|
134
120
|
* Collects all roles including inherited ones from a role hierarchy.
|
|
135
121
|
*
|
|
@@ -147,24 +133,61 @@ function has(key, value) {
|
|
|
147
133
|
* // Returns: Set { "admin", "user", "guest" }
|
|
148
134
|
* ```
|
|
149
135
|
*/
|
|
150
|
-
|
|
136
|
+
const collectInheritedRoles = (roles, hierarchy) => {
|
|
151
137
|
const inherited = /* @__PURE__ */ new Set();
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
roles
|
|
138
|
+
const add = (role) => {
|
|
139
|
+
if (inherited.has(role)) return;
|
|
140
|
+
inherited.add(role);
|
|
141
|
+
const parents = hierarchy[role] ?? [];
|
|
142
|
+
for (const parent of parents) add(parent);
|
|
143
|
+
};
|
|
144
|
+
for (const role of roles) add(role);
|
|
159
145
|
return inherited;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Returns a condition function that checks if the user has a specific role.
|
|
149
|
+
* Supports role hierarchy for inherited permissions.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* // Without hierarchy
|
|
154
|
+
* rules: {
|
|
155
|
+
* post: {
|
|
156
|
+
* delete: when(hasRole("admin")),
|
|
157
|
+
* },
|
|
158
|
+
* }
|
|
159
|
+
*
|
|
160
|
+
* // With hierarchy
|
|
161
|
+
* const hierarchy = {
|
|
162
|
+
* guest: [],
|
|
163
|
+
* user: ["guest"],
|
|
164
|
+
* admin: ["user"],
|
|
165
|
+
* };
|
|
166
|
+
*
|
|
167
|
+
* rules: {
|
|
168
|
+
* post: {
|
|
169
|
+
* read: when(hasRole("guest", hierarchy)), // Admins and users can also read
|
|
170
|
+
* },
|
|
171
|
+
* }
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
const hasRole = (role, hierarchy) => (context) => {
|
|
175
|
+
const userRoles = Array.isArray(context.role) ? context.role : [context.role];
|
|
176
|
+
if (hierarchy === void 0) return userRoles.includes(role);
|
|
177
|
+
return collectInheritedRoles(userRoles, hierarchy).has(role);
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* Splits a typed `resource:action` permission string into its parts.
|
|
181
|
+
* Returns `null` when the string is malformed (missing/empty part or extra segments).
|
|
182
|
+
*/
|
|
183
|
+
const parsePermission = (permission) => {
|
|
184
|
+
const [resourceTypeValue, actionValue, ...rest] = permission.split(":");
|
|
185
|
+
if (resourceTypeValue === void 0 || resourceTypeValue.length === 0 || actionValue === void 0 || actionValue.length === 0 || rest.length > 0) return null;
|
|
186
|
+
return {
|
|
187
|
+
action: actionValue,
|
|
188
|
+
resourceType: resourceTypeValue
|
|
166
189
|
};
|
|
167
|
-
}
|
|
190
|
+
};
|
|
168
191
|
/**
|
|
169
192
|
* Creates a type-safe policy from resource schemas, actions, and rules.
|
|
170
193
|
*
|
|
@@ -220,12 +243,12 @@ function hasRole(role, hierarchy) {
|
|
|
220
243
|
* await policy.can(ctx, "post:write", post); // depends on ctx.user.id
|
|
221
244
|
* ```
|
|
222
245
|
*/
|
|
223
|
-
|
|
246
|
+
const createPolicy = (config) => {
|
|
224
247
|
const { rules, resources, actions } = config;
|
|
225
248
|
const validators = /* @__PURE__ */ new Map();
|
|
226
249
|
const getValidatedResource = async (resourceType, resource) => {
|
|
227
250
|
const validator = validators.get(resourceType);
|
|
228
|
-
if (
|
|
251
|
+
if (validator === void 0) return null;
|
|
229
252
|
try {
|
|
230
253
|
const result = await validator(resource);
|
|
231
254
|
if (result.issues) return null;
|
|
@@ -235,41 +258,42 @@ function createPolicy(config) {
|
|
|
235
258
|
return null;
|
|
236
259
|
}
|
|
237
260
|
};
|
|
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
261
|
const hasAllowedAction = (resourceType, action) => actions[resourceType]?.includes(action) ?? false;
|
|
247
262
|
const evaluatePolicy = (context, resourceType, action, resource) => {
|
|
248
263
|
const policyFn = rules[resourceType]?.[action];
|
|
249
|
-
if (
|
|
264
|
+
if (policyFn === void 0) return false;
|
|
250
265
|
try {
|
|
251
266
|
return policyFn(context, action, resource) === "allow";
|
|
252
267
|
} catch (error) {
|
|
253
|
-
console.warn(`Policy evaluation error for ${String(resourceType)}.${
|
|
268
|
+
console.warn(`Policy evaluation error for ${String(resourceType)}.${action}: ${String(error)}`);
|
|
254
269
|
return false;
|
|
255
270
|
}
|
|
256
271
|
};
|
|
257
272
|
for (const key of Object.keys(resources)) {
|
|
258
273
|
const schema = resources[key];
|
|
259
|
-
if (
|
|
274
|
+
if (schema === void 0) throw new PolicyError(`Missing schema for resource: ${String(key)}`);
|
|
260
275
|
const validator = createStandardValidator(schema);
|
|
261
|
-
validators.set(key, async (input) => validator(input));
|
|
276
|
+
validators.set(key, async (input) => await validator(input));
|
|
262
277
|
}
|
|
263
278
|
return { async can(context, permission, resource) {
|
|
264
279
|
const parsedPermission = parsePermission(permission);
|
|
265
|
-
if (
|
|
280
|
+
if (parsedPermission === null) return false;
|
|
266
281
|
const { action, resourceType } = parsedPermission;
|
|
267
282
|
if (!hasAllowedAction(resourceType, action)) return false;
|
|
268
283
|
const validatedResource = await getValidatedResource(resourceType, resource);
|
|
269
|
-
if (
|
|
284
|
+
if (validatedResource === null) return false;
|
|
270
285
|
return evaluatePolicy(context, resourceType, action, validatedResource);
|
|
271
286
|
} };
|
|
272
|
-
}
|
|
287
|
+
};
|
|
288
|
+
const mergePoliciesWithStrategy = (policies, strategy) => ({ async can(context, permission, resource) {
|
|
289
|
+
if (policies.length === 0) return false;
|
|
290
|
+
for (const policy of policies) {
|
|
291
|
+
const allowed = await policy.can(context, permission, resource);
|
|
292
|
+
if (strategy === "allow-overrides" && allowed) return true;
|
|
293
|
+
if (strategy === "deny-overrides" && !allowed) return false;
|
|
294
|
+
}
|
|
295
|
+
return strategy === "deny-overrides";
|
|
296
|
+
} });
|
|
273
297
|
/**
|
|
274
298
|
* Merges multiple policies into one using "deny-overrides" strategy.
|
|
275
299
|
* If any policy denies, the merged policy denies. All must allow for the result to allow.
|
|
@@ -283,9 +307,7 @@ function createPolicy(config) {
|
|
|
283
307
|
* // Both policies must allow for the action to be permitted
|
|
284
308
|
* ```
|
|
285
309
|
*/
|
|
286
|
-
|
|
287
|
-
return mergePoliciesWithStrategy(policies, "deny-overrides");
|
|
288
|
-
}
|
|
310
|
+
const mergePolicies = (...policies) => mergePoliciesWithStrategy(policies, "deny-overrides");
|
|
289
311
|
/**
|
|
290
312
|
* Merges multiple policies into one using "allow-overrides" strategy.
|
|
291
313
|
* If any policy allows, the merged policy allows. All must deny for the result to deny.
|
|
@@ -299,20 +321,7 @@ function mergePolicies(...policies) {
|
|
|
299
321
|
* // If either policy allows, the action is permitted
|
|
300
322
|
* ```
|
|
301
323
|
*/
|
|
302
|
-
|
|
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
|
-
}
|
|
324
|
+
const mergePoliciesAny = (...policies) => mergePoliciesWithStrategy(policies, "allow-overrides");
|
|
316
325
|
//#endregion
|
|
317
326
|
export { allow, and, collectInheritedRoles, createPolicy, deny, has, hasRole, mergePolicies, mergePoliciesAny, not, or, when };
|
|
318
327
|
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Policy composition and evaluation utilities.\n *\n * @module @zap-studio/permit\n */\n\nimport 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 const allow =\n <\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n >(): PolicyFn<TContext, TAction, TResource> =>\n () =>\n \"allow\";\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 const deny =\n <\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n >(): PolicyFn<TContext, TAction, TResource> =>\n () =>\n \"deny\";\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 const when =\n <\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n >(\n condition: ConditionFn<TContext, TAction, TResource>\n ): PolicyFn<TContext, TAction, TResource> =>\n (context, action, resource) =>\n condition(context, action, resource) ? \"allow\" : \"deny\";\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 const and =\n <\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n >(\n ...conditions: ConditionFn<TContext, TAction, TResource>[]\n ): ConditionFn<TContext, TAction, TResource> =>\n (context, action, resource) =>\n conditions.every((condition) => condition(context, action, resource));\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 const or =\n <\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n >(\n ...conditions: ConditionFn<TContext, TAction, TResource>[]\n ): ConditionFn<TContext, TAction, TResource> =>\n (context, action, resource) =>\n conditions.some((condition) => condition(context, action, resource));\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 const not =\n <\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n >(\n condition: ConditionFn<TContext, TAction, TResource>\n ): ConditionFn<TContext, TAction, TResource> =>\n (context, action, resource) =>\n !condition(context, action, resource);\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 const has =\n <TContext extends Context, K extends keyof TContext>(\n key: K,\n value: TContext[K]\n ): ConditionFn<TContext> =>\n (context) =>\n context[key] === value;\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 const collectInheritedRoles = <TRole extends Role = Role>(\n roles: TRole[],\n hierarchy: RoleHierarchy<TRole>\n): Set<TRole> => {\n const inherited = new Set<TRole>();\n\n const add = (role: TRole): void => {\n if (inherited.has(role)) {\n return;\n }\n\n inherited.add(role);\n const parents = hierarchy[role] ?? [];\n for (const parent of parents) {\n add(parent);\n }\n };\n\n for (const role of roles) {\n add(role);\n }\n return inherited;\n};\n\n/**\n * Call signatures for {@link hasRole}, preserving the with/without hierarchy overloads.\n */\ninterface HasRoleFn {\n <\n TContext extends { role: Role | Role[] },\n TAction extends string = string,\n TResource = unknown,\n >(\n role: Role\n ): ConditionFn<TContext, TAction, TResource>;\n <\n TContext extends { role: TRole | TRole[] },\n TAction extends string = string,\n TResource = unknown,\n TRole extends Role = Role,\n >(\n role: TRole,\n hierarchy: RoleHierarchy<TRole>\n ): ConditionFn<TContext, TAction, TResource>;\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 const hasRole: HasRoleFn =\n (\n role: Role,\n hierarchy?: RoleHierarchy\n ): ConditionFn<{ role: Role | Role[] }> =>\n (context) => {\n const userRoles = Array.isArray(context.role)\n ? context.role\n : [context.role];\n\n if (hierarchy === undefined) {\n return userRoles.includes(role);\n }\n\n const inherited = collectInheritedRoles(userRoles, hierarchy);\n return inherited.has(role);\n };\n\n/**\n * Splits a typed `resource:action` permission string into its parts.\n * Returns `null` when the string is malformed (missing/empty part or extra segments).\n */\nconst parsePermission = <\n TResources extends Resources,\n TActions extends Actions<TResources>,\n K extends keyof TResources & keyof TActions,\n>(\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 (\n resourceTypeValue === undefined ||\n resourceTypeValue.length === 0 ||\n actionValue === undefined ||\n actionValue.length === 0 ||\n rest.length > 0\n ) {\n return null;\n }\n\n return {\n action: actionValue,\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- Parsed permission strings are constrained by the typed permission template.\n resourceType: resourceTypeValue as K,\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 const createPolicy = <\n TContext extends Context,\n TResources extends Resources = Resources,\n TActions extends Actions<TResources> = Actions<TResources>,\n>(\n config: PermitConfig<TContext, TResources, TActions>\n): 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 === undefined) {\n return null;\n }\n try {\n const result = await validator(resource);\n if (result.issues) {\n return null;\n }\n return result.value;\n } catch (error) {\n console.warn(\n `Resource validation failed for ${String(resourceType)}: ${String(error)}`\n );\n return null;\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 === undefined) {\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)}.${action}: ${String(error)}`\n );\n return false;\n }\n };\n\n for (const key of Object.keys(resources) as (keyof TResources)[]) {\n const schema = resources[key];\n if (schema === undefined) {\n throw new PolicyError(`Missing schema for resource: ${String(key)}`);\n }\n const validator = createStandardValidator(schema);\n validators.set(key, async (input: unknown) => await 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<TResources, TActions, K>(\n permission\n );\n if (parsedPermission === null) {\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(\n resourceType,\n resource\n );\n if (validatedResource === null) {\n return false;\n }\n\n return evaluatePolicy(context, resourceType, action, validatedResource);\n },\n };\n};\n\nconst 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 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 === 0) {\n return false;\n }\n for (const policy of policies) {\n // oxlint-disable-next-line no-await-in-loop -- Policies must evaluate sequentially to preserve short-circuit semantics.\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/**\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 const mergePolicies = <\n TContext extends Context,\n TResources extends Resources = Resources,\n TActions extends Actions<TResources> = Actions<TResources>,\n>(\n ...policies: Policy<TContext, TResources, TActions>[]\n): Policy<TContext, TResources, TActions> =>\n mergePoliciesWithStrategy(policies, \"deny-overrides\");\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 const mergePoliciesAny = <\n TContext extends Context,\n TResources extends Resources = Resources,\n TActions extends Actions<TResources> = Actions<TResources>,\n>(\n ...policies: Policy<TContext, TResources, TActions>[]\n): Policy<TContext, TResources, TActions> =>\n mergePoliciesWithStrategy(policies, \"allow-overrides\");\n"],"mappings":"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"}
|
package/dist/types.d.mts
CHANGED
|
@@ -1,142 +1,141 @@
|
|
|
1
1
|
import { StandardSchemaV1 } from "@zap-studio/validation";
|
|
2
|
-
|
|
3
2
|
//#region src/types.d.ts
|
|
4
3
|
/**
|
|
5
|
-
* Represents the possible outcomes of a policy decision.
|
|
6
|
-
* - "allow": The action is permitted.
|
|
7
|
-
* - "deny": The action is not permitted.
|
|
8
|
-
*/
|
|
4
|
+
* Represents the possible outcomes of a policy decision.
|
|
5
|
+
* - "allow": The action is permitted.
|
|
6
|
+
* - "deny": The action is not permitted.
|
|
7
|
+
*/
|
|
9
8
|
type Decision = "allow" | "deny";
|
|
10
9
|
/**
|
|
11
|
-
* Represents the context in which a policy decision is made.
|
|
12
|
-
* Can include user information, environment, or any relevant data.
|
|
13
|
-
*/
|
|
10
|
+
* Represents the context in which a policy decision is made.
|
|
11
|
+
* Can include user information, environment, or any relevant data.
|
|
12
|
+
*/
|
|
14
13
|
type Context<TContext = unknown> = TContext;
|
|
15
14
|
/**
|
|
16
|
-
* Represents a role within the system.
|
|
17
|
-
*/
|
|
15
|
+
* Represents a role within the system.
|
|
16
|
+
*/
|
|
18
17
|
type Role<TRole extends string = string> = TRole;
|
|
19
18
|
/**
|
|
20
|
-
* Represents a role hierarchy within the system.
|
|
21
|
-
* Maps each role to an array of roles it inherits from.
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```ts
|
|
25
|
-
* type Roles = "guest" | "user" | "admin";
|
|
26
|
-
*
|
|
27
|
-
* const hierarchy: RoleHierarchy<Roles> = {
|
|
28
|
-
* guest: [],
|
|
29
|
-
* user: ["guest"],
|
|
30
|
-
* admin: ["user"],
|
|
31
|
-
* };
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
19
|
+
* Represents a role hierarchy within the system.
|
|
20
|
+
* Maps each role to an array of roles it inherits from.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* type Roles = "guest" | "user" | "admin";
|
|
25
|
+
*
|
|
26
|
+
* const hierarchy: RoleHierarchy<Roles> = {
|
|
27
|
+
* guest: [],
|
|
28
|
+
* user: ["guest"],
|
|
29
|
+
* admin: ["user"],
|
|
30
|
+
* };
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
34
33
|
type RoleHierarchy<TRole extends Role = Role> = Record<TRole, TRole[]>;
|
|
35
34
|
/**
|
|
36
|
-
* Type helper for defining resource schemas using Standard Schema.
|
|
37
|
-
* Use with `satisfies` to ensure type safety when defining resources.
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* ```ts
|
|
41
|
-
* import { z } from "zod";
|
|
42
|
-
* import type { Resources } from "@zap-studio/permit/types";
|
|
43
|
-
*
|
|
44
|
-
* const resources = {
|
|
45
|
-
* post: z.object({ id: z.string(), authorId: z.string() }),
|
|
46
|
-
* comment: z.object({ id: z.string(), postId: z.string() }),
|
|
47
|
-
* } satisfies Resources;
|
|
48
|
-
* ```
|
|
49
|
-
*/
|
|
35
|
+
* Type helper for defining resource schemas using Standard Schema.
|
|
36
|
+
* Use with `satisfies` to ensure type safety when defining resources.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { z } from "zod";
|
|
41
|
+
* import type { Resources } from "@zap-studio/permit/types";
|
|
42
|
+
*
|
|
43
|
+
* const resources = {
|
|
44
|
+
* post: z.object({ id: z.string(), authorId: z.string() }),
|
|
45
|
+
* comment: z.object({ id: z.string(), postId: z.string() }),
|
|
46
|
+
* } satisfies Resources;
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
50
49
|
type Resources<TResourceKey extends string = string> = Record<TResourceKey, StandardSchemaV1>;
|
|
51
50
|
/**
|
|
52
|
-
* Type helper for defining actions per resource.
|
|
53
|
-
* Use with `satisfies` to ensure keys match the resource definitions.
|
|
54
|
-
*
|
|
55
|
-
* @example
|
|
56
|
-
* ```ts
|
|
57
|
-
* import type { Actions } from "@zap-studio/permit/types";
|
|
58
|
-
*
|
|
59
|
-
* const actions = {
|
|
60
|
-
* post: ["read", "write", "delete"],
|
|
61
|
-
* comment: ["read", "write"],
|
|
62
|
-
* } as const satisfies Actions<typeof resources>;
|
|
63
|
-
* ```
|
|
64
|
-
*/
|
|
65
|
-
type Actions<TResources extends Resources> = { [K in keyof TResources]: readonly string[] };
|
|
51
|
+
* Type helper for defining actions per resource.
|
|
52
|
+
* Use with `satisfies` to ensure keys match the resource definitions.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import type { Actions } from "@zap-studio/permit/types";
|
|
57
|
+
*
|
|
58
|
+
* const actions = {
|
|
59
|
+
* post: ["read", "write", "delete"],
|
|
60
|
+
* comment: ["read", "write"],
|
|
61
|
+
* } as const satisfies Actions<typeof resources>;
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
type Actions<TResources extends Resources> = { [K in keyof TResources]: readonly string[]; };
|
|
66
65
|
/**
|
|
67
|
-
* Infers the output type from a Standard Schema.
|
|
68
|
-
*/
|
|
66
|
+
* Infers the output type from a Standard Schema.
|
|
67
|
+
*/
|
|
69
68
|
type InferResource<TResources extends Resources, TResourceKey extends keyof TResources> = StandardSchemaV1.InferOutput<TResources[TResourceKey]>;
|
|
70
69
|
/**
|
|
71
|
-
* Infers the action union type for a specific resource.
|
|
72
|
-
*/
|
|
70
|
+
* Infers the action union type for a specific resource.
|
|
71
|
+
*/
|
|
73
72
|
type InferAction<TActions extends Record<string, readonly string[]>, K extends keyof TActions> = TActions[K][number];
|
|
74
73
|
/**
|
|
75
|
-
* Infers the permission-string union for all resource/action combinations.
|
|
76
|
-
*
|
|
77
|
-
* @example
|
|
78
|
-
* ```ts
|
|
79
|
-
* type Permission = InferPermission<typeof resources, typeof actions>;
|
|
80
|
-
* // "post:read" | "post:write" | "comment:read"
|
|
81
|
-
* ```
|
|
82
|
-
*/
|
|
83
|
-
type InferPermission<TResources extends Resources, TActions extends Actions<TResources>> = { [K in keyof TResources & keyof TActions]: `${K & string}:${InferAction<TActions, K> & string}
|
|
74
|
+
* Infers the permission-string union for all resource/action combinations.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* type Permission = InferPermission<typeof resources, typeof actions>;
|
|
79
|
+
* // "post:read" | "post:write" | "comment:read"
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
type InferPermission<TResources extends Resources, TActions extends Actions<TResources>> = { [K in keyof TResources & keyof TActions]: `${K & string}:${InferAction<TActions, K> & string}`; }[keyof TResources & keyof TActions];
|
|
84
83
|
/**
|
|
85
|
-
* A function that determines whether a given action on a resource is allowed in a specific context.
|
|
86
|
-
*/
|
|
84
|
+
* A function that determines whether a given action on a resource is allowed in a specific context.
|
|
85
|
+
*/
|
|
87
86
|
type PolicyFn<TContext extends Context, TAction extends string = string, TResource = unknown> = (context: TContext, action: TAction, resource: TResource) => Decision;
|
|
88
87
|
/**
|
|
89
|
-
* A function that evaluates a condition for a given action and resource in a specific context.
|
|
90
|
-
*/
|
|
88
|
+
* A function that evaluates a condition for a given action and resource in a specific context.
|
|
89
|
+
*/
|
|
91
90
|
type ConditionFn<TContext extends Context, TAction extends string = string, TResource = unknown> = (context: TContext, action: TAction, resource: TResource) => boolean;
|
|
92
91
|
/**
|
|
93
|
-
* Maps actions to their corresponding policy functions for a specific resource.
|
|
94
|
-
*/
|
|
95
|
-
type ActionPolicyMap<TContext extends Context, TAction extends string = string, TResource = unknown> = { [A in TAction]?: PolicyFn<TContext, A, TResource
|
|
92
|
+
* Maps actions to their corresponding policy functions for a specific resource.
|
|
93
|
+
*/
|
|
94
|
+
type ActionPolicyMap<TContext extends Context, TAction extends string = string, TResource = unknown> = { [A in TAction]?: PolicyFn<TContext, A, TResource>; };
|
|
96
95
|
/**
|
|
97
|
-
* Defines the rules for each resource and action combination.
|
|
98
|
-
* Each resource key maps to an object where each action key maps to a policy function.
|
|
99
|
-
*/
|
|
100
|
-
type Rules<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> = { [K in keyof TResources & keyof TActions]: ActionPolicyMap<TContext, InferAction<TActions, K>, InferResource<TResources, K
|
|
96
|
+
* Defines the rules for each resource and action combination.
|
|
97
|
+
* Each resource key maps to an object where each action key maps to a policy function.
|
|
98
|
+
*/
|
|
99
|
+
type Rules<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> = { [K in keyof TResources & keyof TActions]: ActionPolicyMap<TContext, InferAction<TActions, K>, InferResource<TResources, K>>; };
|
|
101
100
|
/**
|
|
102
|
-
* Configuration object for creating a permit policy.
|
|
103
|
-
*
|
|
104
|
-
* @example
|
|
105
|
-
* ```ts
|
|
106
|
-
* const config: PermitConfig<MyContext> = {
|
|
107
|
-
* resources,
|
|
108
|
-
* actions,
|
|
109
|
-
* rules: {
|
|
110
|
-
* post: { read: allow(), write: deny() },
|
|
111
|
-
* },
|
|
112
|
-
* };
|
|
113
|
-
* ```
|
|
114
|
-
*/
|
|
101
|
+
* Configuration object for creating a permit policy.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* const config: PermitConfig<MyContext> = {
|
|
106
|
+
* resources,
|
|
107
|
+
* actions,
|
|
108
|
+
* rules: {
|
|
109
|
+
* post: { read: allow(), write: deny() },
|
|
110
|
+
* },
|
|
111
|
+
* };
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
115
114
|
interface PermitConfig<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> {
|
|
116
115
|
actions: TActions;
|
|
117
116
|
resources: TResources;
|
|
118
117
|
rules: Rules<TContext, TResources, TActions>;
|
|
119
118
|
}
|
|
120
119
|
/**
|
|
121
|
-
* Represents a policy object that can evaluate permissions.
|
|
122
|
-
* The `can` method checks if a given action is permitted on a resource in a specific context.
|
|
123
|
-
*
|
|
124
|
-
* @example
|
|
125
|
-
* ```ts
|
|
126
|
-
* const policy: Policy<MyContext> = createPolicy({
|
|
127
|
-
* resources,
|
|
128
|
-
* actions,
|
|
129
|
-
* rules: { ... },
|
|
130
|
-
* });
|
|
131
|
-
*
|
|
132
|
-
* await policy.can(ctx, "post:read", postData); // true or false
|
|
133
|
-
* ```
|
|
134
|
-
*/
|
|
120
|
+
* Represents a policy object that can evaluate permissions.
|
|
121
|
+
* The `can` method checks if a given action is permitted on a resource in a specific context.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* const policy: Policy<MyContext> = createPolicy({
|
|
126
|
+
* resources,
|
|
127
|
+
* actions,
|
|
128
|
+
* rules: { ... },
|
|
129
|
+
* });
|
|
130
|
+
*
|
|
131
|
+
* await policy.can(ctx, "post:read", postData); // true or false
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
135
134
|
interface Policy<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> {
|
|
136
135
|
/**
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
can<K extends keyof TResources & keyof TActions>(context: TContext, permission: `${K & string}:${InferAction<TActions, K> & string}`, resource: InferResource<TResources, K>)
|
|
136
|
+
* Determines if the specified action is permitted on the resource in the given context.
|
|
137
|
+
*/
|
|
138
|
+
can: <K extends keyof TResources & keyof TActions>(context: TContext, permission: `${K & string}:${InferAction<TActions, K> & string}`, resource: InferResource<TResources, K>) => Promise<boolean>;
|
|
140
139
|
}
|
|
141
140
|
//#endregion
|
|
142
141
|
export { ActionPolicyMap, Actions, ConditionFn, Context, Decision, InferAction, InferPermission, InferResource, PermitConfig, Policy, PolicyFn, Resources, Role, RoleHierarchy, Rules };
|
package/dist/types.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;KAaY;;;;;KAMA,QAAQ,sBAAsB;;;;KAK9B,KAAK,iCAAiC;;;;;;;;;;;;;;;;KAiBtC,cAAc,cAAc,OAAO,QAAQ,OAAO,OAAO;;;;;;;;;;;;;;;;KAiBzD,UAAU,wCAAwC,OAC5D,cACA;;;;;;;;;;;;;;;KAiBU,QAAQ,mBAAmB,gBACpC,WAAW;;;;KAMF,cACV,mBAAmB,WACnB,2BAA2B,cACzB,iBAAiB,YAAY,WAAW;;;;KAKhC,YACV,iBAAiB,mCACjB,gBAAgB,YACd,SAAS;;;;;;;;;;KAWD,gBACV,mBAAmB,WACnB,iBAAiB,QAAQ,kBAExB,WAAW,mBACJ,cAAc,cAAc,YAAY,UAAU,uBACpD,mBAAmB;;;;KAKf,SACV,iBAAiB,SACjB,iCACA,wBACG,SAAS,UAAU,QAAQ,SAAS,UAAU,cAAc;;;;KAKrD,YACV,iBAAiB,SACjB,iCACA,wBACG,SAAS,UAAU,QAAQ,SAAS,UAAU;;;;KAKvC,gBACV,iBAAiB,SACjB,iCACA,0BAEC,KAAK,WAAW,SAAS,UAAU,GAAG;;;;;KAO7B,MACV,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ,kBAE9C,WAAW,mBAAmB,WAAW,gBACxC,UACA,YAAY,UAAU,IACtB,cAAc,YAAY;;;;;;;;;;;;;;;UAkBb,aACf,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ;EAE/C,SAAS;EACT,WAAW;EACX,OAAO,MAAM,UAAU,YAAY;;;;;;;;;;;;;;;;;UAkBpB,OACf,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ;;;;EAK/C,MAAM,gBAAgB,mBAAmB,UACvC,SAAS,UACT,eAAe,cAAc,YAAY,UAAU,eACnD,UAAU,cAAc,YAAY,OACjC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zap-studio/permit",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A type-safe, declarative authorization library for TypeScript with Standard Schema support",
|
|
6
6
|
"keywords": [
|
|
@@ -43,19 +43,18 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@zap-studio/validation": "0.3.
|
|
46
|
+
"@zap-studio/validation": "0.3.5"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"
|
|
50
|
-
"
|
|
49
|
+
"tsdown": "^0.22.4",
|
|
50
|
+
"typescript": "^7.0.2",
|
|
51
|
+
"vitest": "^4.1.10",
|
|
51
52
|
"@zap-studio/typescript": "0.0.0"
|
|
52
53
|
},
|
|
53
54
|
"engines": {
|
|
54
55
|
"node": ">=18.0.0"
|
|
55
56
|
},
|
|
56
57
|
"scripts": {
|
|
57
|
-
"build": "
|
|
58
|
-
"test": "vp test run",
|
|
59
|
-
"test:watch": "vp test watch"
|
|
58
|
+
"build": "tsdown --config ./tsdown.config.ts"
|
|
60
59
|
}
|
|
61
60
|
}
|