@camstack/server 1.2.8 → 1.2.10
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/dist/api/core/cap-providers.js +35 -14
- package/dist/api/trpc/cap-router-runtime.js +6 -0
- package/dist/api/trpc/generated-cap-mounts.js +1 -2
- package/dist/api/trpc/generated-cap-routers.js +792 -754
- package/dist/core/addon/addon-package.service.js +63 -2
- package/dist/core/addon/addon-registry.service.js +8 -4
- package/package.json +9 -10
- package/dist/api/addons-custom.router.js +0 -91
|
@@ -41,6 +41,7 @@ exports.computeTopology = computeTopology;
|
|
|
41
41
|
exports.createNodeRootPackageLookup = createNodeRootPackageLookup;
|
|
42
42
|
exports.buildNodesProvider = buildNodesProvider;
|
|
43
43
|
exports.buildIntegrationsProvider = buildIntegrationsProvider;
|
|
44
|
+
exports.dispatchCustomAction = dispatchCustomAction;
|
|
44
45
|
exports.buildAddonsProvider = buildAddonsProvider;
|
|
45
46
|
/* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access -- pre-existing lint debt across this 800-line provider-factory module. The flagged sites delegate into Moleculer/EventBus/IntegrationRegistry surfaces typed as `unknown` to break circular dependencies; runtime contracts are validated by the cap-mount-helper layer above. Tracked separately. */
|
|
46
47
|
/**
|
|
@@ -972,6 +973,39 @@ function ensureCustomActionAuth(ctx, level) {
|
|
|
972
973
|
return;
|
|
973
974
|
}
|
|
974
975
|
}
|
|
976
|
+
/**
|
|
977
|
+
* Resolve + dispatch a single addon custom action. This is the LIVE dispatch
|
|
978
|
+
* used by the `addons.custom` cap-provider handler below — factored out so it
|
|
979
|
+
* can be unit-tested directly against the real code path.
|
|
980
|
+
*
|
|
981
|
+
* Responsibilities (in order):
|
|
982
|
+
* 1. Resolve `(addonId, action)` against the process `CustomActionRegistry`.
|
|
983
|
+
* 2. Enforce the action's declared per-action auth (`ensureCustomActionAuth`).
|
|
984
|
+
* NOTE: the OUTER cap mount is `protected`, so unauthenticated callers are
|
|
985
|
+
* already rejected before this runs; the generic cap-router auth matrix
|
|
986
|
+
* covers that mount-level gate.
|
|
987
|
+
* 3. Validate input against the action's Zod schema.
|
|
988
|
+
* 4. Forward the authenticated caller only when the action opts in with
|
|
989
|
+
* `caller: 'required'` (derived server-side from the principal, never
|
|
990
|
+
* trusted from input).
|
|
991
|
+
* 5. Validate the addon's output — crash-early on a misbehaving addon.
|
|
992
|
+
*/
|
|
993
|
+
async function dispatchCustomAction(registry, ctx, input) {
|
|
994
|
+
const entry = registry.resolve(input.addonId, input.action);
|
|
995
|
+
if (!entry) {
|
|
996
|
+
throw new server_1.TRPCError({
|
|
997
|
+
code: 'NOT_FOUND',
|
|
998
|
+
message: `addon '${input.addonId}' has no custom action '${input.action}'`,
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
ensureCustomActionAuth(ctx, entry.spec.auth);
|
|
1002
|
+
const parsedInput = entry.spec.input.parse(input.input);
|
|
1003
|
+
const caller = entry.spec.caller === 'required' && ctx.user
|
|
1004
|
+
? { userId: ctx.user.id, isAdmin: ctx.user.isAdmin }
|
|
1005
|
+
: undefined;
|
|
1006
|
+
const result = await entry.handler(parsedInput, caller);
|
|
1007
|
+
return entry.spec.output.parse(result);
|
|
1008
|
+
}
|
|
975
1009
|
/** A node id that refers to the hub itself (top-level or a hub group runner). */
|
|
976
1010
|
function isHubNode(nodeId) {
|
|
977
1011
|
return nodeId === 'hub' || nodeId.startsWith('hub/');
|
|
@@ -1163,20 +1197,7 @@ function buildAddonsProvider(ar, ps, ls, moleculer, configService, ctx) {
|
|
|
1163
1197
|
getJob: async (input) => lifecycleRunner.getJob(input.jobId),
|
|
1164
1198
|
listJobs: async (input) => lifecycleRunner.listJobs({ activeOnly: input.activeOnly }),
|
|
1165
1199
|
cancelJob: async (input) => lifecycleRunner.cancelJob(input.jobId),
|
|
1166
|
-
custom: async (input) =>
|
|
1167
|
-
const registry = ar.getCustomActionRegistry();
|
|
1168
|
-
const entry = registry.resolve(input.addonId, input.action);
|
|
1169
|
-
if (!entry) {
|
|
1170
|
-
throw new server_1.TRPCError({
|
|
1171
|
-
code: 'NOT_FOUND',
|
|
1172
|
-
message: `addon '${input.addonId}' has no custom action '${input.action}'`,
|
|
1173
|
-
});
|
|
1174
|
-
}
|
|
1175
|
-
ensureCustomActionAuth(ctx, entry.spec.auth);
|
|
1176
|
-
const parsedInput = entry.spec.input.parse(input.input);
|
|
1177
|
-
const result = await entry.handler(parsedInput);
|
|
1178
|
-
return entry.spec.output.parse(result);
|
|
1179
|
-
},
|
|
1200
|
+
custom: async (input) => dispatchCustomAction(ar.getCustomActionRegistry(), ctx, input),
|
|
1180
1201
|
onAddonLogs: (input, push) => {
|
|
1181
1202
|
const unsubscribe = ls.subscribe({ tags: { addonId: input.addonId }, level: input.level }, (entry) => {
|
|
1182
1203
|
push({
|
|
@@ -172,6 +172,12 @@ function createCapRouterServices(deps) {
|
|
|
172
172
|
badRequest: (message) => {
|
|
173
173
|
throw new server_1.TRPCError({ code: 'BAD_REQUEST', message });
|
|
174
174
|
},
|
|
175
|
+
// D-1 caller-identity injection: a `caller: 'required'` method whose
|
|
176
|
+
// context carries no authenticated user fails closed — the builder
|
|
177
|
+
// never fabricates an identity.
|
|
178
|
+
unauthorized: (message) => {
|
|
179
|
+
throw new server_1.TRPCError({ code: 'UNAUTHORIZED', message });
|
|
180
|
+
},
|
|
175
181
|
};
|
|
176
182
|
}
|
|
177
183
|
/**
|
|
@@ -74,8 +74,6 @@ function mountAllCaps(services) {
|
|
|
74
74
|
}, remoteCapProxy),
|
|
75
75
|
addons: (0, generated_cap_routers_js_1.createCapRouter_addons)((_ctx) => reg?.getSingleton('addons') ?? null, remoteCapProxy),
|
|
76
76
|
adminUi: (0, generated_cap_routers_js_1.createCapRouter_adminUi)((_ctx) => reg?.getSingleton('admin-ui') ?? null, remoteCapProxy),
|
|
77
|
-
advancedNotifier: (0, generated_cap_routers_js_1.createCapRouter_advancedNotifier)((_ctx) => reg?.getSingleton('advanced-notifier') ??
|
|
78
|
-
null, remoteCapProxy),
|
|
79
77
|
airQualitySensor: (0, generated_cap_routers_js_1.createCapRouter_airQualitySensor)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'air-quality-sensor'), remoteCapProxy),
|
|
80
78
|
alarmPanel: (0, generated_cap_routers_js_1.createCapRouter_alarmPanel)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'alarm-panel'), remoteCapProxy),
|
|
81
79
|
alerts: (0, generated_cap_routers_js_1.createCapRouter_alerts)((_ctx) => reg?.getSingleton('alerts') ?? null, remoteCapProxy),
|
|
@@ -321,6 +319,7 @@ function mountAllCaps(services) {
|
|
|
321
319
|
discoverTargets: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'discoverTargets'),
|
|
322
320
|
};
|
|
323
321
|
}, remoteCapProxy),
|
|
322
|
+
notificationRules: (0, generated_cap_routers_js_1.createCapRouter_notificationRules)((_ctx) => reg?.getSingleton('notification-rules') ?? null, remoteCapProxy),
|
|
324
323
|
notifier: (0, generated_cap_routers_js_1.createCapRouter_notifier)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'notifier'), remoteCapProxy),
|
|
325
324
|
numericSensor: (0, generated_cap_routers_js_1.createCapRouter_numericSensor)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'numeric-sensor'), remoteCapProxy),
|
|
326
325
|
oauthIntegration: (0, generated_cap_routers_js_1.createCapRouter_oauthIntegration)((_ctx, addonId) => {
|