@pikku/cli 0.12.26 → 0.12.28
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/cli.schema.json +1 -1
- package/console-app/assets/index-Ca6xJwNm.js +229 -0
- package/console-app/assets/{index-C52h1B_L.css → index-DwUzVI5k.css} +1 -1
- package/console-app/index.html +2 -2
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.js +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
- package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +192 -170
- package/dist/.pikku/function/pikku-functions.gen.js +3 -1
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
- package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.json +4 -0
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +11 -10
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
- package/dist/.pikku/schemas/register.gen.js +13 -13
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
- package/dist/.pikku/workflow/meta/allWorkflow.gen.json +8 -2
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
- package/dist/bin/pikku-bin.mjs +2 -2
- package/dist/src/fabric/functions/validate-core.js +6 -6
- package/dist/src/fabric/functions/validate.function.js +23 -7
- package/dist/src/functions/commands/tests-coverage.js +4 -2
- package/dist/src/functions/db/annotation-parser.d.ts +7 -7
- package/dist/src/functions/db/annotation-parser.js +61 -11
- package/dist/src/functions/db/db-codegen.d.ts +4 -0
- package/dist/src/functions/db/db-codegen.js +117 -15
- package/dist/src/functions/db/local-db.d.ts +6 -0
- package/dist/src/functions/db/local-db.js +134 -34
- package/dist/src/functions/db/postgres/postgres-introspector.d.ts +8 -2
- package/dist/src/functions/db/postgres/postgres-introspector.js +26 -14
- package/dist/src/functions/validate/workspace-validate.js +4 -1
- package/dist/src/functions/wirings/auth/pikku-command-auth.d.ts +1 -0
- package/dist/src/functions/wirings/auth/pikku-command-auth.js +22 -0
- package/dist/src/functions/wirings/auth/serialize-auth-gen.d.ts +1 -0
- package/dist/src/functions/wirings/auth/serialize-auth-gen.js +115 -0
- package/dist/src/functions/workflows/all.workflow.js +1 -0
- package/dist/src/scaffold/rpc-remote.gen.js +1 -1
- package/dist/src/utils/pikku-cli-config.js +3 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -4
- package/skills/pikku-auth-js/SKILL.md +137 -117
- package/skills/pikku-middleware/SKILL.md +283 -0
- package/skills/pikku-permissions/SKILL.md +165 -0
- package/skills/pikku-security/SKILL.md +38 -177
- package/skills/pikku-services/SKILL.md +44 -7
- package/skills/pikku-tag-middleware/SKILL.md +13 -0
- package/console-app/assets/index-Ba9K10XZ.js +0 -232
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pikku-permissions
|
|
3
|
+
description: 'Use when adding authorization checks to Pikku functions or routes — pikkuPermission, pikkuAuth, per-function permissions, pattern-based permissions, or understanding OR/AND permission logic.
|
|
4
|
+
TRIGGER when: user wants to restrict who can call a function, check resource ownership, add role-based access, or understand where permission checks belong.
|
|
5
|
+
DO NOT TRIGGER when: user asks about middleware or request interception (use pikku-middleware), authentication strategies (use pikku-security), or session management.'
|
|
6
|
+
installGroups: [core]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Pikku Permissions
|
|
10
|
+
|
|
11
|
+
## The Rule
|
|
12
|
+
|
|
13
|
+
**ALWAYS put authorization checks in the `permissions` field of `pikkuFunc` or `pikkuSessionlessFunc` — NEVER inside the `func` body.**
|
|
14
|
+
|
|
15
|
+
This includes: org access checks, repo access checks, role checks, resource ownership, and any other authorization logic. The `permissions` field runs before `func`, is visible to the inspector, and is the only place Pikku enforces authorization.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// CORRECT
|
|
19
|
+
export const deleteBook = pikkuFunc({
|
|
20
|
+
func: async ({ db }, { bookId }) => {
|
|
21
|
+
await db.deleteBook(bookId)
|
|
22
|
+
},
|
|
23
|
+
permissions: {
|
|
24
|
+
owner: isBookOwner, // ← authorization here
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// WRONG — permission check inside func body
|
|
29
|
+
export const deleteBook = pikkuFunc({
|
|
30
|
+
func: async ({ db }, { bookId }, { session }) => {
|
|
31
|
+
if (!session) throw new UnauthorizedError() // ← never do this
|
|
32
|
+
await db.deleteBook(bookId)
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Agent Operating Procedure
|
|
38
|
+
|
|
39
|
+
1. Discover before editing. Run `pikku info permissions --verbose` and `pikku info functions --verbose` to understand what permissions are already defined and applied.
|
|
40
|
+
2. Define permission checkers in a `src/permissions.ts` or domain-specific `src/lib/*-permissions.ts` file.
|
|
41
|
+
3. Apply them via the `permissions` field on the function, or via `addHTTPPermission` / `addPermission` for pattern/tag-based application.
|
|
42
|
+
4. Validate: run `pikku tsc` to confirm permission checker signatures are correct.
|
|
43
|
+
|
|
44
|
+
## Permission Factories
|
|
45
|
+
|
|
46
|
+
### `pikkuAuth(fn)` — Session-Only Checks
|
|
47
|
+
|
|
48
|
+
Use for checks that only need the session — no request data required.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { pikkuAuth } from '#pikku'
|
|
52
|
+
|
|
53
|
+
export const isAuthenticated = pikkuAuth(
|
|
54
|
+
async (_services, session) => !!session
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
export const isAdmin = pikkuAuth(
|
|
58
|
+
async (_services, session) => session?.role === 'admin'
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### `pikkuPermission(fn)` — Data-Aware Checks
|
|
63
|
+
|
|
64
|
+
Use when authorization depends on the actual request data (e.g., resource ownership).
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { pikkuPermission } from '#pikku'
|
|
68
|
+
|
|
69
|
+
export const isBookOwner = pikkuPermission(
|
|
70
|
+
async ({ db }, { bookId }, { session }) => {
|
|
71
|
+
const book = await db.getBook(bookId)
|
|
72
|
+
return book?.authorId === session?.userId
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
export const hasBookAccess = pikkuPermission(
|
|
77
|
+
async ({ db }, { bookId }, { session }) => {
|
|
78
|
+
return await db.hasAccess(session?.userId, bookId)
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## OR / AND Logic
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
permissions: {
|
|
87
|
+
admin: isAdmin, // OR: admins can access
|
|
88
|
+
owner: isBookOwner, // OR: owners can access
|
|
89
|
+
reviewer: [isAuthenticated, hasBookAccess], // AND: both must pass
|
|
90
|
+
}
|
|
91
|
+
// Logic: admin OR owner OR (isAuthenticated AND hasBookAccess)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Groups are OR'd. Entries within a group array are AND'd.
|
|
95
|
+
|
|
96
|
+
## Where to Apply Permissions
|
|
97
|
+
|
|
98
|
+
### Per-Function (preferred)
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
export const deleteBook = pikkuFunc({
|
|
102
|
+
func: async ({ db }, { bookId }) => {
|
|
103
|
+
await db.deleteBook(bookId)
|
|
104
|
+
},
|
|
105
|
+
permissions: {
|
|
106
|
+
admin: isAdmin,
|
|
107
|
+
owner: isBookOwner,
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Pattern-Based (`addHTTPPermission`)
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { addHTTPPermission } from '@pikku/core/http'
|
|
116
|
+
|
|
117
|
+
addHTTPPermission('/admin/*', { admin: isAdmin })
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Tag-Based (`addPermission`)
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { addPermission } from '.pikku/pikku-types.gen.js'
|
|
124
|
+
|
|
125
|
+
addPermission('internal', { machine: isMachineAgent })
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Complete Example
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// src/permissions.ts
|
|
132
|
+
import { pikkuAuth, pikkuPermission } from '#pikku'
|
|
133
|
+
|
|
134
|
+
export const isAuthenticated = pikkuAuth(
|
|
135
|
+
async (_services, session) => !!session
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
export const isAdmin = pikkuAuth(
|
|
139
|
+
async (_services, session) => session?.role === 'admin'
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
export const isOrgMember = pikkuPermission(
|
|
143
|
+
async ({ db }, { orgId }, { session }) => {
|
|
144
|
+
return await db.isMember(session?.userId, orgId)
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
// src/functions/org.function.ts
|
|
149
|
+
export const deleteOrg = pikkuFunc({
|
|
150
|
+
func: async ({ db }, { orgId }) => {
|
|
151
|
+
await db.deleteOrg(orgId)
|
|
152
|
+
},
|
|
153
|
+
permissions: {
|
|
154
|
+
admin: isAdmin,
|
|
155
|
+
owner: [isAuthenticated, isOrgMember],
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## After Changes
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
pikku tsc # verify permission checker types are correct
|
|
164
|
+
pikku all # regenerate if wirings changed
|
|
165
|
+
```
|
|
@@ -1,43 +1,26 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-security
|
|
3
|
-
description: 'Use when adding authentication
|
|
4
|
-
TRIGGER when:
|
|
5
|
-
DO NOT TRIGGER when: user asks about
|
|
3
|
+
description: 'Use when adding authentication or session management to a Pikku app — pikkuAuth, session lifecycle (setSession/clearSession), built-in auth strategies (authBearer, authCookie, authAPIKey), or JWT setup.
|
|
4
|
+
TRIGGER when: user asks about login, logout, session, bearer tokens, cookie auth, API keys, or JWT.
|
|
5
|
+
DO NOT TRIGGER when: user asks about middleware (use pikku-middleware), permissions/authorization checks (use pikku-permissions), or secrets/env vars (use pikku-config).'
|
|
6
6
|
installGroups: [core]
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
# Pikku Security (
|
|
9
|
+
# Pikku Security (Authentication & Sessions)
|
|
10
10
|
|
|
11
11
|
## Agent Operating Procedure
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
1. Discover before editing. Run `pikku info middleware --verbose` and `pikku info functions --verbose` to understand existing auth setup.
|
|
14
|
+
2. Auth strategies live in wirings files — do not put `addHTTPMiddleware` calls inside function bodies.
|
|
15
|
+
3. Validate with `pikku tsc` after changes; run `pikku all` if wirings changed.
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
|
|
18
|
-
4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
|
|
19
|
-
5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
|
|
17
|
+
For **middleware** (including tag middleware and service-to-service bearer auth) see `pikku-middleware`.
|
|
18
|
+
For **permissions** (pikkuPermission, pikkuAuth, per-function authorization) see `pikku-permissions`.
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## Before You Start
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
pikku info middleware --verbose # See existing middleware and where it's applied
|
|
27
|
-
pikku info permissions --verbose # See existing permissions
|
|
28
|
-
pikku info functions --verbose # See which functions have auth/permissions
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
See `pikku-concepts` for the core mental model.
|
|
32
|
-
|
|
33
|
-
## API Reference
|
|
34
|
-
|
|
35
|
-
### Session Management
|
|
36
|
-
|
|
37
|
-
Functions access session via the wire object:
|
|
20
|
+
## Session Management
|
|
38
21
|
|
|
39
22
|
```typescript
|
|
40
|
-
//
|
|
23
|
+
// Read session in pikkuFunc (session guaranteed to exist)
|
|
41
24
|
const getProfile = pikkuFunc({
|
|
42
25
|
func: async ({ db }, _data, { session }) => {
|
|
43
26
|
return await db.getUser(session.userId)
|
|
@@ -62,78 +45,19 @@ const logout = pikkuFunc({
|
|
|
62
45
|
})
|
|
63
46
|
```
|
|
64
47
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Use for authentication gates that only need the session (no request data).
|
|
68
|
-
|
|
69
|
-
```typescript
|
|
70
|
-
import { pikkuAuth } from '#pikku'
|
|
71
|
-
|
|
72
|
-
// Receives (services, session)
|
|
73
|
-
export const isAuthenticated = pikkuAuth(
|
|
74
|
-
async (_services, session) => !!session
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
export const isAdmin = pikkuAuth(
|
|
78
|
-
async (_services, session) => session?.role === 'admin'
|
|
79
|
-
)
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### `pikkuPermission(fn)` — Data-Aware Checks
|
|
83
|
-
|
|
84
|
-
Use when authorization depends on the actual request data.
|
|
85
|
-
|
|
86
|
-
```typescript
|
|
87
|
-
import { pikkuPermission } from '#pikku'
|
|
88
|
-
|
|
89
|
-
// Receives (services, data, wire)
|
|
90
|
-
export const isOwner = pikkuPermission(
|
|
91
|
-
async ({ db }, { bookId }, { session }) => {
|
|
92
|
-
const book = await db.getBook(bookId)
|
|
93
|
-
return book?.authorId === session?.userId
|
|
94
|
-
}
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
export const hasBookAccess = pikkuPermission(
|
|
98
|
-
async ({ db }, { bookId }, { session }) => {
|
|
99
|
-
return await db.hasAccess(session?.userId, bookId)
|
|
100
|
-
}
|
|
101
|
-
)
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Permission Groups (OR/AND Logic)
|
|
48
|
+
## Built-in Auth Strategies
|
|
105
49
|
|
|
106
|
-
|
|
107
|
-
{
|
|
108
|
-
admin: isAdmin, // OR: admins can access
|
|
109
|
-
owner: isOwner, // OR: owners can access
|
|
110
|
-
reviewer: [isAuthenticated, hasBookAccess], // AND: both must pass
|
|
111
|
-
}
|
|
112
|
-
// Logic: admin OR owner OR (isAuthenticated AND hasBookAccess)
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### `pikkuMiddleware(fn)` — Before/After Wrapping
|
|
116
|
-
|
|
117
|
-
```typescript
|
|
118
|
-
import { pikkuMiddleware } from '#pikku'
|
|
119
|
-
|
|
120
|
-
const logRequest = pikkuMiddleware(async ({ logger }, wire, next) => {
|
|
121
|
-
logger.info('Before')
|
|
122
|
-
await next()
|
|
123
|
-
logger.info('After')
|
|
124
|
-
})
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Built-in Auth Middleware
|
|
50
|
+
Apply these via `addHTTPMiddleware` in a wirings file:
|
|
128
51
|
|
|
129
52
|
```typescript
|
|
130
53
|
import { authBearer, authCookie, authAPIKey } from '@pikku/core/middleware'
|
|
54
|
+
import { addHTTPMiddleware } from '@pikku/core/http'
|
|
131
55
|
|
|
132
56
|
// JWT bearer token — reads Authorization header
|
|
133
|
-
addHTTPMiddleware([authBearer()])
|
|
57
|
+
addHTTPMiddleware('*', [authBearer()])
|
|
134
58
|
|
|
135
59
|
// Cookie-based sessions — auto-refreshes JWT
|
|
136
|
-
addHTTPMiddleware([
|
|
60
|
+
addHTTPMiddleware('*', [
|
|
137
61
|
authCookie({
|
|
138
62
|
name: 'session',
|
|
139
63
|
expiresIn: { value: 30, unit: 'day' },
|
|
@@ -141,48 +65,8 @@ addHTTPMiddleware([
|
|
|
141
65
|
}),
|
|
142
66
|
])
|
|
143
67
|
|
|
144
|
-
// API key — from x-api-key header or ?apiKey= query
|
|
145
|
-
addHTTPMiddleware([authAPIKey({ source: 'all' })])
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Scoping: Where to Apply
|
|
149
|
-
|
|
150
|
-
Four levels of scoping, from broadest to narrowest:
|
|
151
|
-
|
|
152
|
-
```typescript
|
|
153
|
-
// 1. Global: all HTTP routes
|
|
154
|
-
addHTTPMiddleware('*', [authBearer()])
|
|
155
|
-
|
|
156
|
-
// 2. Prefix-based: URL pattern
|
|
157
|
-
addHTTPMiddleware('/admin/*', [auditLog])
|
|
158
|
-
addHTTPPermission('/admin/*', { admin: requireAdmin })
|
|
159
|
-
|
|
160
|
-
// 3. Tag-based: any wiring with matching tag
|
|
161
|
-
addMiddleware('api', [rateLimiter])
|
|
162
|
-
addPermission('api', { auth: requireAuth })
|
|
163
|
-
|
|
164
|
-
// 4. Inline: per-wiring
|
|
165
|
-
wireHTTP({
|
|
166
|
-
route: '/books/:id',
|
|
167
|
-
func: getBook,
|
|
168
|
-
middleware: [cacheControl],
|
|
169
|
-
permissions: { owner: requireOwnership },
|
|
170
|
-
})
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### Per-Function Permissions
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
export const deleteBook = pikkuFunc({
|
|
177
|
-
func: async ({ db }, { bookId }) => {
|
|
178
|
-
await db.deleteBook(bookId)
|
|
179
|
-
},
|
|
180
|
-
permissions: {
|
|
181
|
-
admin: isAdmin,
|
|
182
|
-
owner: isOwner,
|
|
183
|
-
reviewer: [isAuthenticated, hasBookAccess],
|
|
184
|
-
},
|
|
185
|
-
})
|
|
68
|
+
// API key — from x-api-key header or ?apiKey= query param
|
|
69
|
+
addHTTPMiddleware('*', [authAPIKey({ source: 'all' })])
|
|
186
70
|
```
|
|
187
71
|
|
|
188
72
|
## Complete Example
|
|
@@ -191,53 +75,30 @@ export const deleteBook = pikkuFunc({
|
|
|
191
75
|
// permissions.ts
|
|
192
76
|
import { pikkuAuth, pikkuPermission } from '#pikku'
|
|
193
77
|
|
|
194
|
-
export const isAuthenticated = pikkuAuth(
|
|
195
|
-
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
export const isAdmin = pikkuAuth(
|
|
199
|
-
async (_services, session) => session?.role === 'admin'
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
export const isBookOwner = pikkuPermission(
|
|
203
|
-
async ({ db }, { bookId }, { session }) => {
|
|
204
|
-
const book = await db.getBook(bookId)
|
|
205
|
-
return book?.authorId === session?.userId
|
|
206
|
-
}
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
// middleware.ts
|
|
210
|
-
import { pikkuMiddleware } from '#pikku'
|
|
211
|
-
|
|
212
|
-
export const auditLog = pikkuMiddleware(async ({ logger, db }, wire, next) => {
|
|
213
|
-
const start = Date.now()
|
|
214
|
-
await next()
|
|
215
|
-
await db.createAuditLog({
|
|
216
|
-
duration: Date.now() - start,
|
|
217
|
-
})
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
// wirings/security.wiring.ts
|
|
221
|
-
import { authBearer } from '@pikku/core/middleware'
|
|
222
|
-
import { addHTTPMiddleware, addHTTPPermission } from '#pikku'
|
|
78
|
+
export const isAuthenticated = pikkuAuth(async (_services, session) => !!session)
|
|
79
|
+
export const isAdmin = pikkuAuth(async (_services, session) => session?.role === 'admin')
|
|
223
80
|
|
|
224
|
-
//
|
|
225
|
-
|
|
81
|
+
// wirings/auth.wiring.ts
|
|
82
|
+
import { authCookie } from '@pikku/core/middleware'
|
|
83
|
+
import { addHTTPMiddleware } from '@pikku/core/http'
|
|
226
84
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
85
|
+
addHTTPMiddleware('*', [
|
|
86
|
+
authCookie({ name: 'session', expiresIn: { value: 30, unit: 'day' } }),
|
|
87
|
+
])
|
|
230
88
|
|
|
231
|
-
// functions/
|
|
232
|
-
export const
|
|
233
|
-
|
|
234
|
-
func: async ({ db }, {
|
|
235
|
-
await db.
|
|
236
|
-
|
|
89
|
+
// functions/auth.functions.ts
|
|
90
|
+
export const login = pikkuFunc({
|
|
91
|
+
auth: false,
|
|
92
|
+
func: async ({ jwt, db }, { email, password }, { setSession }) => {
|
|
93
|
+
const user = await db.verifyCredentials(email, password)
|
|
94
|
+
setSession({ userId: user.id, role: user.role })
|
|
95
|
+
return { token: jwt.sign({ userId: user.id }) }
|
|
237
96
|
},
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
export const logout = pikkuFunc({
|
|
100
|
+
func: async ({}, _data, { clearSession }) => {
|
|
101
|
+
clearSession()
|
|
241
102
|
},
|
|
242
103
|
})
|
|
243
104
|
```
|
|
@@ -173,15 +173,52 @@ const createSingletonServices = pikkuServices(async (config) => {
|
|
|
173
173
|
})
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
+
### Audit Wire Service
|
|
177
|
+
|
|
178
|
+
`createInvocationAudit` creates a per-request `InvocationAuditLog` that buffers audit events in memory and flushes them as a batch when the function-runner calls `closeWireServices` at the end of the request. If `singletonServices.audit` is not configured (local dev without Fabric), it returns a no-op `DisabledInvocationAudit` — no crash, events are silently dropped.
|
|
179
|
+
|
|
180
|
+
Pair with `createAuditedKysely` to auto-capture every Kysely query as an audit event.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// services.ts
|
|
184
|
+
import { createInvocationAudit } from '@pikku/core/services'
|
|
185
|
+
import { createAuditedKysely } from '@pikku/kysely'
|
|
186
|
+
|
|
187
|
+
export const createWireServices = pikkuWireServices(async (singletonServices, wire) => {
|
|
188
|
+
const audit = createInvocationAudit(singletonServices.audit, wire)
|
|
189
|
+
const kysely = singletonServices.kysely
|
|
190
|
+
? createAuditedKysely(singletonServices.kysely, { audit })
|
|
191
|
+
: undefined
|
|
192
|
+
return { audit, ...(kysely ? { kysely } : {}) }
|
|
193
|
+
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The `audit` wire service is typed as `AuditLog` (from `@pikku/core`). Functions that emit custom events use it directly:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
const deleteUser = pikkuFunc({
|
|
200
|
+
func: async ({ audit }, { userId }) => {
|
|
201
|
+
await audit.audit({ type: 'user.deleted', actor_user_id: userId })
|
|
202
|
+
// ...
|
|
203
|
+
},
|
|
204
|
+
})
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`closeWireServices` (called automatically by the function-runner) invokes `audit.close()` → `singletonServices.audit.write(batch)` → platform-specific flush (e.g. CF Queue, libsql INSERT). No manual flushing needed.
|
|
208
|
+
|
|
209
|
+
> **Fabric note:** Fabric provisions the audit queue and consumer worker automatically. The audit table schema is in `db/sqlite/0003-audit.sql` (starter-template). Run `pikku fabric validate` to confirm the migration is in place.
|
|
210
|
+
|
|
176
211
|
### Built-in Services
|
|
177
212
|
|
|
178
|
-
| Service
|
|
179
|
-
|
|
|
180
|
-
| `ConsoleLogger`
|
|
181
|
-
| `JoseJWTService`
|
|
182
|
-
| `LocalSecretService`
|
|
183
|
-
| `LocalVariablesService`
|
|
184
|
-
| `PinoLogger`
|
|
213
|
+
| Service | Package | Purpose |
|
|
214
|
+
| -------------------------- | ---------------------- | -------------------------------- |
|
|
215
|
+
| `ConsoleLogger` | `@pikku/core/services` | Console-based logging |
|
|
216
|
+
| `JoseJWTService` | `@pikku/jose` | JWT sign/verify via jose |
|
|
217
|
+
| `LocalSecretService` | `@pikku/core/services` | Local development secrets |
|
|
218
|
+
| `LocalVariablesService` | `@pikku/core/services` | Local environment variables |
|
|
219
|
+
| `PinoLogger` | `@pikku/pino` | Structured logging via Pino |
|
|
220
|
+
| `createInvocationAudit` | `@pikku/core/services` | Per-request audit buffer |
|
|
221
|
+
| `createAuditedKysely` | `@pikku/kysely` | Auto-capture DB queries as audit events |
|
|
185
222
|
|
|
186
223
|
## Complete Example
|
|
187
224
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pikku-tag-middleware
|
|
3
|
+
description: 'Deprecated — use pikku-middleware instead. Tag middleware (addTagMiddleware) is now documented as a section within the pikku-middleware skill, alongside global HTTP middleware, execution order, and the service-to-service bearer auth pattern.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Deprecated: use `pikku-middleware`
|
|
7
|
+
|
|
8
|
+
Tag middleware is covered in the **`pikku-middleware`** skill, which also covers:
|
|
9
|
+
- `addHTTPMiddleware` (global / prefix-based)
|
|
10
|
+
- `addTagMiddleware` (tag-scoped)
|
|
11
|
+
- Middleware execution order and priority
|
|
12
|
+
- Service-to-service bearer auth pattern
|
|
13
|
+
- Session-setting middleware pattern
|