@zap-studio/permit 0.2.0 → 0.2.2
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 +45 -33
- package/bin/intent.js +4 -0
- package/package.json +13 -7
- package/skills/zap-permit-policy-authoring/SKILL.md +157 -0
- /package/{LICENSE.md → LICENSE} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,57 +1,69 @@
|
|
|
1
1
|
# @zap-studio/permit
|
|
2
2
|
|
|
3
|
-
## 0.2.
|
|
3
|
+
## 0.2.2
|
|
4
4
|
|
|
5
|
-
###
|
|
5
|
+
### Patch Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- e26293e: Updated dependencies.
|
|
8
|
+
- @zap-studio/validation@0.3.2
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## 0.2.1
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
### Patch Changes
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
- 5ea3d3b: Updated dependencies.
|
|
15
|
+
- @zap-studio/validation@0.3.1
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
## 0.2.0
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- f0f503e: Made policy evaluation asynchronous by default.
|
|
22
|
+
|
|
23
|
+
### Breaking Changes
|
|
24
|
+
|
|
25
|
+
- f0f503e: `policy.can(...)` now returns `Promise<boolean>`.
|
|
26
|
+
- f0f503e: `createPolicy()` now uses async-safe Standard Schema validation for resource schemas (including async resource schemas); this is not a separate `Policy` schema API.
|
|
27
|
+
- f0f503e: The `Policy` interface changed so `can()` is async, and `mergePolicies`/`mergePoliciesAny` are async accordingly.
|
|
28
|
+
- Action required: callers must `await policy.can(...)` and handle `mergePolicies`/`mergePoliciesAny` as async operations; also account for async-safe resource schema validation in `createPolicy()`.
|
|
16
29
|
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
### Dependencies
|
|
31
|
+
|
|
32
|
+
- f75b984: Updated dependency `@zap-studio/validation` to `0.3.0`.
|
|
19
33
|
|
|
20
34
|
## 0.1.3
|
|
21
35
|
|
|
22
|
-
###
|
|
36
|
+
### Dependencies
|
|
23
37
|
|
|
24
|
-
- Updated
|
|
25
|
-
- @zap-studio/validation@0.2.1
|
|
38
|
+
- e4542bb: Updated dependency `@zap-studio/validation` to `0.2.1`.
|
|
26
39
|
|
|
27
40
|
## 0.1.2
|
|
28
41
|
|
|
29
|
-
###
|
|
42
|
+
### Changed
|
|
30
43
|
|
|
31
|
-
- 2de8183:
|
|
32
|
-
- Updated dependencies [2de8183]
|
|
33
|
-
- @zap-studio/validation@0.2.0
|
|
44
|
+
- 2de8183: Adopted shared synchronous Standard Schema validator utilities from `@zap-studio/validation` for resource schema validation in `createPolicy`.
|
|
34
45
|
|
|
35
|
-
|
|
46
|
+
### Dependencies
|
|
36
47
|
|
|
37
|
-
|
|
48
|
+
- 2de8183: Updated dependency `@zap-studio/validation` to `0.2.0`.
|
|
38
49
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
## 0.1.0
|
|
50
|
+
## 0.1.1
|
|
42
51
|
|
|
43
|
-
###
|
|
52
|
+
### Fixed
|
|
44
53
|
|
|
45
|
-
-
|
|
54
|
+
- 907d903: Added runtime resource validation and fail-closed behavior, including deny-by-default when merges are invoked with no policies.
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
## 0.1.0
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
- Policy
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
- `
|
|
57
|
-
-
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
- 0627885: Initial release of `@zap-studio/permit`.
|
|
61
|
+
- Declarative policy creation with `createPolicy()`.
|
|
62
|
+
- Policy rules: `allow()`, `deny()`, and `when()` for conditional access.
|
|
63
|
+
- Condition combinators: `and()`, `or()`, `not()`, and `has()`.
|
|
64
|
+
- Role-based access control with `hasRole()` and role hierarchies.
|
|
65
|
+
- Policy merging with `mergePolicies()` (deny-overrides) and `mergePoliciesAny()` (allow-overrides).
|
|
66
|
+
- Standard Schema support (Zod, Valibot, ArkType, and more).
|
|
67
|
+
- Full TypeScript type inference support.
|
|
68
|
+
- `PolicyError` for authorization failures.
|
|
69
|
+
- `assertNever()` for exhaustive checks.
|
package/bin/intent.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zap-studio/permit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -30,12 +30,15 @@
|
|
|
30
30
|
"files": [
|
|
31
31
|
"dist",
|
|
32
32
|
"CHANGELOG.md",
|
|
33
|
-
"LICENSE
|
|
34
|
-
"README.md"
|
|
33
|
+
"LICENSE",
|
|
34
|
+
"README.md",
|
|
35
|
+
"skills",
|
|
36
|
+
"bin",
|
|
37
|
+
"!skills/_artifacts"
|
|
35
38
|
],
|
|
36
39
|
"dependencies": {
|
|
37
40
|
"@standard-schema/spec": "^1.1.0",
|
|
38
|
-
"@zap-studio/validation": "0.3.
|
|
41
|
+
"@zap-studio/validation": "0.3.2"
|
|
39
42
|
},
|
|
40
43
|
"devDependencies": {
|
|
41
44
|
"@types/node": "^25.0.2",
|
|
@@ -43,9 +46,9 @@
|
|
|
43
46
|
"tsdown": "^0.18.0",
|
|
44
47
|
"typescript": "^5.9.3",
|
|
45
48
|
"vitest": "^4.0.18",
|
|
49
|
+
"@zap-studio/vitest-config": "0.0.0",
|
|
46
50
|
"@zap-studio/typescript-config": "0.0.0",
|
|
47
|
-
"@zap-studio/tsdown-config": "0.0.0"
|
|
48
|
-
"@zap-studio/vitest-config": "0.0.0"
|
|
51
|
+
"@zap-studio/tsdown-config": "0.0.0"
|
|
49
52
|
},
|
|
50
53
|
"exports": {
|
|
51
54
|
".": "./dist/index.mjs",
|
|
@@ -57,9 +60,12 @@
|
|
|
57
60
|
"main": "./dist/index.mjs",
|
|
58
61
|
"module": "./dist/index.mjs",
|
|
59
62
|
"types": "./dist/index.d.mts",
|
|
63
|
+
"bin": {
|
|
64
|
+
"intent": "./bin/intent.js"
|
|
65
|
+
},
|
|
60
66
|
"scripts": {
|
|
61
67
|
"build": "tsdown --config tsdown.config.ts",
|
|
62
|
-
"
|
|
68
|
+
"typecheck": "tsc --noEmit",
|
|
63
69
|
"test": "vitest run",
|
|
64
70
|
"test:watch": "vitest --watch"
|
|
65
71
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: zap-permit-policy-authoring
|
|
3
|
+
description: >
|
|
4
|
+
Author typed authorization policies with @zap-studio/permit using
|
|
5
|
+
createPolicy, allow/deny/when, condition combinators, has/hasRole,
|
|
6
|
+
and mergePolicies vs mergePoliciesAny decision strategies.
|
|
7
|
+
type: core
|
|
8
|
+
library: '@zap-studio/permit'
|
|
9
|
+
library_version: '0.2.1'
|
|
10
|
+
sources:
|
|
11
|
+
- 'zap-studio/monorepo:packages/permit/README.md'
|
|
12
|
+
- 'zap-studio/monorepo:packages/permit/src/index.ts'
|
|
13
|
+
- 'zap-studio/monorepo:packages/permit/src/types.ts'
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# @zap-studio/permit — Policy Authoring
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { z } from 'zod';
|
|
22
|
+
import { createPolicy, allow, when } from '@zap-studio/permit';
|
|
23
|
+
import type { Resources, Actions } from '@zap-studio/permit/types';
|
|
24
|
+
|
|
25
|
+
const resources = {
|
|
26
|
+
post: z.object({ id: z.string(), authorId: z.string() }),
|
|
27
|
+
} satisfies Resources;
|
|
28
|
+
|
|
29
|
+
const actions = {
|
|
30
|
+
post: ['read', 'write'],
|
|
31
|
+
} as const satisfies Actions<typeof resources>;
|
|
32
|
+
|
|
33
|
+
type AppContext = { user: { id: string; role: 'user' | 'admin' } };
|
|
34
|
+
|
|
35
|
+
const policy = createPolicy<AppContext>({
|
|
36
|
+
resources,
|
|
37
|
+
actions,
|
|
38
|
+
rules: {
|
|
39
|
+
post: {
|
|
40
|
+
read: allow(),
|
|
41
|
+
write: when((ctx, _action, post) => ctx.user.id === post.authorId),
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Core Patterns
|
|
48
|
+
|
|
49
|
+
### Compose conditions with `and`, `or`, and `not`
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { when, and, or, not } from '@zap-studio/permit';
|
|
53
|
+
|
|
54
|
+
const canEdit = when(
|
|
55
|
+
and(
|
|
56
|
+
(ctx, _action, post) => ctx.user.id === post.authorId,
|
|
57
|
+
not((_ctx, _action, post) => post.locked === true),
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const canRead = when(
|
|
62
|
+
or(
|
|
63
|
+
(_ctx, _action, post) => post.visibility === 'public',
|
|
64
|
+
(ctx, _action, post) => ctx.user.id === post.authorId,
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Add role inheritance checks with `hasRole`
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { when, hasRole } from '@zap-studio/permit';
|
|
73
|
+
|
|
74
|
+
const hierarchy = {
|
|
75
|
+
guest: [],
|
|
76
|
+
user: ['guest'],
|
|
77
|
+
admin: ['user'],
|
|
78
|
+
} as const;
|
|
79
|
+
|
|
80
|
+
const adminOnly = when(hasRole('admin', hierarchy));
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Choose merge strategy explicitly
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { mergePolicies, mergePoliciesAny } from '@zap-studio/permit';
|
|
87
|
+
|
|
88
|
+
const strict = mergePolicies(basePolicy, tenantPolicy); // all must allow
|
|
89
|
+
const permissive = mergePoliciesAny(basePolicy, temporaryOverridePolicy); // any can allow
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Common Mistakes
|
|
93
|
+
|
|
94
|
+
### HIGH Using action missing from actions map
|
|
95
|
+
|
|
96
|
+
Wrong:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
await policy.can(ctx, 'publish', 'post', post);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Correct:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const actions = {
|
|
106
|
+
post: ['read', 'write', 'publish'],
|
|
107
|
+
} as const;
|
|
108
|
+
|
|
109
|
+
await policy.can(ctx, 'publish', 'post', post);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`can()` first checks `actions[resourceType]`; missing actions always resolve to `false`.
|
|
113
|
+
|
|
114
|
+
Source: zap-studio/monorepo:packages/permit/src/index.ts
|
|
115
|
+
|
|
116
|
+
### HIGH Assuming invalid resources still hit rule function
|
|
117
|
+
|
|
118
|
+
Wrong:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
await policy.can(ctx, 'write', 'post', { id: 123 } as any);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Correct:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
await policy.can(ctx, 'write', 'post', {
|
|
128
|
+
id: '123',
|
|
129
|
+
authorId: ctx.user.id,
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Resource validation runs before policy evaluation; invalid payloads short-circuit to deny.
|
|
134
|
+
|
|
135
|
+
Source: zap-studio/monorepo:packages/permit/src/index.ts
|
|
136
|
+
|
|
137
|
+
### MEDIUM Expecting `mergePoliciesAny` to enforce deny-overrides
|
|
138
|
+
|
|
139
|
+
Wrong:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const merged = mergePoliciesAny(basePolicy, restrictivePolicy);
|
|
143
|
+
// expecting restrictivePolicy to always win
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Correct:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
const merged = mergePolicies(basePolicy, restrictivePolicy);
|
|
150
|
+
// deny-overrides behavior
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`mergePoliciesAny` returns allowed when any policy allows; use `mergePolicies` for strict composition.
|
|
154
|
+
|
|
155
|
+
Source: zap-studio/monorepo:packages/permit/src/index.ts
|
|
156
|
+
|
|
157
|
+
See also: zap-validation-standard-schema/SKILL.md — invalid resource payload behavior.
|
/package/{LICENSE.md → LICENSE}
RENAMED
|
File without changes
|