@pikku/core 0.12.63 → 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 +147 -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 +9 -34
- 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 +16 -39
- 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
package/src/permissions.test.ts
CHANGED
|
@@ -1,39 +1,21 @@
|
|
|
1
1
|
import { describe, test, beforeEach } from 'node:test'
|
|
2
2
|
import * as assert from 'node:assert'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
addGlobalPermission,
|
|
5
|
+
runPermissions,
|
|
6
|
+
clearPermissionsCache,
|
|
7
|
+
checkAuthPermissions,
|
|
8
|
+
} from './permissions.js'
|
|
4
9
|
import { pikkuState, resetPikkuState } from './pikku-state.js'
|
|
10
|
+
import { pikkuAuth } from './function/functions.types.js'
|
|
5
11
|
import type { CoreServices, CoreUserSession } from './types/core.types.js'
|
|
6
|
-
import type {
|
|
7
|
-
CorePermissionGroup,
|
|
8
|
-
CorePikkuPermission,
|
|
9
|
-
} from './function/functions.types.js'
|
|
12
|
+
import type { CorePermissionGroup } from './function/functions.types.js'
|
|
10
13
|
|
|
11
14
|
beforeEach(() => {
|
|
12
15
|
resetPikkuState()
|
|
16
|
+
clearPermissionsCache()
|
|
13
17
|
})
|
|
14
18
|
|
|
15
|
-
// Test helper to get permissions for tags (not exported from core to keep it lean)
|
|
16
|
-
const getPermissionsForTags = (
|
|
17
|
-
tags?: string[]
|
|
18
|
-
): (CorePermissionGroup | CorePikkuPermission)[] => {
|
|
19
|
-
if (!tags || tags.length === 0) {
|
|
20
|
-
return []
|
|
21
|
-
}
|
|
22
|
-
const permissionsStore = pikkuState(null, 'permissions', 'tagGroup')
|
|
23
|
-
const result: (CorePermissionGroup | CorePikkuPermission)[] = []
|
|
24
|
-
for (const tag of new Set(tags)) {
|
|
25
|
-
const tagPermissions = permissionsStore[tag]
|
|
26
|
-
if (tagPermissions) {
|
|
27
|
-
if (Array.isArray(tagPermissions)) {
|
|
28
|
-
result.push(...tagPermissions)
|
|
29
|
-
} else {
|
|
30
|
-
result.push(tagPermissions)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return result
|
|
35
|
-
}
|
|
36
|
-
|
|
37
19
|
const mockServices: CoreServices = {
|
|
38
20
|
logger: {
|
|
39
21
|
info: () => {},
|
|
@@ -43,395 +25,194 @@ const mockServices: CoreServices = {
|
|
|
43
25
|
},
|
|
44
26
|
} as any
|
|
45
27
|
|
|
46
|
-
const mockSession: CoreUserSession = {
|
|
47
|
-
userId: 'test-user',
|
|
48
|
-
} as any
|
|
49
|
-
|
|
50
|
-
describe('addTagPermission', () => {
|
|
51
|
-
test('should add single permission for a tag', () => {
|
|
52
|
-
const mockPermission = async () => true
|
|
53
|
-
|
|
54
|
-
addTagPermission('testTag', [mockPermission])
|
|
55
|
-
|
|
56
|
-
const permissions = getPermissionsForTags(['testTag'])
|
|
57
|
-
assert.equal(permissions.length, 1)
|
|
58
|
-
assert.equal(permissions[0], mockPermission)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
test('should add multiple permissions for a tag', () => {
|
|
62
|
-
const mockPermission1 = async () => true
|
|
63
|
-
const mockPermission2 = async () => false
|
|
64
|
-
|
|
65
|
-
addTagPermission('multiTestTag', [mockPermission1, mockPermission2])
|
|
28
|
+
const mockSession: CoreUserSession = { userId: 'test-user' } as any
|
|
66
29
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
30
|
+
const run = (opts: {
|
|
31
|
+
funcPermissions?: CorePermissionGroup | any[]
|
|
32
|
+
data?: any
|
|
33
|
+
wire?: any
|
|
34
|
+
packageName?: string | null
|
|
35
|
+
}) =>
|
|
36
|
+
runPermissions({
|
|
37
|
+
funcPermissions: opts.funcPermissions,
|
|
38
|
+
services: mockServices,
|
|
39
|
+
wire: (opts.wire ?? { session: mockSession }) as any,
|
|
40
|
+
data: opts.data ?? {},
|
|
41
|
+
packageName: opts.packageName ?? null,
|
|
71
42
|
})
|
|
72
43
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
addTagPermission('groupTestTag', mockPermissionGroup)
|
|
79
|
-
|
|
80
|
-
const permissions = getPermissionsForTags(['groupTestTag'])
|
|
81
|
-
assert.equal(permissions.length, 1)
|
|
82
|
-
assert.equal(permissions[0], mockPermissionGroup)
|
|
44
|
+
describe('runPermissions — function permissions (OR gate)', () => {
|
|
45
|
+
test('passes when no permissions are defined', async () => {
|
|
46
|
+
await run({})
|
|
83
47
|
})
|
|
84
48
|
|
|
85
|
-
test('
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
wireInheritedPermissions: [{ type: 'tag', tag: 'billing:write' }],
|
|
92
|
-
services: mockServices,
|
|
93
|
-
wire: {},
|
|
94
|
-
data: {},
|
|
95
|
-
})
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
test('should apply both parent and exact tag permissions', async () => {
|
|
99
|
-
const executionOrder: string[] = []
|
|
100
|
-
const billingPermission = async () => {
|
|
101
|
-
executionOrder.push('billing')
|
|
102
|
-
return true
|
|
103
|
-
}
|
|
104
|
-
const billingReadPermission = async () => {
|
|
105
|
-
executionOrder.push('billing:read')
|
|
106
|
-
return true
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
addTagPermission('billing', [billingPermission])
|
|
110
|
-
addTagPermission('billing:read', [billingReadPermission])
|
|
111
|
-
|
|
112
|
-
await runPermissions('rpc', Math.random().toString(), {
|
|
113
|
-
wireInheritedPermissions: [{ type: 'tag', tag: 'billing:read' }],
|
|
114
|
-
services: mockServices,
|
|
115
|
-
wire: {},
|
|
116
|
-
data: {},
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
assert.deepEqual(executionOrder, ['billing:read'])
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
test('should resolve multi-level namespace permissions', async () => {
|
|
123
|
-
const executionOrder: string[] = []
|
|
124
|
-
const billingPermission = async () => {
|
|
125
|
-
executionOrder.push('billing')
|
|
126
|
-
return false
|
|
127
|
-
}
|
|
128
|
-
const billingReadPermission = async () => {
|
|
129
|
-
executionOrder.push('billing:read')
|
|
130
|
-
return false
|
|
131
|
-
}
|
|
132
|
-
const billingReadAdminPermission = async () => {
|
|
133
|
-
executionOrder.push('billing:read:admin')
|
|
134
|
-
return true
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
addTagPermission('billing', [billingPermission])
|
|
138
|
-
addTagPermission('billing:read', [billingReadPermission])
|
|
139
|
-
addTagPermission('billing:read:admin', [billingReadAdminPermission])
|
|
140
|
-
|
|
141
|
-
await runPermissions('rpc', Math.random().toString(), {
|
|
142
|
-
wireInheritedPermissions: [{ type: 'tag', tag: 'billing:read:admin' }],
|
|
143
|
-
services: mockServices,
|
|
144
|
-
wire: {},
|
|
145
|
-
data: {},
|
|
49
|
+
test('passes when at least one group is satisfied (OR)', async () => {
|
|
50
|
+
await run({
|
|
51
|
+
funcPermissions: {
|
|
52
|
+
owner: [async () => false],
|
|
53
|
+
admin: [async () => true],
|
|
54
|
+
},
|
|
146
55
|
})
|
|
147
|
-
|
|
148
|
-
assert.deepEqual(executionOrder, ['billing:read:admin'])
|
|
149
56
|
})
|
|
150
57
|
|
|
151
|
-
test('
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
})
|
|
58
|
+
test('throws when every group fails', async () => {
|
|
59
|
+
await assert.rejects(
|
|
60
|
+
run({
|
|
61
|
+
funcPermissions: {
|
|
62
|
+
owner: [async () => false],
|
|
63
|
+
admin: [async () => false],
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
{ message: 'Permission denied' }
|
|
67
|
+
)
|
|
162
68
|
})
|
|
163
69
|
|
|
164
|
-
test('
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
assert.throws(
|
|
170
|
-
() => {
|
|
171
|
-
addTagPermission('duplicateTag', [mockPermission])
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
message:
|
|
175
|
-
"Permissions for tag 'duplicateTag' already exist. Use a different tag or remove the existing permissions first.",
|
|
176
|
-
}
|
|
70
|
+
test('ANDs the entries within a single branch array', async () => {
|
|
71
|
+
await assert.rejects(
|
|
72
|
+
run({ funcPermissions: { grp: [async () => true, async () => false] } }),
|
|
73
|
+
{ message: 'Permission denied' }
|
|
177
74
|
)
|
|
75
|
+
await run({ funcPermissions: { grp: [async () => true, async () => true] } })
|
|
178
76
|
})
|
|
179
|
-
})
|
|
180
77
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
services: mockServices,
|
|
186
|
-
wire: {},
|
|
187
|
-
data: {},
|
|
78
|
+
test('accepts a bare permission array as a single AND branch', async () => {
|
|
79
|
+
await run({ funcPermissions: [async () => true] })
|
|
80
|
+
await assert.rejects(run({ funcPermissions: [async () => false] }), {
|
|
81
|
+
message: 'Permission denied',
|
|
188
82
|
})
|
|
189
83
|
})
|
|
190
84
|
|
|
191
|
-
test('
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
data
|
|
85
|
+
test('passes request data and wire through to permission functions', async () => {
|
|
86
|
+
let received: any
|
|
87
|
+
const wire = { session: mockSession }
|
|
88
|
+
const data = { hello: 'world' }
|
|
89
|
+
await run({
|
|
90
|
+
funcPermissions: {
|
|
91
|
+
grp: [
|
|
92
|
+
async (services: any, d: any, w: any) => {
|
|
93
|
+
received = { services, d, w }
|
|
94
|
+
return true
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
data,
|
|
99
|
+
wire,
|
|
205
100
|
})
|
|
206
|
-
|
|
207
|
-
assert.
|
|
101
|
+
assert.equal(received.services, mockServices)
|
|
102
|
+
assert.equal(received.d, data)
|
|
103
|
+
assert.equal(received.w, wire)
|
|
208
104
|
})
|
|
105
|
+
})
|
|
209
106
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
executionOrder.push('wiringTag')
|
|
215
|
-
return true
|
|
216
|
-
}
|
|
217
|
-
const funcTagPermission = async () => {
|
|
218
|
-
executionOrder.push('funcTag')
|
|
219
|
-
return true
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
addTagPermission('wiringTag', [wiringTagPermission])
|
|
223
|
-
addTagPermission('funcTag', [funcTagPermission])
|
|
224
|
-
|
|
225
|
-
const wirePermissions: CorePermissionGroup = {
|
|
226
|
-
permissions: [
|
|
227
|
-
async () => {
|
|
228
|
-
executionOrder.push('wiringPermission')
|
|
229
|
-
return true
|
|
230
|
-
},
|
|
231
|
-
],
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const funcPermissions: CorePermissionGroup = {
|
|
235
|
-
permissions: [
|
|
236
|
-
async () => {
|
|
237
|
-
executionOrder.push('funcPermission')
|
|
238
|
-
return true
|
|
239
|
-
},
|
|
240
|
-
],
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
await runPermissions('rpc', Math.random().toString(), {
|
|
244
|
-
wireInheritedPermissions: [{ type: 'tag', tag: 'wiringTag' }],
|
|
245
|
-
wirePermissions,
|
|
246
|
-
funcInheritedPermissions: [{ type: 'tag', tag: 'funcTag' }],
|
|
247
|
-
funcPermissions,
|
|
248
|
-
services: mockServices,
|
|
249
|
-
wire: {},
|
|
250
|
-
data: {},
|
|
251
|
-
})
|
|
107
|
+
describe('runPermissions — global permissions (AND gate)', () => {
|
|
108
|
+
test('requires every registered global to pass', async () => {
|
|
109
|
+
addGlobalPermission([async () => true])
|
|
110
|
+
await run({})
|
|
252
111
|
|
|
253
|
-
|
|
254
|
-
|
|
112
|
+
resetPikkuState()
|
|
113
|
+
clearPermissionsCache()
|
|
114
|
+
addGlobalPermission([async () => false])
|
|
115
|
+
await assert.rejects(run({}), { message: 'Permission denied' })
|
|
255
116
|
})
|
|
256
117
|
|
|
257
|
-
test('
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
addTagPermission('atLeastOneTestTag', [
|
|
262
|
-
failingPermission,
|
|
263
|
-
passingPermission,
|
|
264
|
-
])
|
|
265
|
-
|
|
266
|
-
// Should not throw because at least one permission passes
|
|
267
|
-
await runPermissions('rpc', Math.random().toString(), {
|
|
268
|
-
wireInheritedPermissions: [{ type: 'tag', tag: 'atLeastOneTestTag' }],
|
|
269
|
-
services: mockServices,
|
|
270
|
-
wire: {},
|
|
271
|
-
data: {},
|
|
272
|
-
})
|
|
118
|
+
test('ANDs multiple registered globals', async () => {
|
|
119
|
+
addGlobalPermission([async () => true])
|
|
120
|
+
addGlobalPermission([async () => false])
|
|
121
|
+
await assert.rejects(run({}), { message: 'Permission denied' })
|
|
273
122
|
})
|
|
274
123
|
|
|
275
|
-
test('
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
await assert.rejects(
|
|
282
|
-
runPermissions('rpc', Math.random().toString(), {
|
|
283
|
-
wireInheritedPermissions: [{ type: 'tag', tag: 'allFailTestTag' }],
|
|
284
|
-
services: mockServices,
|
|
285
|
-
wire: {},
|
|
286
|
-
data: {},
|
|
287
|
-
}),
|
|
288
|
-
{
|
|
289
|
-
message: 'Permission denied',
|
|
290
|
-
}
|
|
291
|
-
)
|
|
124
|
+
test('a global group entry ORs internally but must still pass', async () => {
|
|
125
|
+
addGlobalPermission({
|
|
126
|
+
a: [async () => false],
|
|
127
|
+
b: [async () => true],
|
|
128
|
+
} as CorePermissionGroup)
|
|
129
|
+
await run({})
|
|
292
130
|
})
|
|
131
|
+
})
|
|
293
132
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
133
|
+
describe('runPermissions — gates are independent (no escalation)', () => {
|
|
134
|
+
test('a passing global does NOT satisfy a failing function permission', async () => {
|
|
135
|
+
// The classic escalation bug: a broad global must not grant an admin-only fn.
|
|
136
|
+
addGlobalPermission([async () => true])
|
|
299
137
|
await assert.rejects(
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
services: mockServices,
|
|
303
|
-
wire: {},
|
|
304
|
-
data: {},
|
|
305
|
-
}),
|
|
306
|
-
{
|
|
307
|
-
message: 'Permission denied',
|
|
308
|
-
}
|
|
138
|
+
run({ funcPermissions: { admin: [async () => false] } }),
|
|
139
|
+
{ message: 'Permission denied' }
|
|
309
140
|
)
|
|
310
141
|
})
|
|
311
142
|
|
|
312
|
-
test('
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
addTagPermission('funcTag', [failingPermission])
|
|
316
|
-
|
|
143
|
+
test('a passing function permission does NOT bypass a failing global', async () => {
|
|
144
|
+
addGlobalPermission([async () => false])
|
|
317
145
|
await assert.rejects(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
services: mockServices,
|
|
321
|
-
wire: {},
|
|
322
|
-
data: {},
|
|
323
|
-
}),
|
|
324
|
-
{
|
|
325
|
-
message: 'Permission denied',
|
|
326
|
-
}
|
|
146
|
+
run({ funcPermissions: { anyone: [async () => true] } }),
|
|
147
|
+
{ message: 'Permission denied' }
|
|
327
148
|
)
|
|
328
149
|
})
|
|
329
150
|
|
|
330
|
-
test('
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
await assert.rejects(
|
|
336
|
-
runPermissions('rpc', Math.random().toString(), {
|
|
337
|
-
funcPermissions,
|
|
338
|
-
services: mockServices,
|
|
339
|
-
wire: {},
|
|
340
|
-
data: {},
|
|
341
|
-
}),
|
|
342
|
-
{
|
|
343
|
-
message: 'Permission denied',
|
|
344
|
-
}
|
|
345
|
-
)
|
|
151
|
+
test('passes only when both gates pass', async () => {
|
|
152
|
+
addGlobalPermission([async () => true])
|
|
153
|
+
await run({ funcPermissions: { admin: [async () => true] } })
|
|
346
154
|
})
|
|
155
|
+
})
|
|
347
156
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
addTagPermission('failingWiringTag', [failingWiringTagPermission])
|
|
357
|
-
|
|
358
|
-
const wirePermissions: CorePermissionGroup = {
|
|
359
|
-
permissions: [
|
|
360
|
-
async () => {
|
|
361
|
-
executionOrder.push('wiringPermission')
|
|
362
|
-
return true
|
|
363
|
-
},
|
|
364
|
-
],
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Should NOT throw because at least one permission (wirePermissions) passes
|
|
368
|
-
await runPermissions('rpc', Math.random().toString(), {
|
|
369
|
-
wireInheritedPermissions: [{ type: 'tag', tag: 'failingWiringTag' }],
|
|
370
|
-
wirePermissions,
|
|
371
|
-
services: mockServices,
|
|
372
|
-
wire: {},
|
|
373
|
-
data: {},
|
|
157
|
+
describe('runPermissions — package isolation', () => {
|
|
158
|
+
test('globals are scoped per package', async () => {
|
|
159
|
+
addGlobalPermission([async () => false], 'pkg-a')
|
|
160
|
+
// A different package sees no globals, so it passes.
|
|
161
|
+
await run({ packageName: 'pkg-b' })
|
|
162
|
+
// The registering package is gated.
|
|
163
|
+
await assert.rejects(run({ packageName: 'pkg-a' }), {
|
|
164
|
+
message: 'Permission denied',
|
|
374
165
|
})
|
|
375
|
-
|
|
376
|
-
// Both permissions should be evaluated
|
|
377
|
-
assert.deepEqual(executionOrder, ['wiringTag', 'wiringPermission'])
|
|
378
166
|
})
|
|
167
|
+
})
|
|
379
168
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
await runPermissions('rpc', Math.random().toString(), {
|
|
387
|
-
wireInheritedPermissions: [{ type: 'tag', tag: 'arrayTestTag' }],
|
|
388
|
-
services: mockServices,
|
|
389
|
-
wire: {},
|
|
390
|
-
data: {},
|
|
391
|
-
})
|
|
169
|
+
describe('checkAuthPermissions', () => {
|
|
170
|
+
test('returns true when there are no auth predicates', async () => {
|
|
171
|
+
assert.equal(
|
|
172
|
+
await checkAuthPermissions(undefined, mockSession, mockServices),
|
|
173
|
+
true
|
|
174
|
+
)
|
|
392
175
|
})
|
|
393
176
|
|
|
394
|
-
test('
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
177
|
+
test('honours a global pikkuAuth predicate at filter time', async () => {
|
|
178
|
+
addGlobalPermission([pikkuAuth(async () => false)])
|
|
179
|
+
assert.equal(
|
|
180
|
+
await checkAuthPermissions(undefined, mockSession, mockServices),
|
|
181
|
+
false
|
|
182
|
+
)
|
|
400
183
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
184
|
+
resetPikkuState()
|
|
185
|
+
clearPermissionsCache()
|
|
186
|
+
addGlobalPermission([pikkuAuth(async () => true)])
|
|
187
|
+
assert.equal(
|
|
188
|
+
await checkAuthPermissions(undefined, mockSession, mockServices),
|
|
189
|
+
true
|
|
190
|
+
)
|
|
408
191
|
})
|
|
409
192
|
|
|
410
|
-
test('
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const testData = { test: 'data' }
|
|
423
|
-
|
|
424
|
-
addTagPermission('paramTestTag', [testPermission])
|
|
425
|
-
|
|
426
|
-
await runPermissions('rpc', Math.random().toString(), {
|
|
427
|
-
wireInheritedPermissions: [{ type: 'tag', tag: 'paramTestTag' }],
|
|
428
|
-
services: mockServices,
|
|
429
|
-
wire: {},
|
|
430
|
-
data: testData,
|
|
431
|
-
})
|
|
193
|
+
test('resolves a function-referenced (wire) auth predicate by name', async () => {
|
|
194
|
+
const store = pikkuState(null, 'misc', 'permissions')
|
|
195
|
+
store['isAdmin'] = [pikkuAuth(async () => false)] as any
|
|
196
|
+
assert.equal(
|
|
197
|
+
await checkAuthPermissions(
|
|
198
|
+
[{ type: 'wire', name: 'isAdmin' }],
|
|
199
|
+
mockSession,
|
|
200
|
+
mockServices
|
|
201
|
+
),
|
|
202
|
+
false
|
|
203
|
+
)
|
|
204
|
+
})
|
|
432
205
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
206
|
+
test('ignores data-dependent permissions (no auth marker)', async () => {
|
|
207
|
+
const store = pikkuState(null, 'misc', 'permissions')
|
|
208
|
+
store['ownsRow'] = [async () => false] as any
|
|
209
|
+
assert.equal(
|
|
210
|
+
await checkAuthPermissions(
|
|
211
|
+
[{ type: 'wire', name: 'ownsRow' }],
|
|
212
|
+
mockSession,
|
|
213
|
+
mockServices
|
|
214
|
+
),
|
|
215
|
+
true
|
|
216
|
+
)
|
|
436
217
|
})
|
|
437
218
|
})
|