@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
package/src/permissions.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CoreServices,
|
|
3
3
|
CoreUserSession,
|
|
4
|
-
PikkuWiringTypes,
|
|
5
4
|
PermissionMetadata,
|
|
6
5
|
PikkuWire,
|
|
7
6
|
} from './types/core.types.js'
|
|
@@ -11,16 +10,25 @@ import type {
|
|
|
11
10
|
} from './function/functions.types.js'
|
|
12
11
|
import { pikkuState } from './pikku-state.js'
|
|
13
12
|
import { ForbiddenError } from './errors/errors.js'
|
|
14
|
-
import { freezeDedupe, getTagGroups } from './utils.js'
|
|
15
13
|
|
|
16
14
|
/**
|
|
17
|
-
*
|
|
15
|
+
* Evaluates a single permission group. A group ORs its branches together and
|
|
16
|
+
* ANDs the entries within a branch: the group passes if at least one branch
|
|
17
|
+
* (an array of permission functions, all of which must return true — or a lone
|
|
18
|
+
* function) is satisfied.
|
|
19
|
+
*
|
|
20
|
+
* This is the only place OR lives in authorization. Everything cross-cutting —
|
|
21
|
+
* global permissions, scopes, and auth — ANDs together; a function's own
|
|
22
|
+
* `permissions` group is the sole place where "owner OR admin"-style
|
|
23
|
+
* alternatives are expressed.
|
|
24
|
+
*
|
|
25
|
+
* @param permissions - The permission group to evaluate.
|
|
18
26
|
* @param services - The core services required for permission validation.
|
|
19
|
-
* @param data - The data to
|
|
27
|
+
* @param data - The request data passed to each permission function.
|
|
20
28
|
* @param wire - The wire object containing request/response context and session.
|
|
21
|
-
* @returns A promise
|
|
29
|
+
* @returns A promise resolving to true if the group is satisfied.
|
|
22
30
|
*/
|
|
23
|
-
const verifyPermissions = async
|
|
31
|
+
const verifyPermissions = async (
|
|
24
32
|
permissions: CorePermissionGroup,
|
|
25
33
|
services: CoreServices,
|
|
26
34
|
data: any,
|
|
@@ -30,7 +38,6 @@ const verifyPermissions = async <Out = any>(
|
|
|
30
38
|
return true
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
let valid = false
|
|
34
41
|
const permissionGroups = Object.values(permissions)
|
|
35
42
|
if (permissionGroups.length === 0) {
|
|
36
43
|
return true
|
|
@@ -42,13 +49,12 @@ const verifyPermissions = async <Out = any>(
|
|
|
42
49
|
funcs.map((func) => func(services, data, wire))
|
|
43
50
|
)
|
|
44
51
|
if (permissioned.every((result) => result)) {
|
|
45
|
-
|
|
52
|
+
return true
|
|
46
53
|
}
|
|
47
54
|
} else {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return true
|
|
55
|
+
if (await funcs(services, data, wire as any)) {
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
52
58
|
}
|
|
53
59
|
}
|
|
54
60
|
return false
|
|
@@ -61,12 +67,16 @@ const verifyPermissions = async <Out = any>(
|
|
|
61
67
|
* It's used internally by the framework to resolve permission references in metadata.
|
|
62
68
|
*
|
|
63
69
|
* @param {string} name - The unique name (pikkuFuncId) of the permission function.
|
|
70
|
+
* @param {string | null} packageName - Optional package namespace.
|
|
64
71
|
* @returns {CorePikkuPermission | undefined} The permission function, or undefined if not found.
|
|
65
72
|
*
|
|
66
73
|
* @internal
|
|
67
74
|
*/
|
|
68
|
-
const getPermissionByName = (
|
|
69
|
-
|
|
75
|
+
const getPermissionByName = (
|
|
76
|
+
name: string,
|
|
77
|
+
packageName: string | null = null
|
|
78
|
+
): CorePikkuPermission | undefined => {
|
|
79
|
+
const permissionStore = pikkuState(packageName, 'misc', 'permissions')
|
|
70
80
|
const permission = permissionStore[name]
|
|
71
81
|
if (Array.isArray(permission) && permission.length === 1) {
|
|
72
82
|
return permission[0]
|
|
@@ -74,37 +84,27 @@ const getPermissionByName = (name: string): CorePikkuPermission | undefined => {
|
|
|
74
84
|
return undefined
|
|
75
85
|
}
|
|
76
86
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
* export const adminPermissions = () => addTagPermission('admin', [adminPermission])
|
|
86
|
-
*/
|
|
87
|
-
export const addTagPermission = (
|
|
88
|
-
tag: string,
|
|
89
|
-
permissions: CorePermissionGroup | CorePikkuPermission[],
|
|
90
|
-
packageName: string | null = null
|
|
91
|
-
): CorePermissionGroup | CorePikkuPermission[] => {
|
|
92
|
-
const tagGroups = pikkuState(packageName, 'permissions', 'tagGroup')
|
|
93
|
-
if (tagGroups[tag]) {
|
|
94
|
-
throw new Error(
|
|
95
|
-
`Permissions for tag '${tag}' already exist. Use a different tag or remove the existing permissions first.`
|
|
96
|
-
)
|
|
87
|
+
const globalPermissionsCache: Record<
|
|
88
|
+
string,
|
|
89
|
+
readonly (CorePermissionGroup | CorePikkuPermission)[]
|
|
90
|
+
> = {}
|
|
91
|
+
|
|
92
|
+
export const clearPermissionsCache = () => {
|
|
93
|
+
for (const key of Object.keys(globalPermissionsCache)) {
|
|
94
|
+
delete globalPermissionsCache[key]
|
|
97
95
|
}
|
|
98
|
-
tagGroups[tag] = permissions
|
|
99
|
-
return permissions
|
|
100
96
|
}
|
|
101
97
|
|
|
102
98
|
/**
|
|
103
|
-
* Registers wire-agnostic global permissions.
|
|
104
|
-
*
|
|
105
|
-
*
|
|
99
|
+
* Registers wire-agnostic global permissions. Global permissions are an AND
|
|
100
|
+
* gate: every registered requirement must pass on every function invocation,
|
|
101
|
+
* regardless of transport. A requirement may be a single permission function
|
|
102
|
+
* or a permission group (in which case that group ORs internally, but the
|
|
103
|
+
* group as a whole must still pass).
|
|
106
104
|
*
|
|
107
|
-
*
|
|
105
|
+
* Global permissions never satisfy a function's own `permissions` — the two
|
|
106
|
+
* are independent gates that both have to pass, so a broad global can only
|
|
107
|
+
* ever narrow access.
|
|
108
108
|
*
|
|
109
109
|
* @example
|
|
110
110
|
* addGlobalPermission([signedInUser])
|
|
@@ -122,237 +122,113 @@ export const addGlobalPermission = (
|
|
|
122
122
|
} else {
|
|
123
123
|
state.push(permissions)
|
|
124
124
|
}
|
|
125
|
+
clearPermissionsCache()
|
|
125
126
|
return permissions
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
export const clearPermissionsCache = () => {
|
|
146
|
-
for (const key of Object.keys(
|
|
147
|
-
combinedPermissionsCache
|
|
148
|
-
) as PikkuWiringTypes[]) {
|
|
149
|
-
combinedPermissionsCache[key] = {}
|
|
150
|
-
}
|
|
129
|
+
/**
|
|
130
|
+
* @deprecated Tag-level permissions were removed in #972 — permissions are now
|
|
131
|
+
* function-scoped only (declare them on the function via `pikkuFunc({ permissions })`).
|
|
132
|
+
* This throwing stub exists solely so the pinned bootstrap CLI (which still
|
|
133
|
+
* generates an `addTagPermission` wrapper) can resolve the import at build time;
|
|
134
|
+
* it is never called. Delete once `PIKKU_CLI_VERSION` in the CLI build is bumped
|
|
135
|
+
* past the release that removes tag permissions.
|
|
136
|
+
*/
|
|
137
|
+
export const addTagPermission = (
|
|
138
|
+
_tag: string,
|
|
139
|
+
_permissions: CorePermissionGroup | CorePikkuPermission[],
|
|
140
|
+
_packageName: string | null = null
|
|
141
|
+
): never => {
|
|
142
|
+
throw new Error(
|
|
143
|
+
'addTagPermission was removed in #972 — tag-level permissions no longer exist. Declare permissions on the function definition instead: pikkuFunc({ permissions }).'
|
|
144
|
+
)
|
|
151
145
|
}
|
|
152
146
|
|
|
153
147
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* (those are passed separately as wirePermissions).
|
|
159
|
-
*
|
|
160
|
-
* @param {object} options - Configuration object for combining permissions.
|
|
161
|
-
* @param {PermissionMetadata[] | undefined} options.wireInheritedPermissions - Metadata from wiring (HTTP + tags + wire with tags).
|
|
162
|
-
* @param {CorePermissionGroup | CorePikkuPermission[] | undefined} options.wirePermissions - Inline wire permissions.
|
|
163
|
-
* @param {PermissionMetadata[] | undefined} options.funcInheritedPermissions - Function permissions metadata (only tags).
|
|
164
|
-
* @param {CorePermissionGroup | CorePikkuPermission[] | undefined} options.funcPermissions - Inline function permissions.
|
|
165
|
-
* @returns {(CorePermissionGroup | CorePikkuPermission)[]} Combined array of resolved permissions.
|
|
166
|
-
*
|
|
167
|
-
* @example
|
|
168
|
-
* ```typescript
|
|
169
|
-
* const combined = combinePermissions(wireType, wireId, {
|
|
170
|
-
* wireInheritedPermissions: meta.permissions,
|
|
171
|
-
* wirePermissions: inlinePermissions,
|
|
172
|
-
* funcInheritedPermissions: funcMeta.permissions,
|
|
173
|
-
* funcPermissions: funcConfig.permissions
|
|
174
|
-
* })
|
|
175
|
-
* ```
|
|
148
|
+
* Resolves the registered global permission requirements for a package,
|
|
149
|
+
* caching the resolved array per package. The cache is keyed by package name
|
|
150
|
+
* (an empty string for the root package) so packages never collide, and it is
|
|
151
|
+
* cleared whenever globals are registered or on hot reload.
|
|
176
152
|
*/
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
uid: string,
|
|
180
|
-
{
|
|
181
|
-
wireInheritedPermissions,
|
|
182
|
-
wirePermissions,
|
|
183
|
-
funcInheritedPermissions,
|
|
184
|
-
funcPermissions,
|
|
185
|
-
packageName = null,
|
|
186
|
-
}: {
|
|
187
|
-
wireInheritedPermissions?: PermissionMetadata[]
|
|
188
|
-
wirePermissions?: CorePermissionGroup | CorePikkuPermission[]
|
|
189
|
-
funcInheritedPermissions?: PermissionMetadata[]
|
|
190
|
-
funcPermissions?: CorePermissionGroup | CorePikkuPermission[]
|
|
191
|
-
packageName?: string | null
|
|
192
|
-
} = {}
|
|
153
|
+
const resolveGlobalPermissions = (
|
|
154
|
+
packageName: string | null
|
|
193
155
|
): readonly (CorePermissionGroup | CorePikkuPermission)[] => {
|
|
194
|
-
|
|
195
|
-
|
|
156
|
+
const key = packageName ?? ''
|
|
157
|
+
const cached = globalPermissionsCache[key]
|
|
158
|
+
if (cached) {
|
|
159
|
+
return cached
|
|
196
160
|
}
|
|
197
|
-
|
|
198
|
-
const resolved: (CorePermissionGroup | CorePikkuPermission)[] = []
|
|
199
|
-
|
|
200
161
|
const globals = pikkuState(
|
|
201
162
|
packageName,
|
|
202
163
|
'permissions',
|
|
203
164
|
'global'
|
|
204
165
|
) as unknown as (CorePermissionGroup | CorePikkuPermission)[]
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (wireInheritedPermissions) {
|
|
210
|
-
for (const meta of wireInheritedPermissions) {
|
|
211
|
-
if (meta.type === 'http') {
|
|
212
|
-
// Look up HTTP permission group from pikkuState
|
|
213
|
-
const group = pikkuState(packageName, 'permissions', 'httpGroup')[
|
|
214
|
-
meta.route
|
|
215
|
-
]
|
|
216
|
-
if (group) {
|
|
217
|
-
if (Array.isArray(group)) {
|
|
218
|
-
resolved.push(...group)
|
|
219
|
-
} else {
|
|
220
|
-
resolved.push(group)
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
} else if (meta.type === 'tag') {
|
|
224
|
-
const groups = getTagGroups(
|
|
225
|
-
pikkuState(packageName, 'permissions', 'tagGroup'),
|
|
226
|
-
meta.tag
|
|
227
|
-
)
|
|
228
|
-
for (const group of groups) {
|
|
229
|
-
if (Array.isArray(group)) {
|
|
230
|
-
resolved.push(...group)
|
|
231
|
-
} else {
|
|
232
|
-
resolved.push(group)
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
} else if (meta.type === 'wire') {
|
|
236
|
-
// Individual wire permission (exported, not inline)
|
|
237
|
-
const permission = getPermissionByName(meta.name)
|
|
238
|
-
if (permission) {
|
|
239
|
-
resolved.push(permission)
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// 2. Add inline wire permissions
|
|
246
|
-
if (wirePermissions) {
|
|
247
|
-
if (Array.isArray(wirePermissions)) {
|
|
248
|
-
resolved.push(...wirePermissions)
|
|
249
|
-
} else {
|
|
250
|
-
resolved.push(wirePermissions)
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// 3. Resolve function inherited permissions (only tags, wire permissions already handled)
|
|
255
|
-
if (funcInheritedPermissions) {
|
|
256
|
-
for (const meta of funcInheritedPermissions) {
|
|
257
|
-
if (meta.type === 'tag') {
|
|
258
|
-
const groups = getTagGroups(
|
|
259
|
-
pikkuState(packageName, 'permissions', 'tagGroup'),
|
|
260
|
-
meta.tag
|
|
261
|
-
)
|
|
262
|
-
for (const group of groups) {
|
|
263
|
-
if (Array.isArray(group)) {
|
|
264
|
-
resolved.push(...group)
|
|
265
|
-
} else {
|
|
266
|
-
resolved.push(group)
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
// Note: wire permissions are already handled in wireInheritedPermissions
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// 4. Add inline function permissions
|
|
275
|
-
if (funcPermissions) {
|
|
276
|
-
if (Array.isArray(funcPermissions)) {
|
|
277
|
-
resolved.push(...funcPermissions)
|
|
278
|
-
} else {
|
|
279
|
-
resolved.push(funcPermissions)
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Deduplicate and freeze
|
|
284
|
-
combinedPermissionsCache[wireType][uid] = freezeDedupe(resolved) as readonly (
|
|
285
|
-
| CorePermissionGroup
|
|
286
|
-
| CorePikkuPermission
|
|
287
|
-
)[]
|
|
288
|
-
|
|
289
|
-
return combinedPermissionsCache[wireType][uid]
|
|
166
|
+
const resolved = globals && globals.length > 0 ? [...globals] : []
|
|
167
|
+
globalPermissionsCache[key] = resolved
|
|
168
|
+
return resolved
|
|
290
169
|
}
|
|
291
170
|
|
|
171
|
+
const asGroup = (
|
|
172
|
+
entry: CorePermissionGroup | CorePikkuPermission
|
|
173
|
+
): CorePermissionGroup =>
|
|
174
|
+
typeof entry === 'function' ? { permission: entry } : entry
|
|
175
|
+
|
|
292
176
|
/**
|
|
293
|
-
* Runs
|
|
294
|
-
*
|
|
177
|
+
* Runs authorization for a function invocation. Two independent gates, both of
|
|
178
|
+
* which must pass:
|
|
179
|
+
*
|
|
180
|
+
* 1. Global permissions — AND. Every registered global requirement must pass.
|
|
181
|
+
* 2. Function permissions — OR. The function's own `permissions` group must be
|
|
182
|
+
* satisfied (see {@link verifyPermissions}).
|
|
183
|
+
*
|
|
184
|
+
* A passing global requirement never contributes to the function gate, so a
|
|
185
|
+
* broad global like `signedIn` can't satisfy an admin-only function.
|
|
295
186
|
*/
|
|
296
|
-
export const runPermissions = async (
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
data: any
|
|
316
|
-
packageName?: string | null
|
|
187
|
+
export const runPermissions = async ({
|
|
188
|
+
funcPermissions,
|
|
189
|
+
services,
|
|
190
|
+
wire,
|
|
191
|
+
data,
|
|
192
|
+
packageName = null,
|
|
193
|
+
}: {
|
|
194
|
+
funcPermissions?: CorePermissionGroup | CorePikkuPermission[]
|
|
195
|
+
services: CoreServices
|
|
196
|
+
wire: PikkuWire<any, never, any, CoreUserSession, never, never, never>
|
|
197
|
+
data: any
|
|
198
|
+
packageName?: string | null
|
|
199
|
+
}) => {
|
|
200
|
+
const globals = resolveGlobalPermissions(packageName)
|
|
201
|
+
for (const entry of globals) {
|
|
202
|
+
if (!(await verifyPermissions(asGroup(entry), services, data, wire))) {
|
|
203
|
+
services.logger.debug('Permission denied - global permission')
|
|
204
|
+
throw new ForbiddenError('Permission denied')
|
|
205
|
+
}
|
|
317
206
|
}
|
|
318
|
-
) => {
|
|
319
|
-
// Combine all permissions: wireInheritedPermissions → wirePermissions → funcInheritedPermissions → funcPermissions
|
|
320
|
-
const allPermissions = combinePermissions(wireType, uid, {
|
|
321
|
-
wireInheritedPermissions,
|
|
322
|
-
wirePermissions,
|
|
323
|
-
funcInheritedPermissions,
|
|
324
|
-
funcPermissions,
|
|
325
|
-
packageName,
|
|
326
|
-
})
|
|
327
207
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
)
|
|
337
|
-
if (result) {
|
|
338
|
-
return
|
|
208
|
+
if (funcPermissions) {
|
|
209
|
+
const group = Array.isArray(funcPermissions)
|
|
210
|
+
? { permissions: funcPermissions }
|
|
211
|
+
: funcPermissions
|
|
212
|
+
if (group && Object.keys(group).length > 0) {
|
|
213
|
+
if (!(await verifyPermissions(group, services, data, wire))) {
|
|
214
|
+
services.logger.debug('Permission denied - function permission')
|
|
215
|
+
throw new ForbiddenError('Permission denied')
|
|
339
216
|
}
|
|
340
217
|
}
|
|
341
|
-
services.logger.debug('Permission denied - combined permissions')
|
|
342
|
-
throw new ForbiddenError('Permission denied')
|
|
343
218
|
}
|
|
344
219
|
}
|
|
345
220
|
|
|
346
221
|
/**
|
|
347
222
|
* Checks whether a session passes the auth checks (pikkuAuth only) for a
|
|
348
223
|
* given function/agent. Skips pikkuPermission checks since those require
|
|
349
|
-
* request data which isn't available at filter time.
|
|
224
|
+
* request data which isn't available at filter time. Global auth requirements
|
|
225
|
+
* are included so a filtered list honours app-wide auth.
|
|
350
226
|
*
|
|
351
227
|
* @param funcPermissions - The PermissionMetadata[] from function or agent metadata
|
|
352
228
|
* @param session - The user's session
|
|
353
229
|
* @param services - Singleton services
|
|
354
230
|
* @param packageName - Optional package namespace
|
|
355
|
-
* @returns true if the session passes
|
|
231
|
+
* @returns true if the session passes the auth checks (or no auth checks exist)
|
|
356
232
|
*/
|
|
357
233
|
export const checkAuthPermissions = async (
|
|
358
234
|
funcPermissions: PermissionMetadata[] | undefined,
|
|
@@ -360,19 +236,6 @@ export const checkAuthPermissions = async (
|
|
|
360
236
|
services: CoreServices,
|
|
361
237
|
packageName: string | null = null
|
|
362
238
|
): Promise<boolean> => {
|
|
363
|
-
if (!funcPermissions?.length) return true
|
|
364
|
-
|
|
365
|
-
const allPermissions = combinePermissions(
|
|
366
|
-
'agent',
|
|
367
|
-
`authcheck:${Math.random()}`,
|
|
368
|
-
{
|
|
369
|
-
funcInheritedPermissions: funcPermissions,
|
|
370
|
-
packageName,
|
|
371
|
-
}
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
if (allPermissions.length === 0) return true
|
|
375
|
-
|
|
376
239
|
const wire = { session } as unknown as PikkuWire<
|
|
377
240
|
any,
|
|
378
241
|
never,
|
|
@@ -382,15 +245,18 @@ export const checkAuthPermissions = async (
|
|
|
382
245
|
never
|
|
383
246
|
>
|
|
384
247
|
|
|
385
|
-
//
|
|
248
|
+
// Collect auth-only (pikkuAuth) predicates from globals and the function's
|
|
249
|
+
// referenced permissions. Data-dependent permissions are ignored — they
|
|
250
|
+
// can't be evaluated without request data at filter time.
|
|
386
251
|
const authPerms: CorePikkuPermission<any, any, any>[] = []
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
252
|
+
|
|
253
|
+
const collect = (perm: CorePermissionGroup | CorePikkuPermission) => {
|
|
254
|
+
if (typeof perm === 'function') {
|
|
255
|
+
if ((perm as any).__pikkuAuth) {
|
|
256
|
+
authPerms.push(perm)
|
|
391
257
|
}
|
|
392
|
-
} else if (
|
|
393
|
-
for (const funcs of Object.values(
|
|
258
|
+
} else if (perm && typeof perm === 'object') {
|
|
259
|
+
for (const funcs of Object.values(perm)) {
|
|
394
260
|
const arr = Array.isArray(funcs) ? funcs : [funcs]
|
|
395
261
|
for (const fn of arr) {
|
|
396
262
|
if (typeof fn === 'function' && (fn as any).__pikkuAuth) {
|
|
@@ -401,10 +267,24 @@ export const checkAuthPermissions = async (
|
|
|
401
267
|
}
|
|
402
268
|
}
|
|
403
269
|
|
|
270
|
+
for (const entry of resolveGlobalPermissions(packageName)) {
|
|
271
|
+
collect(entry)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (funcPermissions?.length) {
|
|
275
|
+
for (const meta of funcPermissions) {
|
|
276
|
+
if (meta.type === 'wire') {
|
|
277
|
+
const permission = getPermissionByName(meta.name, packageName)
|
|
278
|
+
if (permission) {
|
|
279
|
+
collect(permission)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
404
285
|
// No auth permissions = allowed (only data-dependent permissions exist)
|
|
405
286
|
if (authPerms.length === 0) return true
|
|
406
287
|
|
|
407
|
-
// All auth permissions must pass
|
|
408
288
|
for (const perm of authPerms) {
|
|
409
289
|
const result = await perm(services, null, wire)
|
|
410
290
|
if (result) return true
|