@pikku/core 0.12.62 → 0.12.64
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 +153 -0
- package/dist/dev/hot-reload.js +13 -55
- package/dist/dev/module-runner.d.ts +14 -0
- package/dist/dev/module-runner.js +59 -0
- package/dist/dev/reload-meta.d.ts +13 -0
- package/dist/dev/reload-meta.js +22 -0
- package/dist/errors/errors.d.ts +12 -0
- package/dist/errors/errors.js +19 -0
- package/dist/function/function-runner.d.ts +5 -5
- package/dist/function/function-runner.js +39 -111
- package/dist/function/functions.types.d.ts +17 -7
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -1
- package/dist/middleware/auth-apikey.d.ts +1 -0
- package/dist/middleware/auth-bearer.d.ts +1 -0
- package/dist/middleware/auth-cookie.d.ts +1 -0
- package/dist/middleware/cors.d.ts +1 -0
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/remote-auth.d.ts +1 -0
- package/dist/middleware/telemetry.d.ts +2 -0
- package/dist/middleware/timeout.d.ts +1 -0
- package/dist/permissions.d.ts +32 -25
- package/dist/permissions.js +104 -185
- package/dist/pikku-state.js +0 -2
- package/dist/scopes.d.ts +17 -0
- package/dist/scopes.js +59 -0
- package/dist/services/ai-embedding-service.d.ts +31 -0
- package/dist/services/ai-embedding-service.js +1 -0
- package/dist/services/credential-wire-service.d.ts +3 -1
- package/dist/services/credential-wire-service.js +10 -4
- package/dist/services/index.d.ts +4 -0
- package/dist/services/index.js +2 -0
- package/dist/services/meta-service.d.ts +15 -2
- package/dist/services/meta-service.js +51 -1
- package/dist/services/queue-webhook-service.d.ts +55 -0
- package/dist/services/queue-webhook-service.js +136 -0
- package/dist/services/scope-service.d.ts +67 -0
- package/dist/services/scope-service.js +1 -0
- package/dist/services/typed-secret-service.d.ts +8 -0
- package/dist/services/typed-secret-service.js +39 -4
- package/dist/services/webhook-service.d.ts +140 -0
- package/dist/services/webhook-service.js +44 -0
- package/dist/services/workflow-service.d.ts +7 -0
- package/dist/types/core.types.d.ts +34 -6
- package/dist/types/state.types.d.ts +20 -2
- package/dist/utils/hmac.d.ts +16 -0
- package/dist/utils/hmac.js +26 -0
- package/dist/utils/safe-fetch.d.ts +51 -0
- package/dist/utils/safe-fetch.js +192 -0
- package/dist/wirings/ai-agent/ai-agent-agui.d.ts +84 -0
- package/dist/wirings/ai-agent/ai-agent-agui.js +305 -0
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +41 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +146 -6
- package/dist/wirings/ai-agent/ai-agent-runner.js +6 -1
- package/dist/wirings/ai-agent/ai-agent-stream.js +61 -10
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +27 -1
- package/dist/wirings/ai-agent/index.d.ts +1 -0
- package/dist/wirings/ai-agent/index.js +1 -0
- package/dist/wirings/ai-agent/voice-input.d.ts +1 -0
- package/dist/wirings/ai-agent/voice-input.js +2 -38
- package/dist/wirings/ai-agent/voice-output.d.ts +1 -0
- package/dist/wirings/channel/channel-common.js +0 -1
- package/dist/wirings/channel/channel-handler.js +1 -4
- package/dist/wirings/channel/channel-middleware-runner.d.ts +12 -0
- package/dist/wirings/channel/channel-middleware-runner.js +34 -19
- package/dist/wirings/channel/channel.types.d.ts +2 -6
- package/dist/wirings/cli/cli-runner.js +0 -2
- package/dist/wirings/cli/cli.types.d.ts +1 -2
- package/dist/wirings/http/http-routes.js +0 -3
- package/dist/wirings/http/http-runner.d.ts +9 -29
- package/dist/wirings/http/http-runner.js +13 -35
- package/dist/wirings/http/http.types.d.ts +1 -9
- package/dist/wirings/mcp/mcp-runner.js +0 -2
- package/dist/wirings/mcp/mcp.types.d.ts +5 -11
- package/dist/wirings/oauth2/index.d.ts +0 -3
- package/dist/wirings/oauth2/index.js +1 -2
- package/dist/wirings/rpc/addon-runner.d.ts +28 -0
- package/dist/wirings/rpc/addon-runner.js +173 -0
- package/dist/wirings/rpc/index.d.ts +5 -1
- package/dist/wirings/rpc/index.js +3 -1
- package/dist/wirings/rpc/remote-addon-auth.d.ts +26 -0
- package/dist/wirings/rpc/remote-addon-auth.js +43 -0
- package/dist/wirings/rpc/rpc-runner.d.ts +19 -0
- package/dist/wirings/rpc/rpc-runner.js +109 -3
- package/dist/wirings/rpc/rpc-types.d.ts +15 -1
- package/dist/wirings/rpc/wire-addon.js +9 -0
- package/dist/wirings/rpc/wire-remote-addon.d.ts +47 -0
- package/dist/wirings/rpc/wire-remote-addon.js +19 -0
- package/dist/wirings/scope/index.d.ts +3 -0
- package/dist/wirings/scope/index.js +2 -0
- package/dist/wirings/scope/scope.types.d.ts +39 -0
- package/dist/wirings/scope/scope.types.js +1 -0
- package/dist/wirings/scope/validate-scope-definitions.d.ts +16 -0
- package/dist/wirings/scope/validate-scope-definitions.js +76 -0
- package/dist/wirings/scope/wire-scope.d.ts +33 -0
- package/dist/wirings/scope/wire-scope.js +32 -0
- package/dist/wirings/workflow/dsl/index.d.ts +1 -1
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +53 -1
- package/dist/wirings/workflow/graph/graph-node.d.ts +2 -0
- package/dist/wirings/workflow/graph/graph-runner.js +3 -0
- package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +6 -0
- package/dist/wirings/workflow/graph/wire-workflow-graph.js +1 -0
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +5 -0
- package/dist/wirings/workflow/index.d.ts +2 -2
- package/dist/wirings/workflow/index.js +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +51 -1
- package/dist/wirings/workflow/pikku-workflow-service.js +171 -0
- package/dist/wirings/workflow/workflow.types.d.ts +1 -1
- package/package.json +5 -1
- package/run-tests.sh +1 -0
- package/src/dev/hot-reload.ts +13 -68
- package/src/dev/module-runner.test.ts +169 -0
- package/src/dev/module-runner.ts +103 -0
- package/src/dev/reload-meta.test.ts +31 -2
- package/src/dev/reload-meta.ts +26 -0
- package/src/errors/errors.ts +24 -0
- package/src/function/function-runner.test.ts +276 -142
- package/src/function/function-runner.ts +52 -145
- package/src/function/functions.types.ts +28 -38
- package/src/index.ts +8 -6
- package/src/middleware/index.ts +1 -5
- package/src/permissions.test.ts +153 -372
- package/src/permissions.ts +139 -259
- package/src/pikku-state.ts +0 -2
- package/src/scopes.test.ts +167 -0
- package/src/scopes.ts +69 -0
- package/src/services/ai-embedding-service.ts +31 -0
- package/src/services/credential-wire-service.ts +10 -4
- package/src/services/index.ts +20 -0
- package/src/services/meta-service.ts +68 -3
- package/src/services/queue-webhook-service.test.ts +408 -0
- package/src/services/queue-webhook-service.ts +182 -0
- package/src/services/scope-service.ts +75 -0
- package/src/services/typed-secret-service.test.ts +35 -0
- package/src/services/typed-secret-service.ts +39 -4
- package/src/services/webhook-service.ts +180 -0
- package/src/services/workflow-service.ts +7 -0
- package/src/types/core.types.ts +42 -14
- package/src/types/state.types.ts +23 -3
- package/src/utils/hmac.ts +27 -0
- package/src/utils/safe-fetch.test.ts +373 -0
- package/src/utils/safe-fetch.ts +213 -0
- package/src/wirings/ai-agent/ai-agent-agui.test.ts +1127 -0
- package/src/wirings/ai-agent/ai-agent-agui.ts +386 -0
- package/src/wirings/ai-agent/ai-agent-prepare.test.ts +308 -0
- package/src/wirings/ai-agent/ai-agent-prepare.ts +192 -8
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +48 -1
- package/src/wirings/ai-agent/ai-agent-runner.ts +21 -0
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +198 -10
- package/src/wirings/ai-agent/ai-agent-stream.ts +84 -10
- package/src/wirings/ai-agent/ai-agent.types.ts +28 -0
- package/src/wirings/ai-agent/index.ts +1 -0
- package/src/wirings/ai-agent/voice-input.ts +6 -35
- package/src/wirings/channel/channel-common.ts +0 -1
- package/src/wirings/channel/channel-handler.ts +1 -9
- package/src/wirings/channel/channel-middleware-runner.test.ts +28 -2
- package/src/wirings/channel/channel-middleware-runner.ts +39 -27
- package/src/wirings/channel/channel.types.ts +0 -6
- package/src/wirings/cli/cli-runner.ts +0 -2
- package/src/wirings/cli/cli.types.ts +0 -2
- package/src/wirings/http/http-routes.ts +0 -3
- package/src/wirings/http/http-runner.test.ts +1 -11
- package/src/wirings/http/http-runner.ts +19 -40
- package/src/wirings/http/http.types.ts +1 -10
- package/src/wirings/mcp/mcp-runner.ts +0 -2
- package/src/wirings/mcp/mcp.types.ts +3 -11
- package/src/wirings/oauth2/index.ts +0 -3
- package/src/wirings/rpc/addon-runner.ts +233 -0
- package/src/wirings/rpc/index.ts +17 -1
- package/src/wirings/rpc/remote-addon-auth.ts +69 -0
- package/src/wirings/rpc/rpc-runner.test.ts +319 -16
- package/src/wirings/rpc/rpc-runner.ts +155 -3
- package/src/wirings/rpc/rpc-types.ts +16 -5
- package/src/wirings/rpc/wire-addon.test.ts +6 -3
- package/src/wirings/rpc/wire-addon.ts +9 -0
- package/src/wirings/rpc/wire-remote-addon.ts +57 -0
- package/src/wirings/scope/index.ts +14 -0
- package/src/wirings/scope/scope.test.ts +135 -0
- package/src/wirings/scope/scope.types.ts +44 -0
- package/src/wirings/scope/validate-scope-definitions.ts +110 -0
- package/src/wirings/scope/wire-scope.ts +34 -0
- package/src/wirings/workflow/dsl/index.ts +4 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +61 -0
- package/src/wirings/workflow/graph/graph-node.ts +2 -0
- package/src/wirings/workflow/graph/graph-runner.test.ts +58 -0
- package/src/wirings/workflow/graph/graph-runner.ts +3 -0
- package/src/wirings/workflow/graph/wire-workflow-graph.ts +7 -0
- package/src/wirings/workflow/graph/workflow-graph.types.ts +5 -0
- package/src/wirings/workflow/index.ts +5 -0
- package/src/wirings/workflow/pikku-workflow-service.test.ts +341 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +236 -0
- package/src/wirings/workflow/workflow-step-session.test.ts +21 -5
- package/src/wirings/workflow/workflow.types.ts +4 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/src/wirings/oauth2/oauth2-client.test.ts +0 -930
- package/src/wirings/oauth2/oauth2-client.ts +0 -331
- package/src/wirings/oauth2/oauth2-routes.ts +0 -234
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
getFunctionNames,
|
|
7
7
|
runPikkuFunc,
|
|
8
8
|
} from './function-runner.js'
|
|
9
|
-
import {
|
|
9
|
+
import { addGlobalPermission, addTagMiddleware } from '../index.js'
|
|
10
10
|
import { resetPikkuState, pikkuState } from '../pikku-state.js'
|
|
11
11
|
import type { CoreServices, CorePikkuMiddleware } from '../types/core.types.js'
|
|
12
12
|
import type { CorePermissionGroup } from './functions.types.js'
|
|
13
13
|
import { PikkuSessionService } from '../services/user-session-service.js'
|
|
14
|
-
import { ReadonlySessionError } from '../errors/errors.js'
|
|
14
|
+
import { MissingScopeError, ReadonlySessionError } from '../errors/errors.js'
|
|
15
15
|
import { createInvocationAudit } from '../services/audit-service.js'
|
|
16
16
|
|
|
17
17
|
beforeEach(() => {
|
|
@@ -21,21 +21,17 @@ beforeEach(() => {
|
|
|
21
21
|
// Helper function to add function with metadata for tests
|
|
22
22
|
const addTestFunction = (funcName: string, funcConfig: any) => {
|
|
23
23
|
addFunction(funcName, funcConfig)
|
|
24
|
-
// Convert tags to middleware metadata
|
|
24
|
+
// Convert tags to middleware metadata (tags are organizational only; they no
|
|
25
|
+
// longer carry permissions in the session+function authorization model).
|
|
25
26
|
const middleware = funcConfig.tags
|
|
26
27
|
? funcConfig.tags.map((tag: string) => ({ type: 'tag' as const, tag }))
|
|
27
28
|
: undefined
|
|
28
|
-
// Convert tags to permissions metadata
|
|
29
|
-
const permissions = funcConfig.tags
|
|
30
|
-
? funcConfig.tags.map((tag: string) => ({ type: 'tag' as const, tag }))
|
|
31
|
-
: undefined
|
|
32
29
|
pikkuState(null, 'function', 'meta')[funcName] = {
|
|
33
30
|
pikkuFuncId: funcName,
|
|
34
31
|
inputSchemaName: null,
|
|
35
32
|
outputSchemaName: null,
|
|
36
33
|
sessionless: true,
|
|
37
34
|
middleware,
|
|
38
|
-
permissions,
|
|
39
35
|
}
|
|
40
36
|
}
|
|
41
37
|
|
|
@@ -101,31 +97,16 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
101
97
|
])
|
|
102
98
|
})
|
|
103
99
|
|
|
104
|
-
test('
|
|
100
|
+
test('runs global (AND) then function (OR) permissions before the handler', async () => {
|
|
105
101
|
const executionOrder: string[] = []
|
|
106
102
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
executionOrder.push('funcTag')
|
|
114
|
-
return true
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
addTagPermission('wiringTag', [wiringTagPermission])
|
|
118
|
-
addTagPermission('funcTag', [funcTagPermission])
|
|
103
|
+
addGlobalPermission([
|
|
104
|
+
async () => {
|
|
105
|
+
executionOrder.push('globalPermission')
|
|
106
|
+
return true
|
|
107
|
+
},
|
|
108
|
+
])
|
|
119
109
|
|
|
120
|
-
// Setup direct permissions
|
|
121
|
-
const wiringPermissions: CorePermissionGroup = {
|
|
122
|
-
permissions: [
|
|
123
|
-
async () => {
|
|
124
|
-
executionOrder.push('wiringPermission')
|
|
125
|
-
return true
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
}
|
|
129
110
|
const funcPermissions: CorePermissionGroup = {
|
|
130
111
|
permissions: [
|
|
131
112
|
async () => {
|
|
@@ -135,14 +116,12 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
135
116
|
],
|
|
136
117
|
}
|
|
137
118
|
|
|
138
|
-
// Register function with permissions and tags
|
|
139
119
|
addTestFunction('testFunc', {
|
|
140
120
|
func: async () => {
|
|
141
121
|
executionOrder.push('main')
|
|
142
122
|
return 'success'
|
|
143
123
|
},
|
|
144
124
|
permissions: funcPermissions,
|
|
145
|
-
tags: ['funcTag'],
|
|
146
125
|
})
|
|
147
126
|
|
|
148
127
|
const result = await runPikkuFunc(
|
|
@@ -153,49 +132,33 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
153
132
|
singletonServices: mockSingletonServices,
|
|
154
133
|
getAllServices: () => mockServices,
|
|
155
134
|
data: () => ({}),
|
|
156
|
-
wirePermissions: wiringPermissions,
|
|
157
|
-
tags: ['wiringTag'],
|
|
158
135
|
auth: false,
|
|
159
136
|
wire: {},
|
|
160
137
|
}
|
|
161
138
|
)
|
|
162
139
|
|
|
163
140
|
assert.equal(result, 'success')
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const failingWiringTagPermission = async () => false
|
|
170
|
-
|
|
171
|
-
addTagPermission('wiringTag', [failingWiringTagPermission])
|
|
172
|
-
|
|
173
|
-
addTestFunction('testFunc', {
|
|
174
|
-
func: async () => 'success',
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
await assert.rejects(
|
|
178
|
-
runPikkuFunc('rpc', Math.random().toString(), 'testFunc', {
|
|
179
|
-
singletonServices: mockSingletonServices,
|
|
180
|
-
getAllServices: () => mockServices,
|
|
181
|
-
data: () => ({}),
|
|
182
|
-
tags: ['wiringTag'],
|
|
183
|
-
auth: false,
|
|
184
|
-
wire: {},
|
|
185
|
-
}),
|
|
186
|
-
{
|
|
187
|
-
message: 'Permission denied',
|
|
188
|
-
}
|
|
189
|
-
)
|
|
141
|
+
assert.deepEqual(executionOrder, [
|
|
142
|
+
'globalPermission',
|
|
143
|
+
'funcPermission',
|
|
144
|
+
'main',
|
|
145
|
+
])
|
|
190
146
|
})
|
|
191
147
|
|
|
192
|
-
test('
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
148
|
+
test('a failing global permission denies before the function permission runs', async () => {
|
|
149
|
+
let funcPermissionRan = false
|
|
150
|
+
addGlobalPermission([async () => false])
|
|
196
151
|
|
|
197
152
|
addTestFunction('testFunc', {
|
|
198
153
|
func: async () => 'success',
|
|
154
|
+
permissions: {
|
|
155
|
+
allow: [
|
|
156
|
+
async () => {
|
|
157
|
+
funcPermissionRan = true
|
|
158
|
+
return true
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
},
|
|
199
162
|
})
|
|
200
163
|
|
|
201
164
|
await assert.rejects(
|
|
@@ -203,7 +166,6 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
203
166
|
singletonServices: mockSingletonServices,
|
|
204
167
|
getAllServices: () => mockServices,
|
|
205
168
|
data: () => ({}),
|
|
206
|
-
wirePermissions: wiringPermissions,
|
|
207
169
|
auth: false,
|
|
208
170
|
wire: {},
|
|
209
171
|
}),
|
|
@@ -211,16 +173,15 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
211
173
|
message: 'Permission denied',
|
|
212
174
|
}
|
|
213
175
|
)
|
|
176
|
+
assert.equal(funcPermissionRan, false)
|
|
214
177
|
})
|
|
215
178
|
|
|
216
|
-
test('
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
addTagPermission('funcTag', [failingFuncTagPermission])
|
|
179
|
+
test('a passing global permission does not satisfy a failing function permission', async () => {
|
|
180
|
+
addGlobalPermission([async () => true])
|
|
220
181
|
|
|
221
182
|
addTestFunction('testFunc', {
|
|
222
183
|
func: async () => 'success',
|
|
223
|
-
|
|
184
|
+
permissions: { admin: [async () => false] },
|
|
224
185
|
})
|
|
225
186
|
|
|
226
187
|
await assert.rejects(
|
|
@@ -294,39 +255,31 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
294
255
|
assert.equal(executionCount, 1)
|
|
295
256
|
})
|
|
296
257
|
|
|
297
|
-
test('should
|
|
258
|
+
test('should OR function permission groups and short-circuit on the first pass', async () => {
|
|
298
259
|
const executionOrder: string[] = []
|
|
299
260
|
|
|
300
|
-
//
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
},
|
|
310
|
-
]
|
|
311
|
-
|
|
312
|
-
const objectPermission: CorePermissionGroup = {
|
|
313
|
-
permissions: [
|
|
261
|
+
// Two groups ORed together: the first (owner) fails, the second (admin) passes.
|
|
262
|
+
const permissions: CorePermissionGroup = {
|
|
263
|
+
owner: [
|
|
264
|
+
async () => {
|
|
265
|
+
executionOrder.push('owner')
|
|
266
|
+
return false
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
admin: [
|
|
314
270
|
async () => {
|
|
315
|
-
executionOrder.push('
|
|
271
|
+
executionOrder.push('admin')
|
|
316
272
|
return true
|
|
317
273
|
},
|
|
318
274
|
],
|
|
319
275
|
}
|
|
320
276
|
|
|
321
|
-
addTagPermission('mixedTag', arrayPermission)
|
|
322
|
-
|
|
323
277
|
addTestFunction('testFunc', {
|
|
324
278
|
func: async () => {
|
|
325
279
|
executionOrder.push('main')
|
|
326
280
|
return 'success'
|
|
327
281
|
},
|
|
328
|
-
permissions
|
|
329
|
-
tags: ['mixedTag'],
|
|
282
|
+
permissions,
|
|
330
283
|
})
|
|
331
284
|
|
|
332
285
|
const result = await runPikkuFunc(
|
|
@@ -343,12 +296,7 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
343
296
|
)
|
|
344
297
|
|
|
345
298
|
assert.equal(result, 'success')
|
|
346
|
-
|
|
347
|
-
assert.deepEqual(executionOrder, [
|
|
348
|
-
'arrayPermission1',
|
|
349
|
-
'arrayPermission2',
|
|
350
|
-
'main',
|
|
351
|
-
])
|
|
299
|
+
assert.deepEqual(executionOrder, ['owner', 'admin', 'main'])
|
|
352
300
|
})
|
|
353
301
|
|
|
354
302
|
test('should work without any middleware or permissions', async () => {
|
|
@@ -372,7 +320,7 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
372
320
|
assert.equal(result, 'simple success')
|
|
373
321
|
})
|
|
374
322
|
|
|
375
|
-
test('should work with only wiring-level middleware
|
|
323
|
+
test('should work with only wiring-level middleware', async () => {
|
|
376
324
|
const executionOrder: string[] = []
|
|
377
325
|
|
|
378
326
|
const wiringMiddleware: CorePikkuMiddleware = async (
|
|
@@ -384,15 +332,6 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
384
332
|
await next()
|
|
385
333
|
}
|
|
386
334
|
|
|
387
|
-
const wiringPermissions: CorePermissionGroup = {
|
|
388
|
-
permissions: [
|
|
389
|
-
async () => {
|
|
390
|
-
executionOrder.push('wiringPermission')
|
|
391
|
-
return true
|
|
392
|
-
},
|
|
393
|
-
],
|
|
394
|
-
}
|
|
395
|
-
|
|
396
335
|
addTestFunction('testFunc', {
|
|
397
336
|
func: async () => {
|
|
398
337
|
executionOrder.push('main')
|
|
@@ -409,19 +348,13 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
409
348
|
getAllServices: () => mockServices,
|
|
410
349
|
data: () => ({}),
|
|
411
350
|
wireMiddleware: [wiringMiddleware],
|
|
412
|
-
wirePermissions: wiringPermissions,
|
|
413
351
|
auth: false,
|
|
414
352
|
wire: {},
|
|
415
353
|
}
|
|
416
354
|
)
|
|
417
355
|
|
|
418
356
|
assert.equal(result, 'success')
|
|
419
|
-
|
|
420
|
-
assert.deepEqual(executionOrder, [
|
|
421
|
-
'wiringMiddleware',
|
|
422
|
-
'wiringPermission',
|
|
423
|
-
'main',
|
|
424
|
-
])
|
|
357
|
+
assert.deepEqual(executionOrder, ['wiringMiddleware', 'main'])
|
|
425
358
|
})
|
|
426
359
|
|
|
427
360
|
test('should work with only function-level middleware and permissions', async () => {
|
|
@@ -1212,7 +1145,10 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
1212
1145
|
addTestFunction('auditedInner', {
|
|
1213
1146
|
audit: true,
|
|
1214
1147
|
func: async (services: any) => {
|
|
1215
|
-
await services.auditLog.write({
|
|
1148
|
+
await services.auditLog.write({
|
|
1149
|
+
type: 'booking.update',
|
|
1150
|
+
source: 'explicit',
|
|
1151
|
+
})
|
|
1216
1152
|
return 'inner-ok'
|
|
1217
1153
|
},
|
|
1218
1154
|
})
|
|
@@ -1223,15 +1159,20 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
1223
1159
|
},
|
|
1224
1160
|
})
|
|
1225
1161
|
|
|
1226
|
-
const result = await runPikkuFunc(
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1162
|
+
const result = await runPikkuFunc(
|
|
1163
|
+
'http',
|
|
1164
|
+
'exposed-rpc-route',
|
|
1165
|
+
'rpcCallerLike',
|
|
1166
|
+
{
|
|
1167
|
+
singletonServices,
|
|
1168
|
+
data: () => ({}),
|
|
1169
|
+
auth: false,
|
|
1170
|
+
wire: {},
|
|
1171
|
+
createWireServices: async (services: any, wire: any) => ({
|
|
1172
|
+
auditLog: createInvocationAudit(services.audit, wire),
|
|
1173
|
+
}),
|
|
1174
|
+
}
|
|
1175
|
+
)
|
|
1235
1176
|
|
|
1236
1177
|
assert.equal(result, 'inner-ok')
|
|
1237
1178
|
assert.equal(events.length, 1)
|
|
@@ -1249,7 +1190,10 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
1249
1190
|
addTestFunction('auditedChildWriter', {
|
|
1250
1191
|
audit: true,
|
|
1251
1192
|
func: async (services: any) => {
|
|
1252
|
-
await services.auditLog.write({
|
|
1193
|
+
await services.auditLog.write({
|
|
1194
|
+
type: 'child.event',
|
|
1195
|
+
source: 'explicit',
|
|
1196
|
+
})
|
|
1253
1197
|
return 'child-ok'
|
|
1254
1198
|
},
|
|
1255
1199
|
})
|
|
@@ -1257,17 +1201,25 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
1257
1201
|
addTestFunction('auditedParentWriter', {
|
|
1258
1202
|
audit: true,
|
|
1259
1203
|
func: async (services: any, _data: any, wire: any) => {
|
|
1260
|
-
await services.auditLog.write({
|
|
1204
|
+
await services.auditLog.write({
|
|
1205
|
+
type: 'parent.event',
|
|
1206
|
+
source: 'explicit',
|
|
1207
|
+
})
|
|
1261
1208
|
return wire.rpc.invoke('auditedChildWriter', {})
|
|
1262
1209
|
},
|
|
1263
1210
|
})
|
|
1264
1211
|
|
|
1265
|
-
const result = await runPikkuFunc(
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1212
|
+
const result = await runPikkuFunc(
|
|
1213
|
+
'rpc',
|
|
1214
|
+
'audited-parent-child',
|
|
1215
|
+
'auditedParentWriter',
|
|
1216
|
+
{
|
|
1217
|
+
singletonServices,
|
|
1218
|
+
data: () => ({}),
|
|
1219
|
+
auth: false,
|
|
1220
|
+
wire: {},
|
|
1221
|
+
}
|
|
1222
|
+
)
|
|
1271
1223
|
|
|
1272
1224
|
assert.equal(result, 'child-ok')
|
|
1273
1225
|
const byType = Object.fromEntries(events.map((e) => [e.type, e.functionId]))
|
|
@@ -1279,26 +1231,41 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
1279
1231
|
const warnings: any[] = []
|
|
1280
1232
|
const singletonServices = {
|
|
1281
1233
|
...mockSingletonServices,
|
|
1282
|
-
logger: {
|
|
1234
|
+
logger: {
|
|
1235
|
+
...mockSingletonServices.logger,
|
|
1236
|
+
warn: (msg: any) => warnings.push(msg),
|
|
1237
|
+
},
|
|
1283
1238
|
audit: { audit: async () => {} },
|
|
1284
1239
|
}
|
|
1285
1240
|
|
|
1286
1241
|
addTestFunction('unauditedWriter', {
|
|
1287
1242
|
func: async (services: any) => {
|
|
1288
|
-
await services.auditLog.write({
|
|
1243
|
+
await services.auditLog.write({
|
|
1244
|
+
type: 'dropped.event',
|
|
1245
|
+
source: 'explicit',
|
|
1246
|
+
})
|
|
1289
1247
|
return 'ok'
|
|
1290
1248
|
},
|
|
1291
1249
|
})
|
|
1292
1250
|
|
|
1293
|
-
const result = await runPikkuFunc(
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1251
|
+
const result = await runPikkuFunc(
|
|
1252
|
+
'rpc',
|
|
1253
|
+
'unaudited-writer',
|
|
1254
|
+
'unauditedWriter',
|
|
1255
|
+
{
|
|
1256
|
+
singletonServices,
|
|
1257
|
+
data: () => ({}),
|
|
1258
|
+
auth: false,
|
|
1259
|
+
wire: {},
|
|
1260
|
+
createWireServices: async (services: any, wire: any) => ({
|
|
1261
|
+
auditLog: createInvocationAudit(
|
|
1262
|
+
services.audit,
|
|
1263
|
+
wire,
|
|
1264
|
+
services.logger
|
|
1265
|
+
),
|
|
1266
|
+
}),
|
|
1267
|
+
}
|
|
1268
|
+
)
|
|
1302
1269
|
|
|
1303
1270
|
assert.equal(result, 'ok')
|
|
1304
1271
|
assert.equal(warnings.length, 1)
|
|
@@ -1361,3 +1328,170 @@ describe('function-runner helpers', () => {
|
|
|
1361
1328
|
])
|
|
1362
1329
|
})
|
|
1363
1330
|
})
|
|
1331
|
+
|
|
1332
|
+
describe('runPikkuFunc - scopes', () => {
|
|
1333
|
+
const runWithScopes = (
|
|
1334
|
+
funcName: string,
|
|
1335
|
+
{
|
|
1336
|
+
scopes,
|
|
1337
|
+
sessionScopes,
|
|
1338
|
+
data = () => ({}),
|
|
1339
|
+
}: {
|
|
1340
|
+
scopes?: string[]
|
|
1341
|
+
sessionScopes?: string[]
|
|
1342
|
+
data?: () => any
|
|
1343
|
+
}
|
|
1344
|
+
) =>
|
|
1345
|
+
runPikkuFunc('rpc', Math.random().toString(), funcName, {
|
|
1346
|
+
singletonServices: mockSingletonServices,
|
|
1347
|
+
data,
|
|
1348
|
+
auth: false,
|
|
1349
|
+
wire: { session: { userId: 'u1', scopes: sessionScopes } as any },
|
|
1350
|
+
})
|
|
1351
|
+
|
|
1352
|
+
test('runs the function when the session holds the required scope', async () => {
|
|
1353
|
+
addTestFunction('scoped', {
|
|
1354
|
+
func: async () => 'ok',
|
|
1355
|
+
scopes: ['invoices:create'],
|
|
1356
|
+
})
|
|
1357
|
+
|
|
1358
|
+
const result = await runWithScopes('scoped', {
|
|
1359
|
+
sessionScopes: ['invoices:create'],
|
|
1360
|
+
})
|
|
1361
|
+
|
|
1362
|
+
assert.equal(result, 'ok')
|
|
1363
|
+
})
|
|
1364
|
+
|
|
1365
|
+
test('throws MissingScopeError when the session lacks the scope', async () => {
|
|
1366
|
+
addTestFunction('scoped', {
|
|
1367
|
+
func: async () => 'ok',
|
|
1368
|
+
scopes: ['invoices:create'],
|
|
1369
|
+
})
|
|
1370
|
+
|
|
1371
|
+
await assert.rejects(
|
|
1372
|
+
() => runWithScopes('scoped', { sessionScopes: ['billing:read'] }),
|
|
1373
|
+
MissingScopeError
|
|
1374
|
+
)
|
|
1375
|
+
})
|
|
1376
|
+
|
|
1377
|
+
test('honours wildcards held by the session', async () => {
|
|
1378
|
+
addTestFunction('scoped', {
|
|
1379
|
+
func: async () => 'ok',
|
|
1380
|
+
scopes: ['admin:invoices:create'],
|
|
1381
|
+
})
|
|
1382
|
+
|
|
1383
|
+
const result = await runWithScopes('scoped', { sessionScopes: ['admin:*'] })
|
|
1384
|
+
|
|
1385
|
+
assert.equal(result, 'ok')
|
|
1386
|
+
})
|
|
1387
|
+
|
|
1388
|
+
test('does not gate a function that declares no scopes', async () => {
|
|
1389
|
+
addTestFunction('unscoped', { func: async () => 'ok' })
|
|
1390
|
+
|
|
1391
|
+
const result = await runWithScopes('unscoped', { sessionScopes: [] })
|
|
1392
|
+
|
|
1393
|
+
assert.equal(result, 'ok')
|
|
1394
|
+
})
|
|
1395
|
+
|
|
1396
|
+
test('reads scopes from function meta when the config omits them', async () => {
|
|
1397
|
+
addTestFunction('metaScoped', { func: async () => 'ok' })
|
|
1398
|
+
pikkuState(null, 'function', 'meta').metaScoped.scopes = ['invoices:create']
|
|
1399
|
+
|
|
1400
|
+
await assert.rejects(
|
|
1401
|
+
() => runWithScopes('metaScoped', { sessionScopes: [] }),
|
|
1402
|
+
MissingScopeError
|
|
1403
|
+
)
|
|
1404
|
+
})
|
|
1405
|
+
|
|
1406
|
+
// The load-bearing test. Scopes are an AND gate separate from permissions, so
|
|
1407
|
+
// a passing global (or function) permission must never satisfy a scope.
|
|
1408
|
+
test('a passing global permission does not satisfy a scope', async () => {
|
|
1409
|
+
addGlobalPermission([async () => true])
|
|
1410
|
+
addTestFunction('scoped', { func: async () => 'ok', scopes: ['admin'] })
|
|
1411
|
+
|
|
1412
|
+
await assert.rejects(
|
|
1413
|
+
() => runWithScopes('scoped', { sessionScopes: [] }),
|
|
1414
|
+
MissingScopeError
|
|
1415
|
+
)
|
|
1416
|
+
})
|
|
1417
|
+
|
|
1418
|
+
test('a passing function permission does not satisfy a scope', async () => {
|
|
1419
|
+
addTestFunction('scoped', {
|
|
1420
|
+
func: async () => 'ok',
|
|
1421
|
+
scopes: ['admin'],
|
|
1422
|
+
permissions: { allow: [async () => true] },
|
|
1423
|
+
})
|
|
1424
|
+
|
|
1425
|
+
await assert.rejects(
|
|
1426
|
+
() => runWithScopes('scoped', { sessionScopes: [] }),
|
|
1427
|
+
MissingScopeError
|
|
1428
|
+
)
|
|
1429
|
+
})
|
|
1430
|
+
|
|
1431
|
+
test('a held scope does not bypass a failing permission', async () => {
|
|
1432
|
+
addTestFunction('scoped', {
|
|
1433
|
+
func: async () => 'ok',
|
|
1434
|
+
scopes: ['admin'],
|
|
1435
|
+
permissions: { deny: [async () => false] },
|
|
1436
|
+
})
|
|
1437
|
+
|
|
1438
|
+
await assert.rejects(
|
|
1439
|
+
() => runWithScopes('scoped', { sessionScopes: ['admin'] }),
|
|
1440
|
+
{ message: 'Permission denied' }
|
|
1441
|
+
)
|
|
1442
|
+
})
|
|
1443
|
+
|
|
1444
|
+
test('a failing scope short-circuits before permissions run', async () => {
|
|
1445
|
+
let permissionRan = false
|
|
1446
|
+
addTestFunction('scoped', {
|
|
1447
|
+
func: async () => 'ok',
|
|
1448
|
+
scopes: ['admin'],
|
|
1449
|
+
permissions: {
|
|
1450
|
+
allow: [
|
|
1451
|
+
async () => {
|
|
1452
|
+
permissionRan = true
|
|
1453
|
+
return true
|
|
1454
|
+
},
|
|
1455
|
+
],
|
|
1456
|
+
},
|
|
1457
|
+
})
|
|
1458
|
+
|
|
1459
|
+
await assert.rejects(
|
|
1460
|
+
() => runWithScopes('scoped', { sessionScopes: [] }),
|
|
1461
|
+
MissingScopeError
|
|
1462
|
+
)
|
|
1463
|
+
assert.equal(permissionRan, false)
|
|
1464
|
+
})
|
|
1465
|
+
|
|
1466
|
+
// Scopes depend only on the session, so a denied request must never pay to
|
|
1467
|
+
// parse or validate its body.
|
|
1468
|
+
test('a failing scope short-circuits before the data is evaluated', async () => {
|
|
1469
|
+
addTestFunction('scoped', { func: async () => 'ok', scopes: ['admin'] })
|
|
1470
|
+
|
|
1471
|
+
await assert.rejects(
|
|
1472
|
+
() =>
|
|
1473
|
+
runWithScopes('scoped', {
|
|
1474
|
+
sessionScopes: [],
|
|
1475
|
+
data: () => {
|
|
1476
|
+
throw new Error('data should not have been evaluated')
|
|
1477
|
+
},
|
|
1478
|
+
}),
|
|
1479
|
+
MissingScopeError
|
|
1480
|
+
)
|
|
1481
|
+
})
|
|
1482
|
+
|
|
1483
|
+
test('fails closed when there is no session at all', async () => {
|
|
1484
|
+
addTestFunction('scoped', { func: async () => 'ok', scopes: ['admin'] })
|
|
1485
|
+
|
|
1486
|
+
await assert.rejects(
|
|
1487
|
+
() =>
|
|
1488
|
+
runPikkuFunc('rpc', Math.random().toString(), 'scoped', {
|
|
1489
|
+
singletonServices: mockSingletonServices,
|
|
1490
|
+
data: () => ({}),
|
|
1491
|
+
auth: false,
|
|
1492
|
+
wire: {},
|
|
1493
|
+
}),
|
|
1494
|
+
MissingScopeError
|
|
1495
|
+
)
|
|
1496
|
+
})
|
|
1497
|
+
})
|