@pikku/core 0.12.63 → 0.12.66
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 +368 -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 +42 -27
- package/dist/permissions.js +101 -203
- 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/testing/service-tests.js +37 -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 +105 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +249 -11
- package/dist/wirings/ai-agent/ai-agent-runner.js +10 -1
- package/dist/wirings/ai-agent/ai-agent-stream.js +88 -16
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +56 -2
- package/dist/wirings/ai-agent/index.d.ts +2 -1
- package/dist/wirings/ai-agent/index.js +2 -1
- package/dist/wirings/ai-agent/voice-input.d.ts +1 -0
- package/dist/wirings/ai-agent/voice-input.js +5 -41
- 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 +3 -2
- package/dist/wirings/cli/cli.types.d.ts +1 -2
- package/dist/wirings/cli/command-parser.d.ts +2 -0
- package/dist/wirings/cli/command-parser.js +59 -2
- package/dist/wirings/credential/credential.types.d.ts +14 -0
- package/dist/wirings/credential/validate-credential-definitions.js +1 -0
- package/dist/wirings/gateway/gateway-runner.js +100 -50
- package/dist/wirings/gateway/gateway.types.d.ts +8 -5
- 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 +4 -12
- 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/secret/secret.types.d.ts +14 -0
- package/dist/wirings/secret/validate-secret-definitions.js +2 -0
- package/dist/wirings/variable/validate-variable-definitions.js +2 -0
- package/dist/wirings/variable/variable.types.d.ts +14 -0
- package/dist/wirings/workflow/dsl/index.d.ts +1 -1
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +89 -7
- 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 +59 -1
- package/dist/wirings/workflow/pikku-workflow-service.js +187 -0
- package/dist/wirings/workflow/workflow.types.d.ts +1 -3
- package/package.json +6 -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 +160 -373
- package/src/permissions.ts +137 -279
- 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/testing/service-tests.ts +49 -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-authorization.test.ts +204 -0
- package/src/wirings/ai-agent/ai-agent-prepare.test.ts +483 -0
- package/src/wirings/ai-agent/ai-agent-prepare.ts +324 -13
- package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +207 -0
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +48 -1
- package/src/wirings/ai-agent/ai-agent-runner.ts +28 -0
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +301 -10
- package/src/wirings/ai-agent/ai-agent-stream.ts +122 -16
- package/src/wirings/ai-agent/ai-agent.types.ts +57 -0
- package/src/wirings/ai-agent/index.ts +5 -0
- package/src/wirings/ai-agent/voice-input.test.ts +90 -0
- package/src/wirings/ai-agent/voice-input.ts +9 -40
- 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 +4 -2
- package/src/wirings/cli/cli.types.ts +0 -2
- package/src/wirings/cli/command-parser.test.ts +130 -0
- package/src/wirings/cli/command-parser.ts +80 -2
- package/src/wirings/credential/credential.types.ts +14 -0
- package/src/wirings/credential/validate-credential-definitions.ts +1 -0
- package/src/wirings/gateway/gateway-authorization.test.ts +444 -0
- package/src/wirings/gateway/gateway-runner.ts +114 -68
- package/src/wirings/gateway/gateway.types.ts +7 -9
- 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 +7 -14
- 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/secret/secret.types.ts +14 -0
- package/src/wirings/secret/validate-secret-definitions.ts +2 -0
- package/src/wirings/variable/validate-variable-definitions.ts +2 -0
- package/src/wirings/variable/variable.types.ts +14 -0
- package/src/wirings/workflow/dsl/index.ts +4 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +97 -6
- 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 +272 -0
- package/src/wirings/workflow/workflow-on-error.test.ts +154 -0
- package/src/wirings/workflow/workflow-step-session.test.ts +21 -5
- package/src/wirings/workflow/workflow.types.ts +4 -2
- 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,8 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CoreServices,
|
|
3
3
|
CoreUserSession,
|
|
4
|
-
PikkuWiringTypes,
|
|
5
|
-
PermissionMetadata,
|
|
6
4
|
PikkuWire,
|
|
7
5
|
} from './types/core.types.js'
|
|
8
6
|
import type {
|
|
@@ -11,16 +9,25 @@ import type {
|
|
|
11
9
|
} from './function/functions.types.js'
|
|
12
10
|
import { pikkuState } from './pikku-state.js'
|
|
13
11
|
import { ForbiddenError } from './errors/errors.js'
|
|
14
|
-
import { freezeDedupe, getTagGroups } from './utils.js'
|
|
15
12
|
|
|
16
13
|
/**
|
|
17
|
-
*
|
|
14
|
+
* Evaluates a single permission group. A group ORs its branches together and
|
|
15
|
+
* ANDs the entries within a branch: the group passes if at least one branch
|
|
16
|
+
* (an array of permission functions, all of which must return true — or a lone
|
|
17
|
+
* function) is satisfied.
|
|
18
|
+
*
|
|
19
|
+
* This is the only place OR lives in authorization. Everything cross-cutting —
|
|
20
|
+
* global permissions, scopes, and auth — ANDs together; a function's own
|
|
21
|
+
* `permissions` group is the sole place where "owner OR admin"-style
|
|
22
|
+
* alternatives are expressed.
|
|
23
|
+
*
|
|
24
|
+
* @param permissions - The permission group to evaluate.
|
|
18
25
|
* @param services - The core services required for permission validation.
|
|
19
|
-
* @param data - The data to
|
|
26
|
+
* @param data - The request data passed to each permission function.
|
|
20
27
|
* @param wire - The wire object containing request/response context and session.
|
|
21
|
-
* @returns A promise
|
|
28
|
+
* @returns A promise resolving to true if the group is satisfied.
|
|
22
29
|
*/
|
|
23
|
-
const verifyPermissions = async
|
|
30
|
+
const verifyPermissions = async (
|
|
24
31
|
permissions: CorePermissionGroup,
|
|
25
32
|
services: CoreServices,
|
|
26
33
|
data: any,
|
|
@@ -30,7 +37,6 @@ const verifyPermissions = async <Out = any>(
|
|
|
30
37
|
return true
|
|
31
38
|
}
|
|
32
39
|
|
|
33
|
-
let valid = false
|
|
34
40
|
const permissionGroups = Object.values(permissions)
|
|
35
41
|
if (permissionGroups.length === 0) {
|
|
36
42
|
return true
|
|
@@ -42,69 +48,39 @@ const verifyPermissions = async <Out = any>(
|
|
|
42
48
|
funcs.map((func) => func(services, data, wire))
|
|
43
49
|
)
|
|
44
50
|
if (permissioned.every((result) => result)) {
|
|
45
|
-
|
|
51
|
+
return true
|
|
46
52
|
}
|
|
47
53
|
} else {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return true
|
|
54
|
+
if (await funcs(services, data, wire as any)) {
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
52
57
|
}
|
|
53
58
|
}
|
|
54
59
|
return false
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
/**
|
|
58
|
-
* Retrieves a registered permission function by its name.
|
|
59
|
-
*
|
|
60
|
-
* This function looks up permissions that was registered with registerPermission.
|
|
61
|
-
* It's used internally by the framework to resolve permission references in metadata.
|
|
62
|
-
*
|
|
63
|
-
* @param {string} name - The unique name (pikkuFuncId) of the permission function.
|
|
64
|
-
* @returns {CorePikkuPermission | undefined} The permission function, or undefined if not found.
|
|
65
|
-
*
|
|
66
|
-
* @internal
|
|
67
|
-
*/
|
|
68
|
-
const getPermissionByName = (name: string): CorePikkuPermission | undefined => {
|
|
69
|
-
const permissionStore = pikkuState(null, 'misc', 'permissions')
|
|
70
|
-
const permission = permissionStore[name]
|
|
71
|
-
if (Array.isArray(permission) && permission.length === 1) {
|
|
72
|
-
return permission[0]
|
|
73
|
-
}
|
|
74
|
-
return undefined
|
|
75
|
-
}
|
|
76
62
|
|
|
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
|
-
)
|
|
63
|
+
const globalPermissionsCache: Record<
|
|
64
|
+
string,
|
|
65
|
+
readonly (CorePermissionGroup | CorePikkuPermission)[]
|
|
66
|
+
> = {}
|
|
67
|
+
|
|
68
|
+
export const clearPermissionsCache = () => {
|
|
69
|
+
for (const key of Object.keys(globalPermissionsCache)) {
|
|
70
|
+
delete globalPermissionsCache[key]
|
|
97
71
|
}
|
|
98
|
-
tagGroups[tag] = permissions
|
|
99
|
-
return permissions
|
|
100
72
|
}
|
|
101
73
|
|
|
102
74
|
/**
|
|
103
|
-
* Registers wire-agnostic global permissions.
|
|
104
|
-
*
|
|
105
|
-
*
|
|
75
|
+
* Registers wire-agnostic global permissions. Global permissions are an AND
|
|
76
|
+
* gate: every registered requirement must pass on every function invocation,
|
|
77
|
+
* regardless of transport. A requirement may be a single permission function
|
|
78
|
+
* or a permission group (in which case that group ORs internally, but the
|
|
79
|
+
* group as a whole must still pass).
|
|
106
80
|
*
|
|
107
|
-
*
|
|
81
|
+
* Global permissions never satisfy a function's own `permissions` — the two
|
|
82
|
+
* are independent gates that both have to pass, so a broad global can only
|
|
83
|
+
* ever narrow access.
|
|
108
84
|
*
|
|
109
85
|
* @example
|
|
110
86
|
* addGlobalPermission([signedInUser])
|
|
@@ -122,257 +98,129 @@ export const addGlobalPermission = (
|
|
|
122
98
|
} else {
|
|
123
99
|
state.push(permissions)
|
|
124
100
|
}
|
|
101
|
+
clearPermissionsCache()
|
|
125
102
|
return permissions
|
|
126
103
|
}
|
|
127
104
|
|
|
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
|
-
}
|
|
105
|
+
/**
|
|
106
|
+
* @deprecated Tag-level permissions were removed in #972 — permissions are now
|
|
107
|
+
* function-scoped only (declare them on the function via `pikkuFunc({ permissions })`).
|
|
108
|
+
* This throwing stub exists solely so the pinned bootstrap CLI (which still
|
|
109
|
+
* generates an `addTagPermission` wrapper) can resolve the import at build time;
|
|
110
|
+
* it is never called. Delete once `PIKKU_CLI_VERSION` in the CLI build is bumped
|
|
111
|
+
* past the release that removes tag permissions.
|
|
112
|
+
*/
|
|
113
|
+
export const addTagPermission = (
|
|
114
|
+
_tag: string,
|
|
115
|
+
_permissions: CorePermissionGroup | CorePikkuPermission[],
|
|
116
|
+
_packageName: string | null = null
|
|
117
|
+
): never => {
|
|
118
|
+
throw new Error(
|
|
119
|
+
'addTagPermission was removed in #972 — tag-level permissions no longer exist. Declare permissions on the function definition instead: pikkuFunc({ permissions }).'
|
|
120
|
+
)
|
|
151
121
|
}
|
|
152
122
|
|
|
153
123
|
/**
|
|
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
|
-
* ```
|
|
124
|
+
* Resolves the registered global permission requirements for a package,
|
|
125
|
+
* caching the resolved array per package. The cache is keyed by package name
|
|
126
|
+
* (an empty string for the root package) so packages never collide, and it is
|
|
127
|
+
* cleared whenever globals are registered or on hot reload.
|
|
176
128
|
*/
|
|
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
|
-
} = {}
|
|
129
|
+
const resolveGlobalPermissions = (
|
|
130
|
+
packageName: string | null
|
|
193
131
|
): readonly (CorePermissionGroup | CorePikkuPermission)[] => {
|
|
194
|
-
|
|
195
|
-
|
|
132
|
+
const key = packageName ?? ''
|
|
133
|
+
const cached = globalPermissionsCache[key]
|
|
134
|
+
if (cached) {
|
|
135
|
+
return cached
|
|
196
136
|
}
|
|
197
|
-
|
|
198
|
-
const resolved: (CorePermissionGroup | CorePikkuPermission)[] = []
|
|
199
|
-
|
|
200
137
|
const globals = pikkuState(
|
|
201
138
|
packageName,
|
|
202
139
|
'permissions',
|
|
203
140
|
'global'
|
|
204
141
|
) 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]
|
|
142
|
+
const resolved = globals && globals.length > 0 ? [...globals] : []
|
|
143
|
+
globalPermissionsCache[key] = resolved
|
|
144
|
+
return resolved
|
|
290
145
|
}
|
|
291
146
|
|
|
147
|
+
const asGroup = (
|
|
148
|
+
entry: CorePermissionGroup | CorePikkuPermission
|
|
149
|
+
): CorePermissionGroup =>
|
|
150
|
+
typeof entry === 'function' ? { permission: entry } : entry
|
|
151
|
+
|
|
292
152
|
/**
|
|
293
|
-
* Runs
|
|
294
|
-
*
|
|
153
|
+
* Runs authorization for a function invocation. Two independent gates, both of
|
|
154
|
+
* which must pass:
|
|
155
|
+
*
|
|
156
|
+
* 1. Global permissions — AND. Every registered global requirement must pass.
|
|
157
|
+
* 2. Function permissions — OR. The function's own `permissions` group must be
|
|
158
|
+
* satisfied (see {@link verifyPermissions}).
|
|
159
|
+
*
|
|
160
|
+
* A passing global requirement never contributes to the function gate, so a
|
|
161
|
+
* broad global like `signedIn` can't satisfy an admin-only function.
|
|
295
162
|
*/
|
|
296
|
-
export const runPermissions = async (
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
163
|
+
export const runPermissions = async ({
|
|
164
|
+
funcPermissions,
|
|
165
|
+
services,
|
|
166
|
+
wire,
|
|
167
|
+
data,
|
|
168
|
+
packageName = null,
|
|
169
|
+
label = 'function',
|
|
170
|
+
}: {
|
|
171
|
+
funcPermissions?: CorePermissionGroup | CorePikkuPermission[]
|
|
172
|
+
services: CoreServices
|
|
173
|
+
wire: PikkuWire<any, never, any, CoreUserSession, never, never, never>
|
|
174
|
+
data: any
|
|
175
|
+
packageName?: string | null
|
|
176
|
+
/** What the non-global gate is called in debug logs, e.g. 'function', 'agent'. */
|
|
177
|
+
label?: string
|
|
178
|
+
}) => {
|
|
179
|
+
const globals = resolveGlobalPermissions(packageName)
|
|
180
|
+
for (const entry of globals) {
|
|
181
|
+
if (!(await verifyPermissions(asGroup(entry), services, data, wire))) {
|
|
182
|
+
services.logger.debug('Permission denied - global permission')
|
|
183
|
+
throw new ForbiddenError('Permission denied')
|
|
184
|
+
}
|
|
317
185
|
}
|
|
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
186
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
)
|
|
337
|
-
if (result) {
|
|
338
|
-
return
|
|
187
|
+
if (funcPermissions) {
|
|
188
|
+
const group = Array.isArray(funcPermissions)
|
|
189
|
+
? { permissions: funcPermissions }
|
|
190
|
+
: funcPermissions
|
|
191
|
+
if (group && Object.keys(group).length > 0) {
|
|
192
|
+
if (!(await verifyPermissions(group, services, data, wire))) {
|
|
193
|
+
services.logger.debug(`Permission denied - ${label} permission`)
|
|
194
|
+
throw new ForbiddenError('Permission denied')
|
|
339
195
|
}
|
|
340
196
|
}
|
|
341
|
-
services.logger.debug('Permission denied - combined permissions')
|
|
342
|
-
throw new ForbiddenError('Permission denied')
|
|
343
197
|
}
|
|
344
198
|
}
|
|
345
199
|
|
|
346
200
|
/**
|
|
347
201
|
* Checks whether a session passes the auth checks (pikkuAuth only) for a
|
|
348
202
|
* given function/agent. Skips pikkuPermission checks since those require
|
|
349
|
-
* request data which isn't available at filter time.
|
|
203
|
+
* request data which isn't available at filter time. Global auth requirements
|
|
204
|
+
* are included so a filtered list honours app-wide auth.
|
|
350
205
|
*
|
|
351
|
-
*
|
|
206
|
+
* `funcPermissions` is the live {@link CorePermissionGroup} from the function or
|
|
207
|
+
* agent config, not the metadata form: the `pikkuAuth` brand only survives on
|
|
208
|
+
* the actual predicate objects, and the by-name registry those metadata entries
|
|
209
|
+
* would resolve against is never populated. Passing metadata here would silently
|
|
210
|
+
* collect nothing and let every gated tool through.
|
|
211
|
+
*
|
|
212
|
+
* @param funcPermissions - The live permission group from the func/agent config
|
|
352
213
|
* @param session - The user's session
|
|
353
214
|
* @param services - Singleton services
|
|
354
215
|
* @param packageName - Optional package namespace
|
|
355
|
-
* @returns true if the session passes
|
|
216
|
+
* @returns true if the session passes the auth checks (or no auth checks exist)
|
|
356
217
|
*/
|
|
357
218
|
export const checkAuthPermissions = async (
|
|
358
|
-
funcPermissions:
|
|
219
|
+
funcPermissions: CorePermissionGroup | undefined,
|
|
359
220
|
session: CoreUserSession,
|
|
360
221
|
services: CoreServices,
|
|
361
222
|
packageName: string | null = null
|
|
362
223
|
): 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
224
|
const wire = { session } as unknown as PikkuWire<
|
|
377
225
|
any,
|
|
378
226
|
never,
|
|
@@ -382,15 +230,18 @@ export const checkAuthPermissions = async (
|
|
|
382
230
|
never
|
|
383
231
|
>
|
|
384
232
|
|
|
385
|
-
//
|
|
233
|
+
// Collect auth-only (pikkuAuth) predicates from globals and the function's
|
|
234
|
+
// referenced permissions. Data-dependent permissions are ignored — they
|
|
235
|
+
// can't be evaluated without request data at filter time.
|
|
386
236
|
const authPerms: CorePikkuPermission<any, any, any>[] = []
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
237
|
+
|
|
238
|
+
const collect = (perm: CorePermissionGroup | CorePikkuPermission) => {
|
|
239
|
+
if (typeof perm === 'function') {
|
|
240
|
+
if ((perm as any).__pikkuAuth) {
|
|
241
|
+
authPerms.push(perm)
|
|
391
242
|
}
|
|
392
|
-
} else if (
|
|
393
|
-
for (const funcs of Object.values(
|
|
243
|
+
} else if (perm && typeof perm === 'object') {
|
|
244
|
+
for (const funcs of Object.values(perm)) {
|
|
394
245
|
const arr = Array.isArray(funcs) ? funcs : [funcs]
|
|
395
246
|
for (const fn of arr) {
|
|
396
247
|
if (typeof fn === 'function' && (fn as any).__pikkuAuth) {
|
|
@@ -401,10 +252,17 @@ export const checkAuthPermissions = async (
|
|
|
401
252
|
}
|
|
402
253
|
}
|
|
403
254
|
|
|
255
|
+
for (const entry of resolveGlobalPermissions(packageName)) {
|
|
256
|
+
collect(entry)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (funcPermissions) {
|
|
260
|
+
collect(funcPermissions)
|
|
261
|
+
}
|
|
262
|
+
|
|
404
263
|
// No auth permissions = allowed (only data-dependent permissions exist)
|
|
405
264
|
if (authPerms.length === 0) return true
|
|
406
265
|
|
|
407
|
-
// All auth permissions must pass
|
|
408
266
|
for (const perm of authPerms) {
|
|
409
267
|
const result = await perm(services, null, wire)
|
|
410
268
|
if (result) return true
|