@pikku/core 0.9.12-next.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/errors/errors.d.ts +2 -0
- package/dist/errors/errors.js +6 -0
- package/dist/function/function-runner.d.ts +5 -4
- package/dist/function/function-runner.js +10 -4
- package/dist/function/functions.types.d.ts +67 -1
- package/dist/function/functions.types.js +43 -1
- package/dist/index.d.ts +1 -7
- package/dist/index.js +1 -7
- package/dist/middleware/auth-apikey.d.ts +19 -11
- package/dist/middleware/auth-apikey.js +33 -33
- package/dist/middleware/auth-bearer.d.ts +34 -7
- package/dist/middleware/auth-bearer.js +53 -36
- package/dist/middleware/auth-cookie.d.ts +30 -12
- package/dist/middleware/auth-cookie.js +51 -43
- package/dist/middleware/timeout.d.ts +6 -2
- package/dist/middleware/timeout.js +7 -9
- package/dist/middleware-runner.d.ts +0 -18
- package/dist/middleware-runner.js +0 -21
- package/dist/permissions.d.ts +82 -20
- package/dist/permissions.js +175 -62
- package/dist/pikku-state.d.ts +22 -2
- package/dist/pikku-state.js +7 -1
- package/dist/types/core.types.d.ts +58 -1
- package/dist/types/core.types.js +23 -1
- package/dist/utils.d.ts +1 -1
- package/dist/wirings/channel/channel-handler.js +6 -5
- package/dist/wirings/channel/channel-runner.d.ts +3 -1
- package/dist/wirings/channel/channel-runner.js +4 -21
- package/dist/wirings/channel/channel.types.d.ts +12 -13
- package/dist/wirings/cli/channel/cli-channel-runner.js +5 -2
- package/dist/wirings/cli/cli-runner.d.ts +10 -0
- package/dist/wirings/cli/cli-runner.js +44 -20
- package/dist/wirings/cli/cli.types.d.ts +54 -27
- package/dist/wirings/cli/command-parser.js +34 -4
- package/dist/wirings/cli/index.d.ts +1 -1
- package/dist/wirings/cli/index.js +1 -1
- package/dist/wirings/http/http-runner.d.ts +31 -1
- package/dist/wirings/http/http-runner.js +36 -1
- package/dist/wirings/http/http.types.d.ts +2 -1
- package/dist/wirings/http/index.d.ts +1 -1
- package/dist/wirings/http/index.js +1 -1
- package/dist/wirings/mcp/mcp-endpoint-registry.d.ts +1 -1
- package/dist/wirings/mcp/mcp-runner.js +2 -0
- package/dist/wirings/mcp/mcp.types.d.ts +35 -14
- package/dist/wirings/queue/queue-runner.js +1 -0
- package/dist/wirings/scheduler/scheduler-runner.js +1 -0
- package/dist/wirings/scheduler/scheduler.types.d.ts +2 -2
- package/package.json +6 -5
- package/src/errors/errors.ts +6 -0
- package/src/factory-functions.test.ts +60 -1
- package/src/function/function-runner.test.ts +8 -3
- package/src/function/function-runner.ts +18 -5
- package/src/function/functions.types.ts +85 -2
- package/src/index.ts +1 -21
- package/src/middleware/auth-apikey.ts +42 -57
- package/src/middleware/auth-bearer.ts +66 -49
- package/src/middleware/auth-cookie.ts +63 -82
- package/src/middleware/timeout.ts +10 -14
- package/src/middleware-runner.ts +0 -25
- package/src/permissions.test.ts +26 -27
- package/src/permissions.ts +215 -104
- package/src/pikku-state.ts +35 -3
- package/src/types/core.types.ts +70 -3
- package/src/utils.ts +1 -1
- package/src/wirings/channel/channel-handler.ts +11 -10
- package/src/wirings/channel/channel-runner.ts +18 -27
- package/src/wirings/channel/channel.types.ts +24 -15
- package/src/wirings/cli/channel/cli-channel-runner.ts +8 -3
- package/src/wirings/cli/cli-runner.test.ts +80 -59
- package/src/wirings/cli/cli-runner.ts +57 -21
- package/src/wirings/cli/cli.types.ts +74 -44
- package/src/wirings/cli/command-parser.test.ts +91 -86
- package/src/wirings/cli/command-parser.ts +38 -4
- package/src/wirings/cli/index.ts +1 -0
- package/src/wirings/http/http-runner.ts +41 -1
- package/src/wirings/http/http.types.ts +3 -1
- package/src/wirings/http/index.ts +7 -1
- package/src/wirings/mcp/mcp-endpoint-registry.ts +1 -1
- package/src/wirings/mcp/mcp-runner.ts +2 -0
- package/src/wirings/mcp/mcp.types.ts +48 -12
- package/src/wirings/queue/queue-runner.ts +1 -0
- package/src/wirings/scheduler/scheduler-runner.ts +1 -0
- package/src/wirings/scheduler/scheduler.types.ts +2 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import { RequestTimeoutError } from '../errors/errors.js'
|
|
2
|
-
import {
|
|
2
|
+
import { pikkuMiddleware, pikkuMiddlewareFactory } from '../types/core.types.js'
|
|
3
3
|
|
|
4
|
-
export const timeout = (
|
|
5
|
-
|
|
6
|
-
_services,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
setTimeout(() => {
|
|
11
|
-
throw new RequestTimeoutError()
|
|
12
|
-
}, timeout)
|
|
4
|
+
export const timeout = () =>
|
|
5
|
+
pikkuMiddlewareFactory<{ timeout: number }>(({ timeout }) =>
|
|
6
|
+
pikkuMiddleware(async (_services, _interaction, next) => {
|
|
7
|
+
setTimeout(() => {
|
|
8
|
+
throw new RequestTimeoutError()
|
|
9
|
+
}, timeout)
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
11
|
+
await next()
|
|
12
|
+
})
|
|
13
|
+
)
|
package/src/middleware-runner.ts
CHANGED
|
@@ -92,31 +92,6 @@ export const addMiddleware = <PikkuMiddleware extends CorePikkuMiddleware>(
|
|
|
92
92
|
return middleware
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
/**
|
|
96
|
-
* Registers a single middleware function by name in the global middleware store.
|
|
97
|
-
*
|
|
98
|
-
* This function is used by CLI-generated code to register middleware functions
|
|
99
|
-
* that can be referenced by name in metadata. It stores middleware in a special
|
|
100
|
-
* namespace to avoid conflicts with tag-based middleware.
|
|
101
|
-
*
|
|
102
|
-
* @param {string} name - The unique name (pikkuFuncName) of the middleware function.
|
|
103
|
-
* @param {CorePikkuMiddleware} middleware - The middleware function to register.
|
|
104
|
-
*
|
|
105
|
-
* @example
|
|
106
|
-
* ```typescript
|
|
107
|
-
* // Called by CLI-generated pikku-middleware.gen.ts
|
|
108
|
-
* registerMiddleware('authMiddleware_src_middleware_ts_10_5', authMiddleware)
|
|
109
|
-
* registerMiddleware('loggingMiddleware_src_middleware_ts_20_10', loggingMiddleware)
|
|
110
|
-
* ```
|
|
111
|
-
*/
|
|
112
|
-
export const registerMiddleware = (
|
|
113
|
-
name: string,
|
|
114
|
-
middleware: CorePikkuMiddleware
|
|
115
|
-
) => {
|
|
116
|
-
const middlewareStore = pikkuState('misc', 'middleware')
|
|
117
|
-
middlewareStore[name] = [middleware]
|
|
118
|
-
}
|
|
119
|
-
|
|
120
95
|
/**
|
|
121
96
|
* Retrieves a registered middleware function by its name.
|
|
122
97
|
*
|
package/src/permissions.test.ts
CHANGED
|
@@ -181,7 +181,7 @@ describe('runPermissions', () => {
|
|
|
181
181
|
addPermission('wiringTag', [wiringTagPermission])
|
|
182
182
|
|
|
183
183
|
await runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
|
|
184
|
-
|
|
184
|
+
wireInheritedPermissions: [{ type: 'tag', tag: 'wiringTag' }],
|
|
185
185
|
allServices: mockServices,
|
|
186
186
|
data: {},
|
|
187
187
|
session: mockSession,
|
|
@@ -205,7 +205,7 @@ describe('runPermissions', () => {
|
|
|
205
205
|
addPermission('wiringTag', [wiringTagPermission])
|
|
206
206
|
addPermission('funcTag', [funcTagPermission])
|
|
207
207
|
|
|
208
|
-
const
|
|
208
|
+
const wirePermissions: CorePermissionGroup = {
|
|
209
209
|
permissions: [
|
|
210
210
|
async () => {
|
|
211
211
|
executionOrder.push('wiringPermission')
|
|
@@ -224,16 +224,16 @@ describe('runPermissions', () => {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
await runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
227
|
+
wireInheritedPermissions: [{ type: 'tag', tag: 'wiringTag' }],
|
|
228
|
+
wirePermissions,
|
|
229
|
+
funcInheritedPermissions: [{ type: 'tag', tag: 'funcTag' }],
|
|
230
230
|
funcPermissions,
|
|
231
231
|
allServices: mockServices,
|
|
232
232
|
data: {},
|
|
233
233
|
session: mockSession,
|
|
234
234
|
})
|
|
235
235
|
|
|
236
|
-
// Order:
|
|
236
|
+
// Order: wireInheritedPermissions (wiringTag) → wirePermissions → funcInheritedPermissions (funcTag) → funcPermissions
|
|
237
237
|
assert.deepEqual(executionOrder, [
|
|
238
238
|
'wiringTag',
|
|
239
239
|
'wiringPermission',
|
|
@@ -250,7 +250,7 @@ describe('runPermissions', () => {
|
|
|
250
250
|
|
|
251
251
|
// Should not throw because at least one permission passes
|
|
252
252
|
await runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
|
|
253
|
-
|
|
253
|
+
wireInheritedPermissions: [{ type: 'tag', tag: 'atLeastOneTestTag' }],
|
|
254
254
|
allServices: mockServices,
|
|
255
255
|
data: {},
|
|
256
256
|
session: mockSession,
|
|
@@ -265,7 +265,7 @@ describe('runPermissions', () => {
|
|
|
265
265
|
|
|
266
266
|
await assert.rejects(
|
|
267
267
|
runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
|
|
268
|
-
|
|
268
|
+
wireInheritedPermissions: [{ type: 'tag', tag: 'allFailTestTag' }],
|
|
269
269
|
allServices: mockServices,
|
|
270
270
|
data: {},
|
|
271
271
|
session: mockSession,
|
|
@@ -277,13 +277,13 @@ describe('runPermissions', () => {
|
|
|
277
277
|
})
|
|
278
278
|
|
|
279
279
|
test('should throw error when wiring permissions fail', async () => {
|
|
280
|
-
const
|
|
280
|
+
const wirePermissions: CorePermissionGroup = {
|
|
281
281
|
permissions: [async () => false],
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
await assert.rejects(
|
|
285
285
|
runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
|
|
286
|
-
|
|
286
|
+
wirePermissions,
|
|
287
287
|
allServices: mockServices,
|
|
288
288
|
data: {},
|
|
289
289
|
session: mockSession,
|
|
@@ -301,7 +301,7 @@ describe('runPermissions', () => {
|
|
|
301
301
|
|
|
302
302
|
await assert.rejects(
|
|
303
303
|
runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
|
|
304
|
-
|
|
304
|
+
funcInheritedPermissions: [{ type: 'tag', tag: 'funcTag' }],
|
|
305
305
|
allServices: mockServices,
|
|
306
306
|
data: {},
|
|
307
307
|
session: mockSession,
|
|
@@ -330,7 +330,7 @@ describe('runPermissions', () => {
|
|
|
330
330
|
)
|
|
331
331
|
})
|
|
332
332
|
|
|
333
|
-
test('should
|
|
333
|
+
test('should evaluate all permissions and pass if at least one succeeds', async () => {
|
|
334
334
|
const executionOrder: string[] = []
|
|
335
335
|
|
|
336
336
|
const failingWiringTagPermission = async () => {
|
|
@@ -340,7 +340,7 @@ describe('runPermissions', () => {
|
|
|
340
340
|
|
|
341
341
|
addPermission('failingWiringTag', [failingWiringTagPermission])
|
|
342
342
|
|
|
343
|
-
const
|
|
343
|
+
const wirePermissions: CorePermissionGroup = {
|
|
344
344
|
permissions: [
|
|
345
345
|
async () => {
|
|
346
346
|
executionOrder.push('wiringPermission')
|
|
@@ -349,18 +349,17 @@ describe('runPermissions', () => {
|
|
|
349
349
|
],
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
)
|
|
352
|
+
// Should NOT throw because at least one permission (wirePermissions) passes
|
|
353
|
+
await runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
|
|
354
|
+
wireInheritedPermissions: [{ type: 'tag', tag: 'failingWiringTag' }],
|
|
355
|
+
wirePermissions,
|
|
356
|
+
allServices: mockServices,
|
|
357
|
+
data: {},
|
|
358
|
+
session: mockSession,
|
|
359
|
+
})
|
|
361
360
|
|
|
362
|
-
//
|
|
363
|
-
assert.deepEqual(executionOrder, ['wiringTag'])
|
|
361
|
+
// Both permissions should be evaluated
|
|
362
|
+
assert.deepEqual(executionOrder, ['wiringTag', 'wiringPermission'])
|
|
364
363
|
})
|
|
365
364
|
|
|
366
365
|
test('should handle array permissions in tag-based permissions', async () => {
|
|
@@ -370,7 +369,7 @@ describe('runPermissions', () => {
|
|
|
370
369
|
|
|
371
370
|
// Should not throw because verifyPermissions handles array properly
|
|
372
371
|
await runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
|
|
373
|
-
|
|
372
|
+
wireInheritedPermissions: [{ type: 'tag', tag: 'arrayTestTag' }],
|
|
374
373
|
allServices: mockServices,
|
|
375
374
|
data: {},
|
|
376
375
|
session: mockSession,
|
|
@@ -386,7 +385,7 @@ describe('runPermissions', () => {
|
|
|
386
385
|
|
|
387
386
|
// Should not throw because verifyPermissions handles objects properly
|
|
388
387
|
await runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
|
|
389
|
-
|
|
388
|
+
wireInheritedPermissions: [{ type: 'tag', tag: 'objectTestTag' }],
|
|
390
389
|
allServices: mockServices,
|
|
391
390
|
data: {},
|
|
392
391
|
session: mockSession,
|
|
@@ -410,7 +409,7 @@ describe('runPermissions', () => {
|
|
|
410
409
|
addPermission('paramTestTag', [testPermission])
|
|
411
410
|
|
|
412
411
|
await runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
|
|
413
|
-
|
|
412
|
+
wireInheritedPermissions: [{ type: 'tag', tag: 'paramTestTag' }],
|
|
414
413
|
allServices: mockServices,
|
|
415
414
|
data: testData,
|
|
416
415
|
session: mockSession,
|
package/src/permissions.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
CoreServices,
|
|
3
3
|
CoreUserSession,
|
|
4
4
|
PikkuWiringTypes,
|
|
5
|
+
PermissionMetadata,
|
|
5
6
|
} from './types/core.types.js'
|
|
6
7
|
import {
|
|
7
8
|
CorePermissionGroup,
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
} from './function/functions.types.js'
|
|
10
11
|
import { pikkuState } from './pikku-state.js'
|
|
11
12
|
import { ForbiddenError } from './errors/errors.js'
|
|
13
|
+
import { freezeDedupe } from './utils.js'
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* This function validates permissions by iterating over permission groups and executing the corresponding permission functions. If all functions in at least one group return true, the permission is considered valid.
|
|
@@ -51,43 +53,93 @@ export const verifyPermissions = async (
|
|
|
51
53
|
return false
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Registers a single permission function by name in the global permission store.
|
|
58
|
+
*
|
|
59
|
+
* This function is used by CLI-generated code to register permission functions
|
|
60
|
+
* that can be referenced by name in metadata. It stores permissions in a special
|
|
61
|
+
* namespace to avoid conflicts with tag-based permissions.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} name - The unique name (pikkuFuncName) of the permission function.
|
|
64
|
+
* @param {CorePikkuPermission} permission - The permission function to register.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* // Called by CLI-generated pikku-permissions.gen.ts
|
|
69
|
+
* registerPermission('adminPermission_src_permissions_ts_10_5', adminPermission)
|
|
70
|
+
* registerPermission('readPermission_src_permissions_ts_20_10', readPermission)
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export const registerPermission = (
|
|
74
|
+
name: string,
|
|
75
|
+
permission: CorePikkuPermission<any>
|
|
76
|
+
) => {
|
|
77
|
+
const permissionStore = pikkuState('misc', 'permissions')
|
|
78
|
+
permissionStore[name] = [permission]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Retrieves a registered permission function by its name.
|
|
83
|
+
*
|
|
84
|
+
* This function looks up permissions that was registered with registerPermission.
|
|
85
|
+
* It's used internally by the framework to resolve permission references in metadata.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} name - The unique name (pikkuFuncName) of the permission function.
|
|
88
|
+
* @returns {CorePikkuPermission | undefined} The permission function, or undefined if not found.
|
|
89
|
+
*
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
export const getPermissionByName = (
|
|
93
|
+
name: string
|
|
94
|
+
): CorePikkuPermission | undefined => {
|
|
95
|
+
const permissionStore = pikkuState('misc', 'permissions')
|
|
96
|
+
const permission = permissionStore[name]
|
|
97
|
+
if (Array.isArray(permission) && permission.length === 1) {
|
|
98
|
+
return permission[0]
|
|
99
|
+
}
|
|
100
|
+
return undefined
|
|
101
|
+
}
|
|
102
|
+
|
|
54
103
|
/**
|
|
55
104
|
* Adds global permissions for a specific tag.
|
|
56
105
|
*
|
|
57
106
|
* This function allows you to register permissions that will be applied to
|
|
58
107
|
* any wiring (HTTP, Channel, Queue, Scheduler, MCP) that includes the matching tag.
|
|
59
108
|
*
|
|
109
|
+
* For tree-shaking benefits, wrap in a factory function:
|
|
110
|
+
* `export const x = () => addPermission('tag', [...])`
|
|
111
|
+
*
|
|
60
112
|
* @param {string} tag - The tag that the permissions should apply to.
|
|
61
113
|
* @param {any[]} permissions - The permissions array to apply for the specified tag.
|
|
62
114
|
*
|
|
63
|
-
* @
|
|
115
|
+
* @returns {CorePermissionGroup | CorePikkuPermission[]} The permissions (for chaining/wrapping).
|
|
64
116
|
*
|
|
65
117
|
* @example
|
|
66
118
|
* ```typescript
|
|
67
|
-
* //
|
|
68
|
-
* addPermission('admin', [
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
119
|
+
* // Recommended: tree-shakeable
|
|
120
|
+
* export const adminPermissions = () => addPermission('admin', [
|
|
121
|
+
* adminPermission,
|
|
122
|
+
* rolePermission({ role: 'admin' })
|
|
123
|
+
* ])
|
|
72
124
|
*
|
|
73
|
-
* //
|
|
74
|
-
* addPermission('api', [
|
|
125
|
+
* // Also works: no tree-shaking
|
|
126
|
+
* export const apiPermissions = addPermission('api', [
|
|
127
|
+
* readPermission
|
|
128
|
+
* ])
|
|
75
129
|
* ```
|
|
76
130
|
*/
|
|
77
131
|
export const addPermission = (
|
|
78
132
|
tag: string,
|
|
79
133
|
permissions: CorePermissionGroup | CorePikkuPermission[]
|
|
80
|
-
) => {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
// Check if tag already exists
|
|
84
|
-
if (permissionsStore[tag]) {
|
|
134
|
+
): CorePermissionGroup | CorePikkuPermission[] => {
|
|
135
|
+
const tagGroups = pikkuState('permissions', 'tagGroup')
|
|
136
|
+
if (tagGroups[tag]) {
|
|
85
137
|
throw new Error(
|
|
86
138
|
`Permissions for tag '${tag}' already exist. Use a different tag or remove the existing permissions first.`
|
|
87
139
|
)
|
|
88
140
|
}
|
|
89
|
-
|
|
90
|
-
|
|
141
|
+
tagGroups[tag] = permissions
|
|
142
|
+
return permissions
|
|
91
143
|
}
|
|
92
144
|
|
|
93
145
|
const EMPTY: readonly (CorePermissionGroup | CorePikkuPermission)[] = []
|
|
@@ -114,7 +166,7 @@ export const getPermissionsForTags = (
|
|
|
114
166
|
return EMPTY
|
|
115
167
|
}
|
|
116
168
|
|
|
117
|
-
const permissionsStore = pikkuState('
|
|
169
|
+
const permissionsStore = pikkuState('permissions', 'tagGroup')
|
|
118
170
|
const applicablePermissions: Array<
|
|
119
171
|
CorePermissionGroup | CorePikkuPermission
|
|
120
172
|
> = []
|
|
@@ -134,17 +186,9 @@ export const getPermissionsForTags = (
|
|
|
134
186
|
return applicablePermissions
|
|
135
187
|
}
|
|
136
188
|
|
|
137
|
-
const
|
|
189
|
+
const combinedPermissionsCache: Record<
|
|
138
190
|
PikkuWiringTypes,
|
|
139
|
-
|
|
140
|
-
Record<
|
|
141
|
-
string,
|
|
142
|
-
{
|
|
143
|
-
wiringTags: readonly (CorePermissionGroup | CorePikkuPermission)[]
|
|
144
|
-
funcTags: readonly (CorePermissionGroup | CorePikkuPermission)[]
|
|
145
|
-
}
|
|
146
|
-
>
|
|
147
|
-
>
|
|
191
|
+
Record<string, readonly (CorePermissionGroup | CorePikkuPermission)[]>
|
|
148
192
|
> = {
|
|
149
193
|
[PikkuWiringTypes.http]: {},
|
|
150
194
|
[PikkuWiringTypes.rpc]: {},
|
|
@@ -156,111 +200,178 @@ const permissionCache: Record<
|
|
|
156
200
|
}
|
|
157
201
|
|
|
158
202
|
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
203
|
+
* Combines wiring-specific permissions with function-level permissions.
|
|
204
|
+
*
|
|
205
|
+
* This function resolves permission metadata into actual permission functions and combines them.
|
|
206
|
+
* It filters out wire permissions without tags from inheritedPermissions to avoid duplication
|
|
207
|
+
* (those are passed separately as wirePermissions).
|
|
208
|
+
*
|
|
209
|
+
* @param {object} options - Configuration object for combining permissions.
|
|
210
|
+
* @param {PermissionMetadata[] | undefined} options.wireInheritedPermissions - Metadata from wiring (HTTP + tags + wire with tags).
|
|
211
|
+
* @param {CorePermissionGroup | CorePikkuPermission[] | undefined} options.wirePermissions - Inline wire permissions.
|
|
212
|
+
* @param {PermissionMetadata[] | undefined} options.funcInheritedPermissions - Function permissions metadata (only tags).
|
|
213
|
+
* @param {CorePermissionGroup | CorePikkuPermission[] | undefined} options.funcPermissions - Inline function permissions.
|
|
214
|
+
* @returns {(CorePermissionGroup | CorePikkuPermission)[]} Combined array of resolved permissions.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```typescript
|
|
218
|
+
* const combined = combinePermissions(wireType, wireId, {
|
|
219
|
+
* wireInheritedPermissions: meta.permissions,
|
|
220
|
+
* wirePermissions: inlinePermissions,
|
|
221
|
+
* funcInheritedPermissions: funcMeta.permissions,
|
|
222
|
+
* funcPermissions: funcConfig.permissions
|
|
223
|
+
* })
|
|
224
|
+
* ```
|
|
164
225
|
*/
|
|
165
|
-
export const
|
|
226
|
+
export const combinePermissions = (
|
|
166
227
|
wireType: PikkuWiringTypes,
|
|
167
228
|
uid: string,
|
|
168
229
|
{
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
230
|
+
wireInheritedPermissions,
|
|
231
|
+
wirePermissions,
|
|
232
|
+
funcInheritedPermissions,
|
|
172
233
|
funcPermissions,
|
|
173
|
-
allServices,
|
|
174
|
-
data,
|
|
175
|
-
session,
|
|
176
234
|
}: {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
funcPermissions?: CorePermissionGroup
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
235
|
+
wireInheritedPermissions?: PermissionMetadata[]
|
|
236
|
+
wirePermissions?: CorePermissionGroup | CorePikkuPermission[]
|
|
237
|
+
funcInheritedPermissions?: PermissionMetadata[]
|
|
238
|
+
funcPermissions?: CorePermissionGroup | CorePikkuPermission[]
|
|
239
|
+
} = {}
|
|
240
|
+
): readonly (CorePermissionGroup | CorePikkuPermission)[] => {
|
|
241
|
+
if (combinedPermissionsCache[wireType][uid]) {
|
|
242
|
+
return combinedPermissionsCache[wireType][uid]
|
|
184
243
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
244
|
+
|
|
245
|
+
const resolved: (CorePermissionGroup | CorePikkuPermission)[] = []
|
|
246
|
+
|
|
247
|
+
// 1. Resolve wire inherited permissions (HTTP + tag groups + individual wire permissions)
|
|
248
|
+
if (wireInheritedPermissions) {
|
|
249
|
+
for (const meta of wireInheritedPermissions) {
|
|
250
|
+
if (meta.type === 'http') {
|
|
251
|
+
// Look up HTTP permission group from pikkuState
|
|
252
|
+
const group = pikkuState('permissions', 'httpGroup')[meta.route]
|
|
253
|
+
if (group) {
|
|
254
|
+
if (Array.isArray(group)) {
|
|
255
|
+
resolved.push(...group)
|
|
256
|
+
} else {
|
|
257
|
+
resolved.push(group)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} else if (meta.type === 'tag') {
|
|
261
|
+
// Look up tag permission group from pikkuState
|
|
262
|
+
const group = pikkuState('permissions', 'tagGroup')[meta.tag]
|
|
263
|
+
if (group) {
|
|
264
|
+
if (Array.isArray(group)) {
|
|
265
|
+
resolved.push(...group)
|
|
266
|
+
} else {
|
|
267
|
+
resolved.push(group)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} else if (meta.type === 'wire') {
|
|
271
|
+
// Individual wire permission (exported, not inline)
|
|
272
|
+
const permission = getPermissionByName(meta.name)
|
|
273
|
+
if (permission) {
|
|
274
|
+
resolved.push(permission)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
191
277
|
}
|
|
192
|
-
permissionCache[wireType][uid] = cachedPermission
|
|
193
278
|
}
|
|
194
|
-
let permissioned = true
|
|
195
279
|
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
typeof permissions === 'function' ? { permissions } : permissions,
|
|
203
|
-
allServices,
|
|
204
|
-
data,
|
|
205
|
-
session
|
|
206
|
-
)
|
|
207
|
-
if (result) {
|
|
208
|
-
permissioned = true
|
|
209
|
-
break // At least one passed, we're good at this level
|
|
210
|
-
}
|
|
280
|
+
// 2. Add inline wire permissions
|
|
281
|
+
if (wirePermissions) {
|
|
282
|
+
if (Array.isArray(wirePermissions)) {
|
|
283
|
+
resolved.push(...wirePermissions)
|
|
284
|
+
} else {
|
|
285
|
+
resolved.push(wirePermissions)
|
|
211
286
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 3. Resolve function inherited permissions (only tags, wire permissions already handled)
|
|
290
|
+
if (funcInheritedPermissions) {
|
|
291
|
+
for (const meta of funcInheritedPermissions) {
|
|
292
|
+
if (meta.type === 'tag') {
|
|
293
|
+
// Look up tag permission group from pikkuState
|
|
294
|
+
const group = pikkuState('permissions', 'tagGroup')[meta.tag]
|
|
295
|
+
if (group) {
|
|
296
|
+
if (Array.isArray(group)) {
|
|
297
|
+
resolved.push(...group)
|
|
298
|
+
} else {
|
|
299
|
+
resolved.push(group)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Note: wire permissions are already handled in wireInheritedPermissions
|
|
215
304
|
}
|
|
216
305
|
}
|
|
217
306
|
|
|
218
|
-
//
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
session
|
|
225
|
-
)
|
|
226
|
-
if (!permissioned) {
|
|
227
|
-
allServices.logger.debug('Permission denied - wiring permissions')
|
|
228
|
-
throw new ForbiddenError('Permission denied')
|
|
307
|
+
// 4. Add inline function permissions
|
|
308
|
+
if (funcPermissions) {
|
|
309
|
+
if (Array.isArray(funcPermissions)) {
|
|
310
|
+
resolved.push(...funcPermissions)
|
|
311
|
+
} else {
|
|
312
|
+
resolved.push(funcPermissions)
|
|
229
313
|
}
|
|
230
314
|
}
|
|
231
315
|
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
316
|
+
// Deduplicate and freeze
|
|
317
|
+
combinedPermissionsCache[wireType][uid] = freezeDedupe(resolved) as readonly (
|
|
318
|
+
| CorePermissionGroup
|
|
319
|
+
| CorePikkuPermission
|
|
320
|
+
)[]
|
|
321
|
+
|
|
322
|
+
return combinedPermissionsCache[wireType][uid]
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Runs permission checks using combined permissions from all sources.
|
|
327
|
+
* Combines permissions from wire and function levels, then validates them.
|
|
328
|
+
*/
|
|
329
|
+
export const runPermissions = async (
|
|
330
|
+
wireType: PikkuWiringTypes,
|
|
331
|
+
uid: string,
|
|
332
|
+
{
|
|
333
|
+
wireInheritedPermissions,
|
|
334
|
+
wirePermissions,
|
|
335
|
+
funcInheritedPermissions,
|
|
336
|
+
funcPermissions,
|
|
337
|
+
allServices,
|
|
338
|
+
data,
|
|
339
|
+
session,
|
|
340
|
+
}: {
|
|
341
|
+
wireInheritedPermissions?: PermissionMetadata[]
|
|
342
|
+
wirePermissions?: CorePermissionGroup | CorePikkuPermission[]
|
|
343
|
+
funcInheritedPermissions?: PermissionMetadata[]
|
|
344
|
+
funcPermissions?: CorePermissionGroup | CorePikkuPermission[]
|
|
345
|
+
allServices: CoreServices
|
|
346
|
+
data: any
|
|
347
|
+
session?: CoreUserSession
|
|
348
|
+
}
|
|
349
|
+
) => {
|
|
350
|
+
// Combine all permissions: wireInheritedPermissions → wirePermissions → funcInheritedPermissions → funcPermissions
|
|
351
|
+
const allPermissions = combinePermissions(wireType, uid, {
|
|
352
|
+
wireInheritedPermissions,
|
|
353
|
+
wirePermissions,
|
|
354
|
+
funcInheritedPermissions,
|
|
355
|
+
funcPermissions,
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
// Check all combined permissions - at least one must pass if any exist
|
|
359
|
+
if (allPermissions.length > 0) {
|
|
360
|
+
let permissioned = false
|
|
361
|
+
for (const permission of allPermissions) {
|
|
237
362
|
const result = await verifyPermissions(
|
|
238
|
-
typeof
|
|
363
|
+
typeof permission === 'function' ? { permission } : permission,
|
|
239
364
|
allServices,
|
|
240
365
|
data,
|
|
241
366
|
session
|
|
242
367
|
)
|
|
243
368
|
if (result) {
|
|
244
369
|
permissioned = true
|
|
245
|
-
|
|
370
|
+
// Continue executing all permissions (don't break early)
|
|
246
371
|
}
|
|
247
372
|
}
|
|
248
373
|
if (!permissioned) {
|
|
249
|
-
allServices.logger.debug('Permission denied -
|
|
250
|
-
throw new ForbiddenError('Permission denied')
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// 4. Function permissions - must pass if defined
|
|
255
|
-
if (funcPermissions) {
|
|
256
|
-
permissioned = await verifyPermissions(
|
|
257
|
-
funcPermissions,
|
|
258
|
-
allServices,
|
|
259
|
-
data,
|
|
260
|
-
session
|
|
261
|
-
)
|
|
262
|
-
if (!permissioned) {
|
|
263
|
-
allServices.logger.debug('Permission denied - function permissions')
|
|
374
|
+
allServices.logger.debug('Permission denied - combined permissions')
|
|
264
375
|
throw new ForbiddenError('Permission denied')
|
|
265
376
|
}
|
|
266
377
|
}
|