@pikku/core 0.9.11 → 0.10.0
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 +14 -0
- package/dist/errors/errors.d.ts +2 -0
- package/dist/errors/errors.js +6 -0
- package/dist/function/function-runner.d.ts +15 -9
- package/dist/function/function-runner.js +55 -37
- package/dist/function/functions.types.d.ts +67 -1
- package/dist/function/functions.types.js +43 -1
- package/dist/index.d.ts +2 -6
- package/dist/index.js +2 -6
- package/dist/middleware/auth-apikey.d.ts +19 -11
- package/dist/middleware/auth-apikey.js +33 -33
- package/dist/middleware/auth-bearer.d.ts +34 -7
- package/dist/middleware/auth-bearer.js +53 -36
- package/dist/middleware/auth-cookie.d.ts +30 -12
- package/dist/middleware/auth-cookie.js +51 -43
- package/dist/middleware/timeout.d.ts +6 -2
- package/dist/middleware/timeout.js +7 -9
- package/dist/middleware-runner.d.ts +46 -43
- package/dist/middleware-runner.js +100 -69
- package/dist/permissions.d.ts +82 -20
- package/dist/permissions.js +176 -62
- package/dist/pikku-state.d.ts +46 -1
- package/dist/pikku-state.js +16 -1
- package/dist/services/content-service.d.ts +2 -3
- package/dist/services/index.d.ts +0 -1
- package/dist/services/index.js +0 -1
- package/dist/services/local-content.d.ts +2 -4
- package/dist/types/core.types.d.ts +110 -4
- package/dist/types/core.types.js +44 -1
- package/dist/utils.d.ts +1 -1
- package/dist/wirings/channel/channel-handler.d.ts +2 -2
- package/dist/wirings/channel/channel-handler.js +16 -12
- package/dist/wirings/channel/channel-runner.d.ts +2 -2
- package/dist/wirings/channel/channel-runner.js +6 -24
- package/dist/wirings/channel/channel.types.d.ts +15 -14
- package/dist/wirings/channel/local/local-channel-runner.js +12 -12
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +7 -8
- package/dist/wirings/cli/channel/cli-channel-runner.d.ts +12 -0
- package/dist/wirings/cli/channel/cli-channel-runner.js +83 -0
- package/dist/wirings/cli/channel/index.d.ts +1 -0
- package/dist/wirings/cli/channel/index.js +1 -0
- package/dist/wirings/cli/cli-runner.d.ts +42 -0
- package/dist/wirings/cli/cli-runner.js +352 -0
- package/dist/wirings/cli/cli.types.d.ts +204 -0
- package/dist/wirings/cli/cli.types.js +1 -0
- package/dist/wirings/cli/command-parser.d.ts +19 -0
- package/dist/wirings/cli/command-parser.js +403 -0
- package/dist/wirings/cli/index.d.ts +5 -0
- package/dist/wirings/cli/index.js +5 -0
- package/dist/wirings/http/http-runner.d.ts +59 -10
- package/dist/wirings/http/http-runner.js +138 -131
- package/dist/wirings/http/http.types.d.ts +11 -10
- package/dist/wirings/http/index.d.ts +1 -1
- package/dist/wirings/http/index.js +1 -1
- package/dist/wirings/http/pikku-fetch-http-response.js +3 -1
- package/dist/wirings/http/routers/http-router.d.ts +0 -2
- package/dist/wirings/http/routers/path-to-regex.d.ts +1 -1
- package/dist/wirings/http/routers/path-to-regex.js +5 -34
- package/dist/wirings/mcp/mcp-endpoint-registry.d.ts +1 -1
- package/dist/wirings/mcp/mcp-runner.d.ts +4 -5
- package/dist/wirings/mcp/mcp-runner.js +32 -34
- package/dist/wirings/mcp/mcp.types.d.ts +38 -14
- package/dist/wirings/queue/queue-runner.d.ts +2 -2
- package/dist/wirings/queue/queue-runner.js +25 -27
- package/dist/wirings/queue/queue.types.d.ts +6 -5
- package/dist/wirings/rpc/index.d.ts +1 -1
- package/dist/wirings/rpc/index.js +1 -1
- package/dist/wirings/rpc/rpc-runner.d.ts +6 -18
- package/dist/wirings/rpc/rpc-runner.js +13 -8
- package/dist/wirings/scheduler/scheduler-runner.d.ts +2 -2
- package/dist/wirings/scheduler/scheduler-runner.js +38 -35
- package/dist/wirings/scheduler/scheduler.types.d.ts +5 -4
- package/package.json +9 -7
- package/src/errors/errors.ts +6 -0
- package/src/factory-functions.test.ts +60 -1
- package/src/function/function-runner.test.ts +81 -26
- package/src/function/function-runner.ts +89 -57
- package/src/function/functions.types.ts +85 -2
- package/src/index.ts +6 -19
- package/src/middleware/auth-apikey.ts +42 -57
- package/src/middleware/auth-bearer.ts +66 -49
- package/src/middleware/auth-cookie.ts +63 -82
- package/src/middleware/timeout.ts +10 -14
- package/src/middleware-runner.test.ts +24 -16
- package/src/middleware-runner.ts +115 -87
- package/src/permissions.test.ts +26 -27
- package/src/permissions.ts +216 -104
- package/src/pikku-state.ts +79 -2
- package/src/services/content-service.ts +5 -4
- package/src/services/index.ts +0 -1
- package/src/services/local-content.ts +9 -4
- package/src/types/core.types.ts +143 -5
- package/src/utils.ts +1 -1
- package/src/wirings/channel/channel-handler.ts +18 -21
- package/src/wirings/channel/channel-runner.ts +19 -32
- package/src/wirings/channel/channel.types.ts +29 -16
- package/src/wirings/channel/local/local-channel-runner.ts +25 -15
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +15 -17
- package/src/wirings/cli/channel/cli-channel-runner.ts +123 -0
- package/src/wirings/cli/channel/index.ts +1 -0
- package/src/wirings/cli/cli-runner.test.ts +403 -0
- package/src/wirings/cli/cli-runner.ts +539 -0
- package/src/wirings/cli/cli.types.ts +350 -0
- package/src/wirings/cli/command-parser.test.ts +445 -0
- package/src/wirings/cli/command-parser.ts +504 -0
- package/src/wirings/cli/index.ts +13 -0
- package/src/wirings/http/http-runner.test.ts +8 -7
- package/src/wirings/http/http-runner.ts +165 -158
- package/src/wirings/http/http.types.ts +58 -11
- package/src/wirings/http/index.ts +7 -1
- package/src/wirings/http/pikku-fetch-http-response.ts +3 -1
- package/src/wirings/http/routers/http-router.ts +0 -2
- package/src/wirings/http/routers/path-to-regex.test.ts +4 -5
- package/src/wirings/http/routers/path-to-regex.ts +6 -43
- package/src/wirings/mcp/mcp-endpoint-registry.ts +1 -1
- package/src/wirings/mcp/mcp-runner.ts +58 -55
- package/src/wirings/mcp/mcp.types.ts +65 -14
- package/src/wirings/queue/queue-runner.ts +44 -47
- package/src/wirings/queue/queue.types.ts +10 -6
- package/src/wirings/rpc/index.ts +1 -1
- package/src/wirings/rpc/rpc-runner.ts +27 -9
- package/src/wirings/scheduler/scheduler-runner.ts +58 -56
- package/src/wirings/scheduler/scheduler.types.ts +10 -3
- package/tsconfig.tsbuildinfo +1 -1
package/src/permissions.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
CoreServices,
|
|
3
3
|
CoreUserSession,
|
|
4
4
|
PikkuWiringTypes,
|
|
5
|
+
PermissionMetadata,
|
|
5
6
|
} from './types/core.types.js'
|
|
6
7
|
import {
|
|
7
8
|
CorePermissionGroup,
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
} from './function/functions.types.js'
|
|
10
11
|
import { pikkuState } from './pikku-state.js'
|
|
11
12
|
import { ForbiddenError } from './errors/errors.js'
|
|
13
|
+
import { freezeDedupe } from './utils.js'
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* This function validates permissions by iterating over permission groups and executing the corresponding permission functions. If all functions in at least one group return true, the permission is considered valid.
|
|
@@ -51,43 +53,93 @@ export const verifyPermissions = async (
|
|
|
51
53
|
return false
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Registers a single permission function by name in the global permission store.
|
|
58
|
+
*
|
|
59
|
+
* This function is used by CLI-generated code to register permission functions
|
|
60
|
+
* that can be referenced by name in metadata. It stores permissions in a special
|
|
61
|
+
* namespace to avoid conflicts with tag-based permissions.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} name - The unique name (pikkuFuncName) of the permission function.
|
|
64
|
+
* @param {CorePikkuPermission} permission - The permission function to register.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* // Called by CLI-generated pikku-permissions.gen.ts
|
|
69
|
+
* registerPermission('adminPermission_src_permissions_ts_10_5', adminPermission)
|
|
70
|
+
* registerPermission('readPermission_src_permissions_ts_20_10', readPermission)
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export const registerPermission = (
|
|
74
|
+
name: string,
|
|
75
|
+
permission: CorePikkuPermission<any>
|
|
76
|
+
) => {
|
|
77
|
+
const permissionStore = pikkuState('misc', 'permissions')
|
|
78
|
+
permissionStore[name] = [permission]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Retrieves a registered permission function by its name.
|
|
83
|
+
*
|
|
84
|
+
* This function looks up permissions that was registered with registerPermission.
|
|
85
|
+
* It's used internally by the framework to resolve permission references in metadata.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} name - The unique name (pikkuFuncName) of the permission function.
|
|
88
|
+
* @returns {CorePikkuPermission | undefined} The permission function, or undefined if not found.
|
|
89
|
+
*
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
export const getPermissionByName = (
|
|
93
|
+
name: string
|
|
94
|
+
): CorePikkuPermission | undefined => {
|
|
95
|
+
const permissionStore = pikkuState('misc', 'permissions')
|
|
96
|
+
const permission = permissionStore[name]
|
|
97
|
+
if (Array.isArray(permission) && permission.length === 1) {
|
|
98
|
+
return permission[0]
|
|
99
|
+
}
|
|
100
|
+
return undefined
|
|
101
|
+
}
|
|
102
|
+
|
|
54
103
|
/**
|
|
55
104
|
* Adds global permissions for a specific tag.
|
|
56
105
|
*
|
|
57
106
|
* This function allows you to register permissions that will be applied to
|
|
58
107
|
* any wiring (HTTP, Channel, Queue, Scheduler, MCP) that includes the matching tag.
|
|
59
108
|
*
|
|
109
|
+
* For tree-shaking benefits, wrap in a factory function:
|
|
110
|
+
* `export const x = () => addPermission('tag', [...])`
|
|
111
|
+
*
|
|
60
112
|
* @param {string} tag - The tag that the permissions should apply to.
|
|
61
113
|
* @param {any[]} permissions - The permissions array to apply for the specified tag.
|
|
62
114
|
*
|
|
63
|
-
* @
|
|
115
|
+
* @returns {CorePermissionGroup | CorePikkuPermission[]} The permissions (for chaining/wrapping).
|
|
64
116
|
*
|
|
65
117
|
* @example
|
|
66
118
|
* ```typescript
|
|
67
|
-
* //
|
|
68
|
-
* addPermission('admin', [
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
119
|
+
* // Recommended: tree-shakeable
|
|
120
|
+
* export const adminPermissions = () => addPermission('admin', [
|
|
121
|
+
* adminPermission,
|
|
122
|
+
* rolePermission({ role: 'admin' })
|
|
123
|
+
* ])
|
|
72
124
|
*
|
|
73
|
-
* //
|
|
74
|
-
* addPermission('api', [
|
|
125
|
+
* // Also works: no tree-shaking
|
|
126
|
+
* export const apiPermissions = addPermission('api', [
|
|
127
|
+
* readPermission
|
|
128
|
+
* ])
|
|
75
129
|
* ```
|
|
76
130
|
*/
|
|
77
131
|
export const addPermission = (
|
|
78
132
|
tag: string,
|
|
79
133
|
permissions: CorePermissionGroup | CorePikkuPermission[]
|
|
80
|
-
) => {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
// Check if tag already exists
|
|
84
|
-
if (permissionsStore[tag]) {
|
|
134
|
+
): CorePermissionGroup | CorePikkuPermission[] => {
|
|
135
|
+
const tagGroups = pikkuState('permissions', 'tagGroup')
|
|
136
|
+
if (tagGroups[tag]) {
|
|
85
137
|
throw new Error(
|
|
86
138
|
`Permissions for tag '${tag}' already exist. Use a different tag or remove the existing permissions first.`
|
|
87
139
|
)
|
|
88
140
|
}
|
|
89
|
-
|
|
90
|
-
|
|
141
|
+
tagGroups[tag] = permissions
|
|
142
|
+
return permissions
|
|
91
143
|
}
|
|
92
144
|
|
|
93
145
|
const EMPTY: readonly (CorePermissionGroup | CorePikkuPermission)[] = []
|
|
@@ -114,7 +166,7 @@ export const getPermissionsForTags = (
|
|
|
114
166
|
return EMPTY
|
|
115
167
|
}
|
|
116
168
|
|
|
117
|
-
const permissionsStore = pikkuState('
|
|
169
|
+
const permissionsStore = pikkuState('permissions', 'tagGroup')
|
|
118
170
|
const applicablePermissions: Array<
|
|
119
171
|
CorePermissionGroup | CorePikkuPermission
|
|
120
172
|
> = []
|
|
@@ -134,17 +186,9 @@ export const getPermissionsForTags = (
|
|
|
134
186
|
return applicablePermissions
|
|
135
187
|
}
|
|
136
188
|
|
|
137
|
-
const
|
|
189
|
+
const combinedPermissionsCache: Record<
|
|
138
190
|
PikkuWiringTypes,
|
|
139
|
-
|
|
140
|
-
Record<
|
|
141
|
-
string,
|
|
142
|
-
{
|
|
143
|
-
wiringTags: readonly (CorePermissionGroup | CorePikkuPermission)[]
|
|
144
|
-
funcTags: readonly (CorePermissionGroup | CorePikkuPermission)[]
|
|
145
|
-
}
|
|
146
|
-
>
|
|
147
|
-
>
|
|
191
|
+
Record<string, readonly (CorePermissionGroup | CorePikkuPermission)[]>
|
|
148
192
|
> = {
|
|
149
193
|
[PikkuWiringTypes.http]: {},
|
|
150
194
|
[PikkuWiringTypes.rpc]: {},
|
|
@@ -152,114 +196,182 @@ const permissionCache: Record<
|
|
|
152
196
|
[PikkuWiringTypes.queue]: {},
|
|
153
197
|
[PikkuWiringTypes.scheduler]: {},
|
|
154
198
|
[PikkuWiringTypes.mcp]: {},
|
|
199
|
+
[PikkuWiringTypes.cli]: {},
|
|
155
200
|
}
|
|
156
201
|
|
|
157
202
|
/**
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
203
|
+
* Combines wiring-specific permissions with function-level permissions.
|
|
204
|
+
*
|
|
205
|
+
* This function resolves permission metadata into actual permission functions and combines them.
|
|
206
|
+
* It filters out wire permissions without tags from inheritedPermissions to avoid duplication
|
|
207
|
+
* (those are passed separately as wirePermissions).
|
|
208
|
+
*
|
|
209
|
+
* @param {object} options - Configuration object for combining permissions.
|
|
210
|
+
* @param {PermissionMetadata[] | undefined} options.wireInheritedPermissions - Metadata from wiring (HTTP + tags + wire with tags).
|
|
211
|
+
* @param {CorePermissionGroup | CorePikkuPermission[] | undefined} options.wirePermissions - Inline wire permissions.
|
|
212
|
+
* @param {PermissionMetadata[] | undefined} options.funcInheritedPermissions - Function permissions metadata (only tags).
|
|
213
|
+
* @param {CorePermissionGroup | CorePikkuPermission[] | undefined} options.funcPermissions - Inline function permissions.
|
|
214
|
+
* @returns {(CorePermissionGroup | CorePikkuPermission)[]} Combined array of resolved permissions.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```typescript
|
|
218
|
+
* const combined = combinePermissions(wireType, wireId, {
|
|
219
|
+
* wireInheritedPermissions: meta.permissions,
|
|
220
|
+
* wirePermissions: inlinePermissions,
|
|
221
|
+
* funcInheritedPermissions: funcMeta.permissions,
|
|
222
|
+
* funcPermissions: funcConfig.permissions
|
|
223
|
+
* })
|
|
224
|
+
* ```
|
|
163
225
|
*/
|
|
164
|
-
export const
|
|
226
|
+
export const combinePermissions = (
|
|
165
227
|
wireType: PikkuWiringTypes,
|
|
166
228
|
uid: string,
|
|
167
229
|
{
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
230
|
+
wireInheritedPermissions,
|
|
231
|
+
wirePermissions,
|
|
232
|
+
funcInheritedPermissions,
|
|
171
233
|
funcPermissions,
|
|
172
|
-
allServices,
|
|
173
|
-
data,
|
|
174
|
-
session,
|
|
175
234
|
}: {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
funcPermissions?: CorePermissionGroup
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
235
|
+
wireInheritedPermissions?: PermissionMetadata[]
|
|
236
|
+
wirePermissions?: CorePermissionGroup | CorePikkuPermission[]
|
|
237
|
+
funcInheritedPermissions?: PermissionMetadata[]
|
|
238
|
+
funcPermissions?: CorePermissionGroup | CorePikkuPermission[]
|
|
239
|
+
} = {}
|
|
240
|
+
): readonly (CorePermissionGroup | CorePikkuPermission)[] => {
|
|
241
|
+
if (combinedPermissionsCache[wireType][uid]) {
|
|
242
|
+
return combinedPermissionsCache[wireType][uid]
|
|
183
243
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
244
|
+
|
|
245
|
+
const resolved: (CorePermissionGroup | CorePikkuPermission)[] = []
|
|
246
|
+
|
|
247
|
+
// 1. Resolve wire inherited permissions (HTTP + tag groups + individual wire permissions)
|
|
248
|
+
if (wireInheritedPermissions) {
|
|
249
|
+
for (const meta of wireInheritedPermissions) {
|
|
250
|
+
if (meta.type === 'http') {
|
|
251
|
+
// Look up HTTP permission group from pikkuState
|
|
252
|
+
const group = pikkuState('permissions', 'httpGroup')[meta.route]
|
|
253
|
+
if (group) {
|
|
254
|
+
if (Array.isArray(group)) {
|
|
255
|
+
resolved.push(...group)
|
|
256
|
+
} else {
|
|
257
|
+
resolved.push(group)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} else if (meta.type === 'tag') {
|
|
261
|
+
// Look up tag permission group from pikkuState
|
|
262
|
+
const group = pikkuState('permissions', 'tagGroup')[meta.tag]
|
|
263
|
+
if (group) {
|
|
264
|
+
if (Array.isArray(group)) {
|
|
265
|
+
resolved.push(...group)
|
|
266
|
+
} else {
|
|
267
|
+
resolved.push(group)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} else if (meta.type === 'wire') {
|
|
271
|
+
// Individual wire permission (exported, not inline)
|
|
272
|
+
const permission = getPermissionByName(meta.name)
|
|
273
|
+
if (permission) {
|
|
274
|
+
resolved.push(permission)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
190
277
|
}
|
|
191
|
-
permissionCache[wireType][uid] = cachedPermission
|
|
192
278
|
}
|
|
193
|
-
let permissioned = true
|
|
194
279
|
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
typeof permissions === 'function' ? { permissions } : permissions,
|
|
202
|
-
allServices,
|
|
203
|
-
data,
|
|
204
|
-
session
|
|
205
|
-
)
|
|
206
|
-
if (result) {
|
|
207
|
-
permissioned = true
|
|
208
|
-
break // At least one passed, we're good at this level
|
|
209
|
-
}
|
|
280
|
+
// 2. Add inline wire permissions
|
|
281
|
+
if (wirePermissions) {
|
|
282
|
+
if (Array.isArray(wirePermissions)) {
|
|
283
|
+
resolved.push(...wirePermissions)
|
|
284
|
+
} else {
|
|
285
|
+
resolved.push(wirePermissions)
|
|
210
286
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 3. Resolve function inherited permissions (only tags, wire permissions already handled)
|
|
290
|
+
if (funcInheritedPermissions) {
|
|
291
|
+
for (const meta of funcInheritedPermissions) {
|
|
292
|
+
if (meta.type === 'tag') {
|
|
293
|
+
// Look up tag permission group from pikkuState
|
|
294
|
+
const group = pikkuState('permissions', 'tagGroup')[meta.tag]
|
|
295
|
+
if (group) {
|
|
296
|
+
if (Array.isArray(group)) {
|
|
297
|
+
resolved.push(...group)
|
|
298
|
+
} else {
|
|
299
|
+
resolved.push(group)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Note: wire permissions are already handled in wireInheritedPermissions
|
|
214
304
|
}
|
|
215
305
|
}
|
|
216
306
|
|
|
217
|
-
//
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
session
|
|
224
|
-
)
|
|
225
|
-
if (!permissioned) {
|
|
226
|
-
allServices.logger.debug('Permission denied - wiring permissions')
|
|
227
|
-
throw new ForbiddenError('Permission denied')
|
|
307
|
+
// 4. Add inline function permissions
|
|
308
|
+
if (funcPermissions) {
|
|
309
|
+
if (Array.isArray(funcPermissions)) {
|
|
310
|
+
resolved.push(...funcPermissions)
|
|
311
|
+
} else {
|
|
312
|
+
resolved.push(funcPermissions)
|
|
228
313
|
}
|
|
229
314
|
}
|
|
230
315
|
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
316
|
+
// Deduplicate and freeze
|
|
317
|
+
combinedPermissionsCache[wireType][uid] = freezeDedupe(resolved) as readonly (
|
|
318
|
+
| CorePermissionGroup
|
|
319
|
+
| CorePikkuPermission
|
|
320
|
+
)[]
|
|
321
|
+
|
|
322
|
+
return combinedPermissionsCache[wireType][uid]
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Runs permission checks using combined permissions from all sources.
|
|
327
|
+
* Combines permissions from wire and function levels, then validates them.
|
|
328
|
+
*/
|
|
329
|
+
export const runPermissions = async (
|
|
330
|
+
wireType: PikkuWiringTypes,
|
|
331
|
+
uid: string,
|
|
332
|
+
{
|
|
333
|
+
wireInheritedPermissions,
|
|
334
|
+
wirePermissions,
|
|
335
|
+
funcInheritedPermissions,
|
|
336
|
+
funcPermissions,
|
|
337
|
+
allServices,
|
|
338
|
+
data,
|
|
339
|
+
session,
|
|
340
|
+
}: {
|
|
341
|
+
wireInheritedPermissions?: PermissionMetadata[]
|
|
342
|
+
wirePermissions?: CorePermissionGroup | CorePikkuPermission[]
|
|
343
|
+
funcInheritedPermissions?: PermissionMetadata[]
|
|
344
|
+
funcPermissions?: CorePermissionGroup | CorePikkuPermission[]
|
|
345
|
+
allServices: CoreServices
|
|
346
|
+
data: any
|
|
347
|
+
session?: CoreUserSession
|
|
348
|
+
}
|
|
349
|
+
) => {
|
|
350
|
+
// Combine all permissions: wireInheritedPermissions → wirePermissions → funcInheritedPermissions → funcPermissions
|
|
351
|
+
const allPermissions = combinePermissions(wireType, uid, {
|
|
352
|
+
wireInheritedPermissions,
|
|
353
|
+
wirePermissions,
|
|
354
|
+
funcInheritedPermissions,
|
|
355
|
+
funcPermissions,
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
// Check all combined permissions - at least one must pass if any exist
|
|
359
|
+
if (allPermissions.length > 0) {
|
|
360
|
+
let permissioned = false
|
|
361
|
+
for (const permission of allPermissions) {
|
|
236
362
|
const result = await verifyPermissions(
|
|
237
|
-
typeof
|
|
363
|
+
typeof permission === 'function' ? { permission } : permission,
|
|
238
364
|
allServices,
|
|
239
365
|
data,
|
|
240
366
|
session
|
|
241
367
|
)
|
|
242
368
|
if (result) {
|
|
243
369
|
permissioned = true
|
|
244
|
-
|
|
370
|
+
// Continue executing all permissions (don't break early)
|
|
245
371
|
}
|
|
246
372
|
}
|
|
247
373
|
if (!permissioned) {
|
|
248
|
-
allServices.logger.debug('Permission denied -
|
|
249
|
-
throw new ForbiddenError('Permission denied')
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// 4. Function permissions - must pass if defined
|
|
254
|
-
if (funcPermissions) {
|
|
255
|
-
permissioned = await verifyPermissions(
|
|
256
|
-
funcPermissions,
|
|
257
|
-
allServices,
|
|
258
|
-
data,
|
|
259
|
-
session
|
|
260
|
-
)
|
|
261
|
-
if (!permissioned) {
|
|
262
|
-
allServices.logger.debug('Permission denied - function permissions')
|
|
374
|
+
allServices.logger.debug('Permission denied - combined permissions')
|
|
263
375
|
throw new ForbiddenError('Permission denied')
|
|
264
376
|
}
|
|
265
377
|
}
|
package/src/pikku-state.ts
CHANGED
|
@@ -4,7 +4,12 @@ import {
|
|
|
4
4
|
HTTPMethod,
|
|
5
5
|
HTTPWiringsMeta,
|
|
6
6
|
} from './wirings/http/http.types.js'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
FunctionsMeta,
|
|
9
|
+
CorePikkuMiddleware,
|
|
10
|
+
CorePikkuMiddlewareGroup,
|
|
11
|
+
FunctionServicesMeta,
|
|
12
|
+
} from './types/core.types.js'
|
|
8
13
|
import {
|
|
9
14
|
CoreScheduledTask,
|
|
10
15
|
ScheduledTasksMeta,
|
|
@@ -27,6 +32,7 @@ import {
|
|
|
27
32
|
MCPToolMeta,
|
|
28
33
|
MCPPromptMeta,
|
|
29
34
|
} from './wirings/mcp/mcp.types.js'
|
|
35
|
+
import { CLIMeta, CLIProgramState } from './wirings/cli/cli.types.js'
|
|
30
36
|
|
|
31
37
|
interface PikkuState {
|
|
32
38
|
function: {
|
|
@@ -69,6 +75,62 @@ interface PikkuState {
|
|
|
69
75
|
prompts: Map<string, CoreMCPPrompt>
|
|
70
76
|
promptsMeta: MCPPromptMeta
|
|
71
77
|
}
|
|
78
|
+
cli: {
|
|
79
|
+
meta: CLIMeta | Record<string, any> // Backward compatible with old published CLI format
|
|
80
|
+
programs: Record<string, CLIProgramState>
|
|
81
|
+
}
|
|
82
|
+
middleware: {
|
|
83
|
+
tagGroup: Record<string, CorePikkuMiddlewareGroup>
|
|
84
|
+
httpGroup: Record<string, CorePikkuMiddlewareGroup>
|
|
85
|
+
tagGroupMeta: Record<
|
|
86
|
+
string,
|
|
87
|
+
{
|
|
88
|
+
exportName: string | null
|
|
89
|
+
sourceFile: string
|
|
90
|
+
position: number
|
|
91
|
+
services: FunctionServicesMeta
|
|
92
|
+
middlewareCount: number
|
|
93
|
+
isFactory: boolean
|
|
94
|
+
}
|
|
95
|
+
>
|
|
96
|
+
httpGroupMeta: Record<
|
|
97
|
+
string,
|
|
98
|
+
{
|
|
99
|
+
exportName: string | null
|
|
100
|
+
sourceFile: string
|
|
101
|
+
position: number
|
|
102
|
+
services: FunctionServicesMeta
|
|
103
|
+
middlewareCount: number
|
|
104
|
+
isFactory: boolean
|
|
105
|
+
}
|
|
106
|
+
>
|
|
107
|
+
}
|
|
108
|
+
permissions: {
|
|
109
|
+
tagGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>
|
|
110
|
+
httpGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>
|
|
111
|
+
tagGroupMeta: Record<
|
|
112
|
+
string,
|
|
113
|
+
{
|
|
114
|
+
exportName: string | null
|
|
115
|
+
sourceFile: string
|
|
116
|
+
position: number
|
|
117
|
+
services: FunctionServicesMeta
|
|
118
|
+
permissionCount: number
|
|
119
|
+
isFactory: boolean
|
|
120
|
+
}
|
|
121
|
+
>
|
|
122
|
+
httpGroupMeta: Record<
|
|
123
|
+
string,
|
|
124
|
+
{
|
|
125
|
+
exportName: string | null
|
|
126
|
+
sourceFile: string
|
|
127
|
+
position: number
|
|
128
|
+
services: FunctionServicesMeta
|
|
129
|
+
permissionCount: number
|
|
130
|
+
isFactory: boolean
|
|
131
|
+
}
|
|
132
|
+
>
|
|
133
|
+
}
|
|
72
134
|
misc: {
|
|
73
135
|
errors: Map<PikkuError, ErrorDetails>
|
|
74
136
|
schemas: Map<string, any>
|
|
@@ -89,7 +151,6 @@ export const resetPikkuState = () => {
|
|
|
89
151
|
},
|
|
90
152
|
http: {
|
|
91
153
|
permissions: new Map(),
|
|
92
|
-
middleware: new Map(),
|
|
93
154
|
routes: new Map(),
|
|
94
155
|
meta: {},
|
|
95
156
|
},
|
|
@@ -113,6 +174,22 @@ export const resetPikkuState = () => {
|
|
|
113
174
|
prompts: new Map(),
|
|
114
175
|
promptsMeta: {} as MCPPromptMeta,
|
|
115
176
|
},
|
|
177
|
+
cli: {
|
|
178
|
+
meta: { programs: {}, renderers: {} },
|
|
179
|
+
programs: {},
|
|
180
|
+
},
|
|
181
|
+
middleware: {
|
|
182
|
+
tagGroup: {},
|
|
183
|
+
httpGroup: {},
|
|
184
|
+
tagGroupMeta: {},
|
|
185
|
+
httpGroupMeta: {},
|
|
186
|
+
},
|
|
187
|
+
permissions: {
|
|
188
|
+
tagGroup: {},
|
|
189
|
+
httpGroup: {},
|
|
190
|
+
tagGroupMeta: {},
|
|
191
|
+
httpGroupMeta: {},
|
|
192
|
+
},
|
|
116
193
|
misc: {
|
|
117
194
|
errors: globalThis.pikkuState?.misc?.errors || new Map(),
|
|
118
195
|
schemas: new Map(),
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { ReadStream } from 'fs'
|
|
2
|
-
|
|
3
1
|
export interface ContentService {
|
|
4
2
|
/**
|
|
5
3
|
* Signs a content key to generate a secure, time-limited access URL.
|
|
@@ -49,7 +47,10 @@ export interface ContentService {
|
|
|
49
47
|
* @param stream - A readable stream of the file contents.
|
|
50
48
|
* @returns A boolean indicating success.
|
|
51
49
|
*/
|
|
52
|
-
writeFile(
|
|
50
|
+
writeFile(
|
|
51
|
+
assetKey: string,
|
|
52
|
+
stream: ReadableStream | NodeJS.ReadableStream
|
|
53
|
+
): Promise<boolean>
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* Copies a file from a local absolute path into storage under a new asset key.
|
|
@@ -64,5 +65,5 @@ export interface ContentService {
|
|
|
64
65
|
* @param assetKey - The key of the file to read.
|
|
65
66
|
* @returns A readable file stream.
|
|
66
67
|
*/
|
|
67
|
-
readFile(assetKey: string): Promise<
|
|
68
|
+
readFile(assetKey: string): Promise<ReadableStream | NodeJS.ReadableStream>
|
|
68
69
|
}
|
package/src/services/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createReadStream, createWriteStream, promises
|
|
1
|
+
import { createReadStream, createWriteStream, promises } from 'fs'
|
|
2
2
|
import { mkdir } from 'fs/promises'
|
|
3
3
|
import { ContentService, Logger } from '@pikku/core/services'
|
|
4
4
|
import { pipeline } from 'stream/promises'
|
|
@@ -38,7 +38,10 @@ export class LocalContent implements ContentService {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
public async writeFile(
|
|
41
|
+
public async writeFile(
|
|
42
|
+
assetKey: string,
|
|
43
|
+
stream: ReadableStream | NodeJS.ReadableStream
|
|
44
|
+
): Promise<boolean> {
|
|
42
45
|
this.logger.debug(`Writing file: ${assetKey}`)
|
|
43
46
|
|
|
44
47
|
const path = `${this.config.localFileUploadPath}/${assetKey}`
|
|
@@ -47,7 +50,7 @@ export class LocalContent implements ContentService {
|
|
|
47
50
|
await this.createDirectoryForFile(path)
|
|
48
51
|
const fileStream = createWriteStream(path)
|
|
49
52
|
// Use pipeline to properly manage stream piping and errors
|
|
50
|
-
await pipeline(stream, fileStream)
|
|
53
|
+
await pipeline(stream as Readable, fileStream)
|
|
51
54
|
return true
|
|
52
55
|
} catch (e) {
|
|
53
56
|
console.error(e)
|
|
@@ -72,7 +75,9 @@ export class LocalContent implements ContentService {
|
|
|
72
75
|
return false
|
|
73
76
|
}
|
|
74
77
|
|
|
75
|
-
public async readFile(
|
|
78
|
+
public async readFile(
|
|
79
|
+
assetKey: string
|
|
80
|
+
): Promise<ReadableStream | NodeJS.ReadableStream> {
|
|
76
81
|
this.logger.debug(`Getting key: ${assetKey}`)
|
|
77
82
|
|
|
78
83
|
const filePath = `${this.config.localFileUploadPath}/${assetKey}`
|