@zap-studio/permit 0.2.1 → 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 +7 -1
- package/bin/intent.js +4 -0
- package/package.json +10 -4
- package/skills/zap-permit-policy-authoring/SKILL.md +157 -0
- /package/{LICENSE.md → LICENSE} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @zap-studio/permit
|
|
2
2
|
|
|
3
|
+
## 0.2.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e26293e: Updated dependencies.
|
|
8
|
+
- @zap-studio/validation@0.3.2
|
|
9
|
+
|
|
3
10
|
## 0.2.1
|
|
4
11
|
|
|
5
12
|
### Patch Changes
|
|
@@ -7,7 +14,6 @@
|
|
|
7
14
|
- 5ea3d3b: Updated dependencies.
|
|
8
15
|
- @zap-studio/validation@0.3.1
|
|
9
16
|
|
|
10
|
-
|
|
11
17
|
## 0.2.0
|
|
12
18
|
|
|
13
19
|
### Changed
|
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",
|
|
@@ -57,6 +60,9 @@
|
|
|
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",
|
|
@@ -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
|