@pikku/skills 0.12.22 → 0.12.26
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 +134 -0
- package/dist/skills.gen.js +1 -1
- package/package.json +1 -1
- package/skills/pikku-a11y/SKILL.md +59 -0
- package/skills/pikku-addon/SKILL.md +2 -2
- package/skills/pikku-agent/SKILL.md +67 -316
- package/skills/pikku-agent/references/agents.md +299 -0
- package/skills/{pikku-ai-vercel/SKILL.md → pikku-agent/references/runner-vercel.md} +2 -24
- package/skills/{pikku-ai-voice/SKILL.md → pikku-agent/references/voice.md} +1 -22
- package/skills/pikku-architect/SKILL.md +265 -0
- package/skills/pikku-auth/SKILL.md +89 -0
- package/skills/{pikku-better-auth/SKILL.md → pikku-auth/references/better-auth.md} +126 -34
- package/skills/{pikku-jose/SKILL.md → pikku-auth/references/jose.md} +1 -23
- package/skills/{pikku-machine-auth/SKILL.md → pikku-auth/references/machine-auth.md} +0 -23
- package/skills/pikku-auth/references/permissions.md +261 -0
- package/skills/{pikku-security/SKILL.md → pikku-auth/references/sessions.md} +0 -20
- package/skills/pikku-build/SKILL.md +88 -0
- package/skills/{pikku-build-app/SKILL.md → pikku-build/references/app.md} +76 -24
- package/skills/{pikku-feature/SKILL.md → pikku-build/references/feature.md} +1 -8
- package/skills/{pikku-build-app → pikku-build}/references/multi-app.md +56 -1
- package/skills/{pikku-build-platform/SKILL.md → pikku-build/references/platform.md} +22 -37
- package/skills/{pikku-template-clone/SKILL.md → pikku-build/references/post-clone.md} +0 -6
- package/skills/{pikku-build-quick/SKILL.md → pikku-build/references/quick.md} +4 -20
- package/skills/{pikku-build-app → pikku-build}/references/ship.md +9 -3
- package/skills/pikku-concepts/SKILL.md +72 -7
- package/skills/pikku-concepts/references/concept-mapping.md +8 -8
- package/skills/pikku-deploy/SKILL.md +158 -0
- package/skills/{pikku-deploy-azure/SKILL.md → pikku-deploy/references/azure.md} +18 -50
- package/skills/pikku-deploy/references/cloudflare.md +104 -0
- package/skills/pikku-deploy/references/express.md +92 -0
- package/skills/{pikku-deploy-fastify/SKILL.md → pikku-deploy/references/fastify.md} +8 -32
- package/skills/{pikku-deploy-lambda/SKILL.md → pikku-deploy/references/lambda.md} +6 -27
- package/skills/{pikku-deploy-nextjs/SKILL.md → pikku-deploy/references/nextjs.md} +9 -33
- package/skills/pikku-deploy/references/uws.md +72 -0
- package/skills/pikku-deploy/references/ws.md +75 -0
- package/skills/pikku-emails/SKILL.md +3 -2
- package/skills/pikku-fabric/SKILL.md +47 -20
- package/skills/{pikku-fabric-debug/SKILL.md → pikku-fabric/references/debugging.md} +0 -6
- package/skills/pikku-i18n/SKILL.md +62 -207
- package/skills/{pikku-paraglide/SKILL.md → pikku-i18n/references/enum-labels.md} +0 -6
- package/skills/pikku-i18n/references/messages.md +218 -0
- package/skills/{pikku-rtl/SKILL.md → pikku-i18n/references/rtl.md} +3 -9
- package/skills/pikku-knowledge/SKILL.md +15 -0
- package/skills/pikku-kysely/SKILL.md +13 -13
- package/skills/pikku-list-query/SKILL.md +163 -0
- package/skills/pikku-meta/SKILL.md +58 -130
- package/skills/{pikku-deps/SKILL.md → pikku-meta/references/audit.md} +1 -17
- package/skills/pikku-meta/references/meta.md +114 -0
- package/skills/{pikku-versioning/SKILL.md → pikku-meta/references/versioning.md} +0 -26
- package/skills/pikku-middleware/SKILL.md +5 -5
- package/skills/pikku-n8n-import/SKILL.md +0 -1
- package/skills/pikku-permissions/SKILL.md +75 -229
- package/skills/pikku-react/SKILL.md +50 -298
- package/skills/pikku-react/references/client.md +313 -0
- package/skills/{pikku-react-query/SKILL.md → pikku-react/references/react-query.md} +2 -22
- package/skills/{pikku-workflows-client/SKILL.md → pikku-react/references/workflows.md} +1 -22
- package/skills/pikku-realtime/SKILL.md +110 -251
- package/skills/pikku-scenario/SKILL.md +60 -45
- package/skills/pikku-scenario/references/persona-run.md +148 -0
- package/skills/pikku-seo/SKILL.md +133 -0
- package/skills/pikku-service-backends/SKILL.md +154 -0
- package/skills/pikku-service-backends/references/aws.md +106 -0
- package/skills/pikku-service-backends/references/backblaze.md +57 -0
- package/skills/pikku-service-backends/references/mongodb.md +90 -0
- package/skills/pikku-service-backends/references/redis.md +75 -0
- package/skills/pikku-service-backends/references/schema.md +63 -0
- package/skills/pikku-services/SKILL.md +68 -291
- package/skills/{pikku-audit/SKILL.md → pikku-services/references/audit.md} +0 -22
- package/skills/{pikku-config/SKILL.md → pikku-services/references/config.md} +1 -25
- package/skills/{pikku-pino/SKILL.md → pikku-services/references/pino.md} +0 -20
- package/skills/pikku-services/references/services.md +272 -0
- package/skills/pikku-software-archaeology/README.md +5 -1
- package/skills/pikku-software-archaeology/SKILL.md +16 -2
- package/skills/{pikku-product-second-opinion/example/sample-report.md → pikku-software-archaeology/example/second-opinion-sample-report.md} +1 -1
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +1 -1
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +3 -3
- package/skills/{pikku-product-second-opinion/SKILL.md → pikku-software-archaeology/references/second-opinion.md} +4 -9
- package/skills/pikku-webhook/SKILL.md +224 -0
- package/skills/pikku-wiring/SKILL.md +180 -0
- package/skills/{pikku-websocket/SKILL.md → pikku-wiring/references/channel.md} +2 -35
- package/skills/{pikku-cli/SKILL.md → pikku-wiring/references/cli.md} +1 -33
- package/skills/{pikku-gateway-slack/SKILL.md → pikku-wiring/references/gateway-slack.md} +0 -23
- package/skills/{pikku-http/SKILL.md → pikku-wiring/references/http.md} +3 -39
- package/skills/{pikku-mcp/SKILL.md → pikku-wiring/references/mcp.md} +0 -33
- package/skills/{pikku-queue/SKILL.md → pikku-wiring/references/queue.md} +1 -33
- package/skills/pikku-wiring/references/realtime.md +265 -0
- package/skills/{pikku-rpc/SKILL.md → pikku-wiring/references/rpc.md} +0 -32
- package/skills/{pikku-schedule/SKILL.md → pikku-wiring/references/scheduler.md} +1 -35
- package/skills/{pikku-trigger/SKILL.md → pikku-wiring/references/trigger.md} +0 -43
- package/skills/pikku-workflow/SKILL.md +39 -2
- package/skills/pikku-aws/SKILL.md +0 -161
- package/skills/pikku-backblaze/SKILL.md +0 -104
- package/skills/pikku-deploy-cloudflare/SKILL.md +0 -123
- package/skills/pikku-deploy-express/SKILL.md +0 -122
- package/skills/pikku-deploy-uws/SKILL.md +0 -144
- package/skills/pikku-mongodb/SKILL.md +0 -113
- package/skills/pikku-product-second-opinion/README.md +0 -43
- package/skills/pikku-redis/SKILL.md +0 -99
- package/skills/pikku-schema-ajv/SKILL.md +0 -83
- package/skills/pikku-schema-cfworker/SKILL.md +0 -82
- package/skills/pikku-ws/SKILL.md +0 -87
- /package/skills/{pikku-build-app → pikku-build}/references/theming.md +0 -0
- /package/skills/{pikku-product-second-opinion/references/report-template.md → pikku-software-archaeology/references/second-opinion-report-template.md} +0 -0
- /package/skills/{pikku-cli/references/complete-example.md → pikku-wiring/references/cli-complete-example.md} +0 -0
- /package/skills/{pikku-http → pikku-wiring}/references/http-options.md +0 -0
- /package/skills/{pikku-realtime/references/other-routes.md → pikku-wiring/references/realtime-other-routes.md} +0 -0
|
@@ -1,280 +1,126 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-permissions
|
|
3
3
|
description: >-
|
|
4
|
-
Use when
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
ownership, add role-based or scope-based access, declares or grants scopes, hits
|
|
8
|
-
MissingScopeError, or asks where permission checks belong. DO NOT TRIGGER when: user asks about
|
|
9
|
-
middleware or request interception (use pikku-middleware), authentication strategies (use
|
|
10
|
-
pikku-security), or session management.
|
|
4
|
+
Use when deciding WHO may call a function — resource ownership, role gates, admin-only actions, or any "only their own rows" rule. Covers the `permissions` field, `pikkuPermission`, `pikkuAuth`, scopes, and where ownership belongs versus where it does not.
|
|
5
|
+
TRIGGER when: writing or reviewing any function that touches a row a user owns, gating an action on a role, building the permissions half of a contract in build PHASE 2, or about to write an `if` in a function body that decides whether the caller is allowed.
|
|
6
|
+
DO NOT TRIGGER when: the question is how to sign someone in or seed a persona (that is pikku-auth), or how to shape a paginated list (that is pikku-list-query).
|
|
11
7
|
installGroups: [core]
|
|
12
8
|
---
|
|
13
9
|
|
|
14
10
|
# Pikku Permissions
|
|
15
11
|
|
|
16
|
-
##
|
|
12
|
+
## The rule
|
|
17
13
|
|
|
18
|
-
**
|
|
14
|
+
**Authorization goes in the `permissions` field. Never in the `func` body.**
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
This includes: org access checks, repo access checks, role checks, resource ownership, and any other authorization logic. The `permissions` field runs before `func` and is visible to the inspector, so the gate is declared rather than buried — which is what lets `pikku info permissions` and an audit see it at all. Alongside it sits `scopes` (see below) for grant-based gating; between them they are where Pikku enforces authorization. The one sanctioned exception is `permissionsInBody`, covered at the end.
|
|
16
|
+
`permissions` runs before `func`, and it is DECLARED — `pikku meta` and the auditor can
|
|
17
|
+
see it. An `if` in the body is the same check, invisible: nothing can tell you which
|
|
18
|
+
functions are gated or how, and the next person to add a caller gets no warning.
|
|
25
19
|
|
|
26
20
|
```typescript
|
|
27
|
-
//
|
|
21
|
+
// RIGHT
|
|
28
22
|
export const deleteBook = pikkuFunc({
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
permissions: {
|
|
33
|
-
owner: isBookOwner, // ← authorization here
|
|
23
|
+
permissions: { owner: isBookOwner },
|
|
24
|
+
func: async ({ kysely }, { bookId }) => {
|
|
25
|
+
await kysely.deleteFrom('book').where('bookId', '=', bookId).execute()
|
|
34
26
|
},
|
|
35
27
|
})
|
|
36
28
|
|
|
37
|
-
// WRONG —
|
|
29
|
+
// WRONG — the gate is buried in the body
|
|
38
30
|
export const deleteBook = pikkuFunc({
|
|
39
|
-
func: async ({
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
func: async ({ kysely }, { bookId }, { session }) => {
|
|
32
|
+
const book = await kysely.selectFrom('book')...executeTakeFirst()
|
|
33
|
+
if (book?.ownerId !== session.userId) throw new UnauthorizedError()
|
|
34
|
+
await kysely.deleteFrom('book').where('bookId', '=', bookId).execute()
|
|
42
35
|
},
|
|
43
36
|
})
|
|
44
37
|
```
|
|
45
38
|
|
|
46
|
-
##
|
|
39
|
+
## `auth: true` IS NOT OWNERSHIP — this is the one people get wrong
|
|
47
40
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
`auth: true` means "somebody is signed in". It does NOT mean "this row is theirs". A CRUD
|
|
42
|
+
function set to `auth: true` and nothing else lets ANY signed-in user delete ANY other
|
|
43
|
+
user's row by passing its id. Every function that takes a row id needs BOTH: `auth: true`
|
|
44
|
+
for the session, and a `permissions` entry for the ownership.
|
|
52
45
|
|
|
53
|
-
|
|
46
|
+
Equally: do NOT write an `isSignedIn` permission that returns `!!session`. That re-checks
|
|
47
|
+
authentication, which `auth: true` already did. A permission answers *may this user do
|
|
48
|
+
this* — role, ownership, tier — never *is there a session*.
|
|
54
49
|
|
|
55
|
-
|
|
50
|
+
## Single row vs list — where ownership actually goes
|
|
56
51
|
|
|
57
|
-
|
|
58
|
-
something **beyond** merely having a session (a flag, a tier, a claim).
|
|
52
|
+
This is the distinction to get right, and both halves are correct code:
|
|
59
53
|
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
- **A function taking a row id** (`get`, `update`, `delete`) — ownership is a
|
|
55
|
+
PERMISSION. Load the row, compare the owner to the session. It is a yes/no question
|
|
56
|
+
about one row, which is exactly what a permission is.
|
|
57
|
+
- **A function returning many rows** (`list`, `search`, any stats query) — ownership is
|
|
58
|
+
a `WHERE` clause in the query, because "only their rows" is a filter, not a yes/no.
|
|
59
|
+
There is no permission to write here; scoping the query IS the enforcement.
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
export const isVerified = pikkuAuth(
|
|
65
|
-
async (_services, session) => !!session?.emailVerified
|
|
66
|
-
)
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
**Do NOT write an "is signed in" permission.** A checker that just returns
|
|
70
|
-
`!!session` is not authorization — it re-checks authentication, which the
|
|
71
|
-
function already enforces. A function that needs a signed-in user sets
|
|
72
|
-
`auth: true` (the default for `pikkuFunc`); it does not also carry a
|
|
73
|
-
`permissions: { signedIn }`.
|
|
61
|
+
A list that fetches everything and then filters in JS is a bug, not a permission.
|
|
74
62
|
|
|
75
|
-
|
|
76
|
-
// WRONG — redundant with auth: true; adds a permission that gates nothing.
|
|
77
|
-
export const isSignedIn = pikkuAuth(async (_s, session) => !!session)
|
|
78
|
-
pikkuFunc({ auth: true, permissions: { signedIn: isSignedIn } /* ... */ })
|
|
63
|
+
## Writing the checkers
|
|
79
64
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
A permission answers "_may this user do this?_" (role, ownership, tier) — never
|
|
85
|
-
"_is there a session?_".
|
|
86
|
-
|
|
87
|
-
### `pikkuPermission(fn)` — Data-Aware Checks
|
|
88
|
-
|
|
89
|
-
Use when authorization depends on the actual request data (e.g., resource ownership).
|
|
65
|
+
Put them in `src/permissions/`, one file per entity, and reuse one checker across every function on that
|
|
66
|
+
entity rather than writing a near-copy per function.
|
|
90
67
|
|
|
91
68
|
```typescript
|
|
92
|
-
|
|
69
|
+
// src/permissions/book.ts
|
|
70
|
+
import { pikkuPermission, pikkuAuth } from '#pikku/auth'
|
|
93
71
|
|
|
72
|
+
// Data-aware: gets the input, so it can load the row the caller named.
|
|
94
73
|
export const isBookOwner = pikkuPermission(
|
|
95
|
-
async ({
|
|
96
|
-
const book = await
|
|
97
|
-
|
|
98
|
-
|
|
74
|
+
async ({ kysely }, { bookId }, { session }) => {
|
|
75
|
+
const book = await kysely
|
|
76
|
+
.selectFrom('book')
|
|
77
|
+
.select('ownerId')
|
|
78
|
+
.where('bookId', '=', bookId)
|
|
79
|
+
.executeTakeFirst()
|
|
80
|
+
return book?.ownerId === session?.userId
|
|
81
|
+
},
|
|
99
82
|
)
|
|
100
83
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return await db.hasAccess(session?.userId, bookId)
|
|
104
|
-
}
|
|
105
|
-
)
|
|
84
|
+
// Session-only: no input needed. Use for role and flag gates.
|
|
85
|
+
export const isAdmin = pikkuAuth(async (_services, session) => session?.role === 'admin')
|
|
106
86
|
```
|
|
107
87
|
|
|
108
|
-
## OR
|
|
88
|
+
## OR and AND
|
|
109
89
|
|
|
110
90
|
```typescript
|
|
111
91
|
permissions: {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
92
|
+
owner: isBookOwner, // OR — an owner may
|
|
93
|
+
admin: isAdmin, // OR — an admin may
|
|
94
|
+
editor: [isAdmin, isBookOwner] // AND — both, inside one group
|
|
115
95
|
}
|
|
116
|
-
// Logic: verified OR owner OR (isVerified AND hasBookAccess)
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Groups are OR'd. Entries within a group array are AND'd.
|
|
120
|
-
|
|
121
|
-
## Where to Apply Permissions
|
|
122
|
-
|
|
123
|
-
### Per-Function (preferred)
|
|
124
|
-
|
|
125
|
-
```typescript
|
|
126
|
-
export const deleteBook = pikkuFunc({
|
|
127
|
-
func: async ({ db }, { bookId }) => {
|
|
128
|
-
await db.deleteBook(bookId)
|
|
129
|
-
},
|
|
130
|
-
permissions: {
|
|
131
|
-
verified: isVerified,
|
|
132
|
-
owner: isBookOwner,
|
|
133
|
-
},
|
|
134
|
-
})
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Global (`addGlobalPermission`) — App-Wide AND Gate
|
|
138
|
-
|
|
139
|
-
A global permission is an app-wide baseline that **every** function must additionally pass. It is an independent AND gate: it can only ever _narrow_ access — it never grants access a function's own `permissions` would deny.
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
import { addGlobalPermission } from '#pikku/auth'
|
|
143
|
-
|
|
144
|
-
addGlobalPermission([isEmployee]) // every function now also requires an employee session
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
Multiple `addGlobalPermission` calls accumulate and are AND'd together.
|
|
148
|
-
|
|
149
|
-
> Wire-, tag-, and HTTP-route-level permissions (`addHTTPPermission`, `addTagPermission`, and a `permissions` field on HTTP/channel/MCP wirings) were **removed in #972**. Permissions now live only on the function definition, plus the optional global gate. Tags are organizational only — use tag/HTTP _middleware_ (`addTagMiddleware`, `addHTTPMiddleware`) for cross-cutting request handling, not authorization.
|
|
150
|
-
|
|
151
|
-
## Scopes — the AND Gate Above Permissions
|
|
152
|
-
|
|
153
|
-
Scopes answer "what was this session granted?" before permissions ask "may this
|
|
154
|
-
user do this to this resource?". They are AND-ed: every scope listed must be
|
|
155
|
-
held. Because they are checked first and fail closed, a scope can only ever
|
|
156
|
-
_narrow_ access — it never grants what `permissions` would deny.
|
|
157
|
-
|
|
158
|
-
Declare the scope tree once with `defineScope`. The body is a no-op that
|
|
159
|
-
tree-shakes away; the CLI reads the call by AST and generates a `ScopeId` union,
|
|
160
|
-
so a function naming an undeclared scope fails the build rather than silently
|
|
161
|
-
gating on nothing.
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
// src/scopes.ts
|
|
165
|
-
import { defineScope } from '#pikku/scopes'
|
|
166
|
-
|
|
167
|
-
defineScope({
|
|
168
|
-
admin: {
|
|
169
|
-
displayName: 'Administration',
|
|
170
|
-
description: 'Administrative access',
|
|
171
|
-
scopes: {
|
|
172
|
-
invoices: {
|
|
173
|
-
description: 'Invoice management',
|
|
174
|
-
scopes: {
|
|
175
|
-
create: { description: 'Create invoices' },
|
|
176
|
-
void: { description: 'Void invoices' },
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
billing: {},
|
|
182
|
-
})
|
|
183
96
|
```
|
|
184
97
|
|
|
185
|
-
|
|
186
|
-
`admin:invoices`, `admin:invoices:create`, `admin:invoices:void` and `billing`.
|
|
187
|
-
Scopes may be declared across more than one file — the declarations merge.
|
|
98
|
+
Groups are OR'd; entries inside a group array are AND'd.
|
|
188
99
|
|
|
189
|
-
|
|
190
|
-
export const voidInvoice = pikkuFunc({
|
|
191
|
-
scopes: ['admin:invoices:void'],
|
|
192
|
-
permissions: { owner: isInvoiceOwner },
|
|
193
|
-
func: async ({ db }, { invoiceId }) => { ... },
|
|
194
|
-
})
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
A grant satisfies a required scope if it is the scope itself, an ancestor of it,
|
|
198
|
-
or a wildcard at any level — so a session holding `admin` satisfies
|
|
199
|
-
`admin:invoices:void`, and `admin:*` does too. A missing scope throws
|
|
200
|
-
`MissingScopeError` naming the first one that failed.
|
|
201
|
-
|
|
202
|
-
`scopes` requires a session and so is unavailable on `pikkuSessionlessFunc`:
|
|
203
|
-
scopes fail closed, an anonymous caller holds none, and a sessionless function
|
|
204
|
-
with scopes would reject every caller it exists to serve. Gate those with
|
|
205
|
-
`permissions`, which receive the optional session and may pass anonymous.
|
|
100
|
+
## Roles
|
|
206
101
|
|
|
207
|
-
|
|
102
|
+
If the app has roles, the role lives on the session (see pikku-auth / `mapSession`) and
|
|
103
|
+
every mutating or admin-only function names it in `permissions`. Gate the FUNCTION — hiding
|
|
104
|
+
an admin button in the UI is UX, never enforcement, and a member who guesses the RPC name
|
|
105
|
+
gets straight through if the function itself is open.
|
|
208
106
|
|
|
209
|
-
|
|
107
|
+
## Scopes
|
|
210
108
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
109
|
+
`scopes: ['admin:invoices:void']` is an AND gate checked BEFORE permissions and before
|
|
110
|
+
input validation. Declare the tree once with `defineScope`; a function naming an
|
|
111
|
+
undeclared scope fails codegen rather than gating on nothing. A grant satisfies a scope if
|
|
112
|
+
it is that scope, an ancestor, or a wildcard — a session holding `admin` satisfies
|
|
113
|
+
`admin:invoices:void`. Most apps need roles, not scopes; reach for these only when the
|
|
114
|
+
plan asked for granular grants.
|
|
214
115
|
|
|
215
|
-
The
|
|
116
|
+
## The one sanctioned exception
|
|
216
117
|
|
|
217
|
-
|
|
118
|
+
`permissionsInBody: true` — for a check that genuinely cannot be a permission because the
|
|
119
|
+
identity arrives in the payload and there is no session: a webhook signature, a signed
|
|
120
|
+
token, an invite code. It is purely declarative and enforces nothing; its job is to tell
|
|
121
|
+
the auditor the openness is deliberate. Anything expressible as a permission must be one.
|
|
218
122
|
|
|
219
|
-
|
|
220
|
-
signature, a signed token, or an invite code, where the "identity" arrives in the
|
|
221
|
-
payload and there is no session to check. For those, declare
|
|
222
|
-
`permissionsInBody: true` on the function and keep the check in the body.
|
|
123
|
+
## After changes
|
|
223
124
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
permissionsInBody: true,
|
|
227
|
-
auth: false,
|
|
228
|
-
func: async ({ stripe }, data, { http }) => {
|
|
229
|
-
stripe.webhooks.constructEvent(
|
|
230
|
-
data.raw,
|
|
231
|
-
http.request.header('stripe-signature'),
|
|
232
|
-
secret
|
|
233
|
-
)
|
|
234
|
-
// ...
|
|
235
|
-
},
|
|
236
|
-
})
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
This is a last resort, and it is purely declarative — it grants nothing and
|
|
240
|
-
enforces nothing. Its only job is to tell the auditor that this function's
|
|
241
|
-
apparent openness is deliberate, so asserting it falsely disables the very check
|
|
242
|
-
that would have caught the mistake. It requires `"allow": { "permissionsInBody": true }`
|
|
243
|
-
in `pikku.config.json`, which keeps the decision visible at the project level.
|
|
244
|
-
Prefer `permissions` whenever the check can be expressed as one — they are
|
|
245
|
-
declared, inspectable, and reusable.
|
|
246
|
-
|
|
247
|
-
## Complete Example
|
|
248
|
-
|
|
249
|
-
```typescript
|
|
250
|
-
// src/permissions.ts
|
|
251
|
-
import { pikkuAuth, pikkuPermission } from '#pikku/auth'
|
|
252
|
-
|
|
253
|
-
export const isVerified = pikkuAuth(
|
|
254
|
-
async (_services, session) => !!session?.emailVerified
|
|
255
|
-
)
|
|
256
|
-
|
|
257
|
-
export const isOrgMember = pikkuPermission(
|
|
258
|
-
async ({ db }, { orgId }, { session }) => {
|
|
259
|
-
return await db.isMember(session?.userId, orgId)
|
|
260
|
-
}
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
// src/functions/org.function.ts
|
|
264
|
-
export const deleteOrg = pikkuFunc({
|
|
265
|
-
func: async ({ db }, { orgId }) => {
|
|
266
|
-
await db.deleteOrg(orgId)
|
|
267
|
-
},
|
|
268
|
-
permissions: {
|
|
269
|
-
verified: isVerified,
|
|
270
|
-
owner: [isVerified, isOrgMember],
|
|
271
|
-
},
|
|
272
|
-
})
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
## After Changes
|
|
276
|
-
|
|
277
|
-
```bash
|
|
278
|
-
pikku all # regenerate if wirings changed
|
|
279
|
-
pikku all --tsc # regenerate, then verify permission checker types (fails on type errors)
|
|
280
|
-
```
|
|
125
|
+
`pikku all` — regenerates and typechecks the checkers. A permission whose signature is
|
|
126
|
+
wrong fails here, not at runtime.
|