@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,52 +1,60 @@
|
|
|
1
|
+
import { pikkuMiddleware, pikkuMiddlewareFactory } from '../types/core.types.js';
|
|
1
2
|
import { getRelativeTimeOffsetFromNow, } from '../time-utils.js';
|
|
2
3
|
/**
|
|
3
|
-
* Cookie middleware that
|
|
4
|
+
* Cookie-based session middleware that uses JWT for encoding/decoding session data.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
6
|
+
* Reads session from a cookie on incoming requests and automatically updates the cookie
|
|
7
|
+
* when the session changes (e.g., after login).
|
|
8
|
+
*
|
|
9
|
+
* @param options.name - Cookie name
|
|
10
|
+
* @param options.expiresIn - Cookie expiration time (e.g., { value: 30, unit: 'day' })
|
|
11
|
+
* @param options.options - Cookie serialization options (httpOnly, secure, sameSite, etc.)
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { authCookie } from '@pikku/core/middleware'
|
|
16
|
+
*
|
|
17
|
+
* addHTTPMiddleware([
|
|
18
|
+
* authCookie({
|
|
19
|
+
* name: 'session',
|
|
20
|
+
* expiresIn: { value: 30, unit: 'day' },
|
|
21
|
+
* options: {
|
|
22
|
+
* httpOnly: true,
|
|
23
|
+
* secure: true,
|
|
24
|
+
* sameSite: 'strict',
|
|
25
|
+
* path: '/'
|
|
26
|
+
* }
|
|
27
|
+
* })
|
|
28
|
+
* ])
|
|
29
|
+
* ```
|
|
7
30
|
*/
|
|
8
|
-
export const authCookie = ({ name,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
if (cookieValue) {
|
|
17
|
-
if (jwt) {
|
|
18
|
-
if (!jwtService) {
|
|
19
|
-
throw new Error('JWT service is required for JWT decoding.');
|
|
20
|
-
}
|
|
21
|
-
userSession = await jwtService.decode(cookieValue);
|
|
22
|
-
}
|
|
23
|
-
else if (getSessionForCookieValue) {
|
|
24
|
-
userSession = await getSessionForCookieValue(services, cookieValue, name);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
31
|
+
export const authCookie = pikkuMiddlewareFactory(({ name, options, expiresIn }) => pikkuMiddleware(async ({ userSession: userSessionService, jwt: jwtService, logger }, { http }, next) => {
|
|
32
|
+
if (!http?.request || userSessionService.get()) {
|
|
33
|
+
return next();
|
|
34
|
+
}
|
|
35
|
+
// Try to decode session from cookie
|
|
36
|
+
const cookieValue = http.request.cookie(name);
|
|
37
|
+
if (cookieValue && jwtService) {
|
|
38
|
+
const userSession = await jwtService.decode(cookieValue);
|
|
27
39
|
if (userSession) {
|
|
28
40
|
userSessionService.setInitial(userSession);
|
|
29
41
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
}
|
|
43
|
+
await next();
|
|
44
|
+
// Set the cookie in the response if the session has changed
|
|
45
|
+
if (!http?.response) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (userSessionService.sessionChanged) {
|
|
49
|
+
const session = userSessionService.get();
|
|
50
|
+
if (jwtService) {
|
|
51
|
+
http.response.cookie(name, await jwtService.encode(expiresIn, session), {
|
|
52
|
+
...options,
|
|
53
|
+
expires: getRelativeTimeOffsetFromNow(expiresIn),
|
|
54
|
+
});
|
|
34
55
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (jwt) {
|
|
38
|
-
if (!jwtService) {
|
|
39
|
-
throw new Error('JWT service is required for JWT encoding.');
|
|
40
|
-
}
|
|
41
|
-
http.response.cookie(name, await jwtService.encode(expiresIn, session), {
|
|
42
|
-
...options,
|
|
43
|
-
expires: getRelativeTimeOffsetFromNow(expiresIn),
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
logger.warn('No JWT service available, unable to set cookie');
|
|
48
|
-
}
|
|
56
|
+
else {
|
|
57
|
+
logger.warn('No JWT service available, unable to set cookie');
|
|
49
58
|
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
};
|
|
59
|
+
}
|
|
60
|
+
}));
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export declare const timeout: () => import("../types/core.types.js").CorePikkuMiddlewareFactory<{
|
|
2
|
+
timeout: number;
|
|
3
|
+
}, import("../types/core.types.js").CoreSingletonServices<{
|
|
4
|
+
logLevel?: import("../index.js").LogLevel;
|
|
5
|
+
secrets?: {};
|
|
6
|
+
}>, import("../types/core.types.js").CoreUserSession>;
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { RequestTimeoutError } from '../errors/errors.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return middleware;
|
|
10
|
-
};
|
|
2
|
+
import { pikkuMiddleware, pikkuMiddlewareFactory } from '../types/core.types.js';
|
|
3
|
+
export const timeout = () => pikkuMiddlewareFactory(({ timeout }) => pikkuMiddleware(async (_services, _interaction, next) => {
|
|
4
|
+
setTimeout(() => {
|
|
5
|
+
throw new RequestTimeoutError();
|
|
6
|
+
}, timeout);
|
|
7
|
+
await next();
|
|
8
|
+
}));
|
|
@@ -54,24 +54,6 @@ export declare const runMiddleware: <Middleware extends CorePikkuMiddleware>(ser
|
|
|
54
54
|
* ```
|
|
55
55
|
*/
|
|
56
56
|
export declare const addMiddleware: <PikkuMiddleware extends CorePikkuMiddleware>(tag: string, middleware: CorePikkuMiddlewareGroup) => CorePikkuMiddlewareGroup;
|
|
57
|
-
/**
|
|
58
|
-
* Registers a single middleware function by name in the global middleware store.
|
|
59
|
-
*
|
|
60
|
-
* This function is used by CLI-generated code to register middleware functions
|
|
61
|
-
* that can be referenced by name in metadata. It stores middleware in a special
|
|
62
|
-
* namespace to avoid conflicts with tag-based middleware.
|
|
63
|
-
*
|
|
64
|
-
* @param {string} name - The unique name (pikkuFuncName) of the middleware function.
|
|
65
|
-
* @param {CorePikkuMiddleware} middleware - The middleware function to register.
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* ```typescript
|
|
69
|
-
* // Called by CLI-generated pikku-middleware.gen.ts
|
|
70
|
-
* registerMiddleware('authMiddleware_src_middleware_ts_10_5', authMiddleware)
|
|
71
|
-
* registerMiddleware('loggingMiddleware_src_middleware_ts_20_10', loggingMiddleware)
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
74
|
-
export declare const registerMiddleware: (name: string, middleware: CorePikkuMiddleware) => void;
|
|
75
57
|
/**
|
|
76
58
|
* Retrieves a registered middleware function by its name.
|
|
77
59
|
*
|
|
@@ -70,27 +70,6 @@ export const addMiddleware = (tag, middleware) => {
|
|
|
70
70
|
tagGroups[tag] = middleware;
|
|
71
71
|
return middleware;
|
|
72
72
|
};
|
|
73
|
-
/**
|
|
74
|
-
* Registers a single middleware function by name in the global middleware store.
|
|
75
|
-
*
|
|
76
|
-
* This function is used by CLI-generated code to register middleware functions
|
|
77
|
-
* that can be referenced by name in metadata. It stores middleware in a special
|
|
78
|
-
* namespace to avoid conflicts with tag-based middleware.
|
|
79
|
-
*
|
|
80
|
-
* @param {string} name - The unique name (pikkuFuncName) of the middleware function.
|
|
81
|
-
* @param {CorePikkuMiddleware} middleware - The middleware function to register.
|
|
82
|
-
*
|
|
83
|
-
* @example
|
|
84
|
-
* ```typescript
|
|
85
|
-
* // Called by CLI-generated pikku-middleware.gen.ts
|
|
86
|
-
* registerMiddleware('authMiddleware_src_middleware_ts_10_5', authMiddleware)
|
|
87
|
-
* registerMiddleware('loggingMiddleware_src_middleware_ts_20_10', loggingMiddleware)
|
|
88
|
-
* ```
|
|
89
|
-
*/
|
|
90
|
-
export const registerMiddleware = (name, middleware) => {
|
|
91
|
-
const middlewareStore = pikkuState('misc', 'middleware');
|
|
92
|
-
middlewareStore[name] = [middleware];
|
|
93
|
-
};
|
|
94
73
|
/**
|
|
95
74
|
* Retrieves a registered middleware function by its name.
|
|
96
75
|
*
|
package/dist/permissions.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CoreServices, CoreUserSession, PikkuWiringTypes } from './types/core.types.js';
|
|
1
|
+
import { CoreServices, CoreUserSession, PikkuWiringTypes, PermissionMetadata } from './types/core.types.js';
|
|
2
2
|
import { CorePermissionGroup, CorePikkuPermission } from './function/functions.types.js';
|
|
3
3
|
/**
|
|
4
4
|
* 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.
|
|
@@ -8,30 +8,65 @@ import { CorePermissionGroup, CorePikkuPermission } from './function/functions.t
|
|
|
8
8
|
* @returns A promise that resolves to void.
|
|
9
9
|
*/
|
|
10
10
|
export declare const verifyPermissions: (permissions: CorePermissionGroup, services: CoreServices, data: any, session?: CoreUserSession) => Promise<boolean>;
|
|
11
|
+
/**
|
|
12
|
+
* Registers a single permission function by name in the global permission store.
|
|
13
|
+
*
|
|
14
|
+
* This function is used by CLI-generated code to register permission functions
|
|
15
|
+
* that can be referenced by name in metadata. It stores permissions in a special
|
|
16
|
+
* namespace to avoid conflicts with tag-based permissions.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} name - The unique name (pikkuFuncName) of the permission function.
|
|
19
|
+
* @param {CorePikkuPermission} permission - The permission function to register.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Called by CLI-generated pikku-permissions.gen.ts
|
|
24
|
+
* registerPermission('adminPermission_src_permissions_ts_10_5', adminPermission)
|
|
25
|
+
* registerPermission('readPermission_src_permissions_ts_20_10', readPermission)
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare const registerPermission: (name: string, permission: CorePikkuPermission<any>) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Retrieves a registered permission function by its name.
|
|
31
|
+
*
|
|
32
|
+
* This function looks up permissions that was registered with registerPermission.
|
|
33
|
+
* It's used internally by the framework to resolve permission references in metadata.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} name - The unique name (pikkuFuncName) of the permission function.
|
|
36
|
+
* @returns {CorePikkuPermission | undefined} The permission function, or undefined if not found.
|
|
37
|
+
*
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export declare const getPermissionByName: (name: string) => CorePikkuPermission | undefined;
|
|
11
41
|
/**
|
|
12
42
|
* Adds global permissions for a specific tag.
|
|
13
43
|
*
|
|
14
44
|
* This function allows you to register permissions that will be applied to
|
|
15
45
|
* any wiring (HTTP, Channel, Queue, Scheduler, MCP) that includes the matching tag.
|
|
16
46
|
*
|
|
47
|
+
* For tree-shaking benefits, wrap in a factory function:
|
|
48
|
+
* `export const x = () => addPermission('tag', [...])`
|
|
49
|
+
*
|
|
17
50
|
* @param {string} tag - The tag that the permissions should apply to.
|
|
18
51
|
* @param {any[]} permissions - The permissions array to apply for the specified tag.
|
|
19
52
|
*
|
|
20
|
-
* @
|
|
53
|
+
* @returns {CorePermissionGroup | CorePikkuPermission[]} The permissions (for chaining/wrapping).
|
|
21
54
|
*
|
|
22
55
|
* @example
|
|
23
56
|
* ```typescript
|
|
24
|
-
* //
|
|
25
|
-
* addPermission('admin', [
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
57
|
+
* // Recommended: tree-shakeable
|
|
58
|
+
* export const adminPermissions = () => addPermission('admin', [
|
|
59
|
+
* adminPermission,
|
|
60
|
+
* rolePermission({ role: 'admin' })
|
|
61
|
+
* ])
|
|
29
62
|
*
|
|
30
|
-
* //
|
|
31
|
-
* addPermission('api', [
|
|
63
|
+
* // Also works: no tree-shaking
|
|
64
|
+
* export const apiPermissions = addPermission('api', [
|
|
65
|
+
* readPermission
|
|
66
|
+
* ])
|
|
32
67
|
* ```
|
|
33
68
|
*/
|
|
34
|
-
export declare const addPermission: (tag: string, permissions: CorePermissionGroup | CorePikkuPermission[]) =>
|
|
69
|
+
export declare const addPermission: (tag: string, permissions: CorePermissionGroup | CorePikkuPermission[]) => CorePermissionGroup | CorePikkuPermission[];
|
|
35
70
|
/**
|
|
36
71
|
* Retrieves permissions for a given set of tags.
|
|
37
72
|
*
|
|
@@ -49,17 +84,44 @@ export declare const addPermission: (tag: string, permissions: CorePermissionGro
|
|
|
49
84
|
*/
|
|
50
85
|
export declare const getPermissionsForTags: (tags?: string[]) => readonly (CorePermissionGroup | CorePikkuPermission)[];
|
|
51
86
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
87
|
+
* Combines wiring-specific permissions with function-level permissions.
|
|
88
|
+
*
|
|
89
|
+
* This function resolves permission metadata into actual permission functions and combines them.
|
|
90
|
+
* It filters out wire permissions without tags from inheritedPermissions to avoid duplication
|
|
91
|
+
* (those are passed separately as wirePermissions).
|
|
92
|
+
*
|
|
93
|
+
* @param {object} options - Configuration object for combining permissions.
|
|
94
|
+
* @param {PermissionMetadata[] | undefined} options.wireInheritedPermissions - Metadata from wiring (HTTP + tags + wire with tags).
|
|
95
|
+
* @param {CorePermissionGroup | CorePikkuPermission[] | undefined} options.wirePermissions - Inline wire permissions.
|
|
96
|
+
* @param {PermissionMetadata[] | undefined} options.funcInheritedPermissions - Function permissions metadata (only tags).
|
|
97
|
+
* @param {CorePermissionGroup | CorePikkuPermission[] | undefined} options.funcPermissions - Inline function permissions.
|
|
98
|
+
* @returns {(CorePermissionGroup | CorePikkuPermission)[]} Combined array of resolved permissions.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* const combined = combinePermissions(wireType, wireId, {
|
|
103
|
+
* wireInheritedPermissions: meta.permissions,
|
|
104
|
+
* wirePermissions: inlinePermissions,
|
|
105
|
+
* funcInheritedPermissions: funcMeta.permissions,
|
|
106
|
+
* funcPermissions: funcConfig.permissions
|
|
107
|
+
* })
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare const combinePermissions: (wireType: PikkuWiringTypes, uid: string, { wireInheritedPermissions, wirePermissions, funcInheritedPermissions, funcPermissions, }?: {
|
|
111
|
+
wireInheritedPermissions?: PermissionMetadata[];
|
|
112
|
+
wirePermissions?: CorePermissionGroup | CorePikkuPermission[];
|
|
113
|
+
funcInheritedPermissions?: PermissionMetadata[];
|
|
114
|
+
funcPermissions?: CorePermissionGroup | CorePikkuPermission[];
|
|
115
|
+
}) => readonly (CorePermissionGroup | CorePikkuPermission)[];
|
|
116
|
+
/**
|
|
117
|
+
* Runs permission checks using combined permissions from all sources.
|
|
118
|
+
* Combines permissions from wire and function levels, then validates them.
|
|
57
119
|
*/
|
|
58
|
-
export declare const runPermissions: (wireType: PikkuWiringTypes, uid: string, {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
funcPermissions?: CorePermissionGroup;
|
|
120
|
+
export declare const runPermissions: (wireType: PikkuWiringTypes, uid: string, { wireInheritedPermissions, wirePermissions, funcInheritedPermissions, funcPermissions, allServices, data, session, }: {
|
|
121
|
+
wireInheritedPermissions?: PermissionMetadata[];
|
|
122
|
+
wirePermissions?: CorePermissionGroup | CorePikkuPermission[];
|
|
123
|
+
funcInheritedPermissions?: PermissionMetadata[];
|
|
124
|
+
funcPermissions?: CorePermissionGroup | CorePikkuPermission[];
|
|
63
125
|
allServices: CoreServices;
|
|
64
126
|
data: any;
|
|
65
127
|
session?: CoreUserSession;
|
package/dist/permissions.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PikkuWiringTypes, } from './types/core.types.js';
|
|
2
2
|
import { pikkuState } from './pikku-state.js';
|
|
3
3
|
import { ForbiddenError } from './errors/errors.js';
|
|
4
|
+
import { freezeDedupe } from './utils.js';
|
|
4
5
|
/**
|
|
5
6
|
* 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.
|
|
6
7
|
* @param services - The core services required for permission validation.
|
|
@@ -33,36 +34,81 @@ export const verifyPermissions = async (permissions, services, data, session) =>
|
|
|
33
34
|
}
|
|
34
35
|
return false;
|
|
35
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* Registers a single permission function by name in the global permission store.
|
|
39
|
+
*
|
|
40
|
+
* This function is used by CLI-generated code to register permission functions
|
|
41
|
+
* that can be referenced by name in metadata. It stores permissions in a special
|
|
42
|
+
* namespace to avoid conflicts with tag-based permissions.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} name - The unique name (pikkuFuncName) of the permission function.
|
|
45
|
+
* @param {CorePikkuPermission} permission - The permission function to register.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* // Called by CLI-generated pikku-permissions.gen.ts
|
|
50
|
+
* registerPermission('adminPermission_src_permissions_ts_10_5', adminPermission)
|
|
51
|
+
* registerPermission('readPermission_src_permissions_ts_20_10', readPermission)
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export const registerPermission = (name, permission) => {
|
|
55
|
+
const permissionStore = pikkuState('misc', 'permissions');
|
|
56
|
+
permissionStore[name] = [permission];
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Retrieves a registered permission function by its name.
|
|
60
|
+
*
|
|
61
|
+
* This function looks up permissions that was registered with registerPermission.
|
|
62
|
+
* It's used internally by the framework to resolve permission references in metadata.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} name - The unique name (pikkuFuncName) of the permission function.
|
|
65
|
+
* @returns {CorePikkuPermission | undefined} The permission function, or undefined if not found.
|
|
66
|
+
*
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
export const getPermissionByName = (name) => {
|
|
70
|
+
const permissionStore = pikkuState('misc', 'permissions');
|
|
71
|
+
const permission = permissionStore[name];
|
|
72
|
+
if (Array.isArray(permission) && permission.length === 1) {
|
|
73
|
+
return permission[0];
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
};
|
|
36
77
|
/**
|
|
37
78
|
* Adds global permissions for a specific tag.
|
|
38
79
|
*
|
|
39
80
|
* This function allows you to register permissions that will be applied to
|
|
40
81
|
* any wiring (HTTP, Channel, Queue, Scheduler, MCP) that includes the matching tag.
|
|
41
82
|
*
|
|
83
|
+
* For tree-shaking benefits, wrap in a factory function:
|
|
84
|
+
* `export const x = () => addPermission('tag', [...])`
|
|
85
|
+
*
|
|
42
86
|
* @param {string} tag - The tag that the permissions should apply to.
|
|
43
87
|
* @param {any[]} permissions - The permissions array to apply for the specified tag.
|
|
44
88
|
*
|
|
45
|
-
* @
|
|
89
|
+
* @returns {CorePermissionGroup | CorePikkuPermission[]} The permissions (for chaining/wrapping).
|
|
46
90
|
*
|
|
47
91
|
* @example
|
|
48
92
|
* ```typescript
|
|
49
|
-
* //
|
|
50
|
-
* addPermission('admin', [
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
93
|
+
* // Recommended: tree-shakeable
|
|
94
|
+
* export const adminPermissions = () => addPermission('admin', [
|
|
95
|
+
* adminPermission,
|
|
96
|
+
* rolePermission({ role: 'admin' })
|
|
97
|
+
* ])
|
|
54
98
|
*
|
|
55
|
-
* //
|
|
56
|
-
* addPermission('api', [
|
|
99
|
+
* // Also works: no tree-shaking
|
|
100
|
+
* export const apiPermissions = addPermission('api', [
|
|
101
|
+
* readPermission
|
|
102
|
+
* ])
|
|
57
103
|
* ```
|
|
58
104
|
*/
|
|
59
105
|
export const addPermission = (tag, permissions) => {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
if (permissionsStore[tag]) {
|
|
106
|
+
const tagGroups = pikkuState('permissions', 'tagGroup');
|
|
107
|
+
if (tagGroups[tag]) {
|
|
63
108
|
throw new Error(`Permissions for tag '${tag}' already exist. Use a different tag or remove the existing permissions first.`);
|
|
64
109
|
}
|
|
65
|
-
|
|
110
|
+
tagGroups[tag] = permissions;
|
|
111
|
+
return permissions;
|
|
66
112
|
};
|
|
67
113
|
const EMPTY = [];
|
|
68
114
|
/**
|
|
@@ -84,7 +130,7 @@ export const getPermissionsForTags = (tags) => {
|
|
|
84
130
|
if (!tags || tags.length === 0) {
|
|
85
131
|
return EMPTY;
|
|
86
132
|
}
|
|
87
|
-
const permissionsStore = pikkuState('
|
|
133
|
+
const permissionsStore = pikkuState('permissions', 'tagGroup');
|
|
88
134
|
const applicablePermissions = [];
|
|
89
135
|
// Collect permissions for all matching tags
|
|
90
136
|
for (const tag of new Set(tags)) {
|
|
@@ -100,7 +146,7 @@ export const getPermissionsForTags = (tags) => {
|
|
|
100
146
|
}
|
|
101
147
|
return applicablePermissions;
|
|
102
148
|
};
|
|
103
|
-
const
|
|
149
|
+
const combinedPermissionsCache = {
|
|
104
150
|
[PikkuWiringTypes.http]: {},
|
|
105
151
|
[PikkuWiringTypes.rpc]: {},
|
|
106
152
|
[PikkuWiringTypes.channel]: {},
|
|
@@ -110,67 +156,134 @@ const permissionCache = {
|
|
|
110
156
|
[PikkuWiringTypes.cli]: {},
|
|
111
157
|
};
|
|
112
158
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
159
|
+
* Combines wiring-specific permissions with function-level permissions.
|
|
160
|
+
*
|
|
161
|
+
* This function resolves permission metadata into actual permission functions and combines them.
|
|
162
|
+
* It filters out wire permissions without tags from inheritedPermissions to avoid duplication
|
|
163
|
+
* (those are passed separately as wirePermissions).
|
|
164
|
+
*
|
|
165
|
+
* @param {object} options - Configuration object for combining permissions.
|
|
166
|
+
* @param {PermissionMetadata[] | undefined} options.wireInheritedPermissions - Metadata from wiring (HTTP + tags + wire with tags).
|
|
167
|
+
* @param {CorePermissionGroup | CorePikkuPermission[] | undefined} options.wirePermissions - Inline wire permissions.
|
|
168
|
+
* @param {PermissionMetadata[] | undefined} options.funcInheritedPermissions - Function permissions metadata (only tags).
|
|
169
|
+
* @param {CorePermissionGroup | CorePikkuPermission[] | undefined} options.funcPermissions - Inline function permissions.
|
|
170
|
+
* @returns {(CorePermissionGroup | CorePikkuPermission)[]} Combined array of resolved permissions.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```typescript
|
|
174
|
+
* const combined = combinePermissions(wireType, wireId, {
|
|
175
|
+
* wireInheritedPermissions: meta.permissions,
|
|
176
|
+
* wirePermissions: inlinePermissions,
|
|
177
|
+
* funcInheritedPermissions: funcMeta.permissions,
|
|
178
|
+
* funcPermissions: funcConfig.permissions
|
|
179
|
+
* })
|
|
180
|
+
* ```
|
|
118
181
|
*/
|
|
119
|
-
export const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
cachedPermission = {
|
|
123
|
-
wiringTags: getPermissionsForTags(wiringTags),
|
|
124
|
-
funcTags: getPermissionsForTags(funcTags),
|
|
125
|
-
};
|
|
126
|
-
permissionCache[wireType][uid] = cachedPermission;
|
|
182
|
+
export const combinePermissions = (wireType, uid, { wireInheritedPermissions, wirePermissions, funcInheritedPermissions, funcPermissions, } = {}) => {
|
|
183
|
+
if (combinedPermissionsCache[wireType][uid]) {
|
|
184
|
+
return combinedPermissionsCache[wireType][uid];
|
|
127
185
|
}
|
|
128
|
-
|
|
129
|
-
// 1.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
186
|
+
const resolved = [];
|
|
187
|
+
// 1. Resolve wire inherited permissions (HTTP + tag groups + individual wire permissions)
|
|
188
|
+
if (wireInheritedPermissions) {
|
|
189
|
+
for (const meta of wireInheritedPermissions) {
|
|
190
|
+
if (meta.type === 'http') {
|
|
191
|
+
// Look up HTTP permission group from pikkuState
|
|
192
|
+
const group = pikkuState('permissions', 'httpGroup')[meta.route];
|
|
193
|
+
if (group) {
|
|
194
|
+
if (Array.isArray(group)) {
|
|
195
|
+
resolved.push(...group);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
resolved.push(group);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else if (meta.type === 'tag') {
|
|
203
|
+
// Look up tag permission group from pikkuState
|
|
204
|
+
const group = pikkuState('permissions', 'tagGroup')[meta.tag];
|
|
205
|
+
if (group) {
|
|
206
|
+
if (Array.isArray(group)) {
|
|
207
|
+
resolved.push(...group);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
resolved.push(group);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else if (meta.type === 'wire') {
|
|
215
|
+
// Individual wire permission (exported, not inline)
|
|
216
|
+
const permission = getPermissionByName(meta.name);
|
|
217
|
+
if (permission) {
|
|
218
|
+
resolved.push(permission);
|
|
219
|
+
}
|
|
138
220
|
}
|
|
139
221
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
222
|
+
}
|
|
223
|
+
// 2. Add inline wire permissions
|
|
224
|
+
if (wirePermissions) {
|
|
225
|
+
if (Array.isArray(wirePermissions)) {
|
|
226
|
+
resolved.push(...wirePermissions);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
resolved.push(wirePermissions);
|
|
143
230
|
}
|
|
144
231
|
}
|
|
145
|
-
//
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
232
|
+
// 3. Resolve function inherited permissions (only tags, wire permissions already handled)
|
|
233
|
+
if (funcInheritedPermissions) {
|
|
234
|
+
for (const meta of funcInheritedPermissions) {
|
|
235
|
+
if (meta.type === 'tag') {
|
|
236
|
+
// Look up tag permission group from pikkuState
|
|
237
|
+
const group = pikkuState('permissions', 'tagGroup')[meta.tag];
|
|
238
|
+
if (group) {
|
|
239
|
+
if (Array.isArray(group)) {
|
|
240
|
+
resolved.push(...group);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
resolved.push(group);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Note: wire permissions are already handled in wireInheritedPermissions
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// 4. Add inline function permissions
|
|
251
|
+
if (funcPermissions) {
|
|
252
|
+
if (Array.isArray(funcPermissions)) {
|
|
253
|
+
resolved.push(...funcPermissions);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
resolved.push(funcPermissions);
|
|
151
257
|
}
|
|
152
258
|
}
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
259
|
+
// Deduplicate and freeze
|
|
260
|
+
combinedPermissionsCache[wireType][uid] = freezeDedupe(resolved);
|
|
261
|
+
return combinedPermissionsCache[wireType][uid];
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* Runs permission checks using combined permissions from all sources.
|
|
265
|
+
* Combines permissions from wire and function levels, then validates them.
|
|
266
|
+
*/
|
|
267
|
+
export const runPermissions = async (wireType, uid, { wireInheritedPermissions, wirePermissions, funcInheritedPermissions, funcPermissions, allServices, data, session, }) => {
|
|
268
|
+
// Combine all permissions: wireInheritedPermissions → wirePermissions → funcInheritedPermissions → funcPermissions
|
|
269
|
+
const allPermissions = combinePermissions(wireType, uid, {
|
|
270
|
+
wireInheritedPermissions,
|
|
271
|
+
wirePermissions,
|
|
272
|
+
funcInheritedPermissions,
|
|
273
|
+
funcPermissions,
|
|
274
|
+
});
|
|
275
|
+
// Check all combined permissions - at least one must pass if any exist
|
|
276
|
+
if (allPermissions.length > 0) {
|
|
277
|
+
let permissioned = false;
|
|
278
|
+
for (const permission of allPermissions) {
|
|
279
|
+
const result = await verifyPermissions(typeof permission === 'function' ? { permission } : permission, allServices, data, session);
|
|
159
280
|
if (result) {
|
|
160
281
|
permissioned = true;
|
|
161
|
-
|
|
282
|
+
// Continue executing all permissions (don't break early)
|
|
162
283
|
}
|
|
163
284
|
}
|
|
164
285
|
if (!permissioned) {
|
|
165
|
-
allServices.logger.debug('Permission denied -
|
|
166
|
-
throw new ForbiddenError('Permission denied');
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
// 4. Function permissions - must pass if defined
|
|
170
|
-
if (funcPermissions) {
|
|
171
|
-
permissioned = await verifyPermissions(funcPermissions, allServices, data, session);
|
|
172
|
-
if (!permissioned) {
|
|
173
|
-
allServices.logger.debug('Permission denied - function permissions');
|
|
286
|
+
allServices.logger.debug('Permission denied - combined permissions');
|
|
174
287
|
throw new ForbiddenError('Permission denied');
|
|
175
288
|
}
|
|
176
289
|
}
|