@camstack/server 1.2.480 → 1.2.482
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/agent/main.js +15 -4
- package/dist/api/core/auth.router.js +188 -192
- package/dist/api/core/cap-providers.js +49 -5
- package/dist/api/core/integration-secrets.js +60 -0
- package/dist/api/trpc/device-config-secret-redaction.js +111 -13
- package/dist/api/trpc/generated-cap-mounts.js +3 -1
- package/dist/api/trpc/generated-cap-routers.js +1710 -1345
- package/dist/api/trpc/trpc.router.js +1 -1
- package/dist/auth/auth-provider-gate.js +19 -0
- package/dist/auth/external-link-intents.js +63 -0
- package/dist/auth/passkey-provider.js +96 -0
- package/dist/auth/second-factors.js +21 -0
- package/dist/auth/session-mint.js +0 -8
- package/dist/auth/sso-finish-deps.js +71 -0
- package/dist/auth/sso-finish.js +164 -39
- package/dist/boot/platform-keychain-key.js +94 -0
- package/dist/core/addon/addon-registry.service.js +59 -0
- package/dist/core/addon/addon-settings-provider.js +7 -1
- package/dist/core/moleculer/moleculer.service.js +31 -1
- package/dist/main.js +31 -4
- package/package.json +10 -10
|
@@ -1151,6 +1151,22 @@ class AddonRegistryService {
|
|
|
1151
1151
|
* addon (`execution.group`) is never misclassified as `in-process`.
|
|
1152
1152
|
* Falls back to the addon id when the declaration is not yet known.
|
|
1153
1153
|
*/
|
|
1154
|
+
/** The runner id hosting an addon — its `execution.group`, or itself. */
|
|
1155
|
+
runnerIdFor(addonId) {
|
|
1156
|
+
return this.childIdForAddon(addonId);
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* A hub in-process keychain handle acting for `owner` — the door hub-main
|
|
1160
|
+
* code (the integration settings provider) uses to keep another addon's
|
|
1161
|
+
* values (D787). Never handed to an addon.
|
|
1162
|
+
*/
|
|
1163
|
+
secretsForOwner(owner) {
|
|
1164
|
+
return (0, types_1.createSecretsAccess)({
|
|
1165
|
+
owner,
|
|
1166
|
+
transport: this.inProcessKeychainTransport(owner),
|
|
1167
|
+
eventBus: this.eventBusService,
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1154
1170
|
childIdForAddon(addonId) {
|
|
1155
1171
|
const decl = this.addonEntries.get(addonId)?.declaration;
|
|
1156
1172
|
return decl ? (0, types_1.resolveRunnerId)(decl, addonId) : addonId;
|
|
@@ -2934,6 +2950,9 @@ class AddonRegistryService {
|
|
|
2934
2950
|
deps: new system_1.AddonDepsManager(dataDir, logger),
|
|
2935
2951
|
kernel: {
|
|
2936
2952
|
localNodeId: this.broker.nodeID,
|
|
2953
|
+
// The keychain accepts a grouped runner's stamp (its group id) for the
|
|
2954
|
+
// refs of the addons that runner hosts.
|
|
2955
|
+
runnerIdForAddon: (id) => this.childIdForAddon(id),
|
|
2937
2956
|
storage: storageProvider ?? undefined,
|
|
2938
2957
|
deviceRegistry: this.deviceRegistry ?? undefined,
|
|
2939
2958
|
devices: deviceManagerApi,
|
|
@@ -2978,6 +2997,16 @@ class AddonRegistryService {
|
|
|
2978
2997
|
return (0, types_1.createDeviceProxy)(api, binding);
|
|
2979
2998
|
},
|
|
2980
2999
|
settings: settingsView,
|
|
3000
|
+
// This builtin's keychain values (D787). In-process, so the caller is
|
|
3001
|
+
// STATED (no wire, no stamp) — the one place the keychain trusts a
|
|
3002
|
+
// claim. `forOwner` is the door a system builtin (device-manager,
|
|
3003
|
+
// storage-orchestrator) uses to keep another addon's values.
|
|
3004
|
+
secrets: (0, types_1.createSecretsAccess)({
|
|
3005
|
+
owner: addonId,
|
|
3006
|
+
transport: this.inProcessKeychainTransport(addonId),
|
|
3007
|
+
transportForOwner: (owner) => this.inProcessKeychainTransport(owner),
|
|
3008
|
+
eventBus: this.eventBusService,
|
|
3009
|
+
}),
|
|
2981
3010
|
useCapability(capName, scope = { type: 'global' }) {
|
|
2982
3011
|
return getOrCreateHandle(capName, scope, 15_000);
|
|
2983
3012
|
},
|
|
@@ -2997,6 +3026,36 @@ class AddonRegistryService {
|
|
|
2997
3026
|
};
|
|
2998
3027
|
return ctx;
|
|
2999
3028
|
}
|
|
3029
|
+
/**
|
|
3030
|
+
* The keychain transport of a hub in-process caller: the provider lives in
|
|
3031
|
+
* this process, so the call goes straight to it with the caller STATED.
|
|
3032
|
+
* Built per call so a keychain registered after this context still answers;
|
|
3033
|
+
* a missing provider throws, which `ctx.secrets` reads as `unavailable`.
|
|
3034
|
+
*/
|
|
3035
|
+
inProcessKeychainTransport(owner) {
|
|
3036
|
+
const provider = () => {
|
|
3037
|
+
const p = this.capabilityRegistry.getSingleton('keychain');
|
|
3038
|
+
if (p === null || p === undefined)
|
|
3039
|
+
throw new Error('keychain provider is not registered');
|
|
3040
|
+
return p;
|
|
3041
|
+
};
|
|
3042
|
+
const callerAddonId = owner;
|
|
3043
|
+
return {
|
|
3044
|
+
put: (input) => {
|
|
3045
|
+
const stated = { ref: input.ref, value: input.value, callerAddonId };
|
|
3046
|
+
return provider().put(stated);
|
|
3047
|
+
},
|
|
3048
|
+
resolve: (input) => {
|
|
3049
|
+
const stated = { refs: [...input.refs], callerAddonId };
|
|
3050
|
+
return provider().resolve(stated);
|
|
3051
|
+
},
|
|
3052
|
+
delete: (input) => {
|
|
3053
|
+
const stated = { refs: [...input.refs], callerAddonId };
|
|
3054
|
+
return provider().delete(stated);
|
|
3055
|
+
},
|
|
3056
|
+
getState: () => provider().getState({}),
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3000
3059
|
/**
|
|
3001
3060
|
* Per-addon disposer chain. Created lazily the first time an addon
|
|
3002
3061
|
* calls `ctx.addDisposer(...)`. Drained by `restartAddon()` /
|
|
@@ -76,7 +76,13 @@ function createAddonSettingsProvider(deps) {
|
|
|
76
76
|
if (!addon || typeof addon.updateGlobalSettings !== 'function') {
|
|
77
77
|
throw new Error(`Addon "${input.addonId}" does not implement updateGlobalSettings`);
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
// The admin API's write is the OPERATOR's (D787).
|
|
80
|
+
if (typeof addon.updateGlobalSettingsAsOperator === 'function') {
|
|
81
|
+
await addon.updateGlobalSettingsAsOperator(input.patch, input.nodeId);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
await addon.updateGlobalSettings(input.patch, input.nodeId);
|
|
85
|
+
}
|
|
80
86
|
return { success: true };
|
|
81
87
|
}
|
|
82
88
|
return forkedUpdate(input.addonId, 'updateGlobalSettings', { patch: input.patch, ...(input.nodeId ? { nodeId: input.nodeId } : {}) }, input.nodeId);
|
|
@@ -132,6 +132,27 @@ class MoleculerService {
|
|
|
132
132
|
setRunnerIdResolver(resolver) {
|
|
133
133
|
this.runnerIdResolver = resolver;
|
|
134
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Does `nodeId` (an agent) host the runner `runnerId`? Read from what the
|
|
137
|
+
* node itself registered: its manifest names the addons it runs, and an
|
|
138
|
+
* addon's runner is its `execution.group` or itself. A node that never
|
|
139
|
+
* registered hosts nothing.
|
|
140
|
+
*/
|
|
141
|
+
isRunnerOnNode(nodeId, runnerId) {
|
|
142
|
+
const nodes = this.nodeRegistry
|
|
143
|
+
.listNodeIds()
|
|
144
|
+
.filter((id) => id === nodeId || id.startsWith(`${nodeId}/`));
|
|
145
|
+
if (nodes.includes(`${nodeId}/${runnerId}`))
|
|
146
|
+
return true;
|
|
147
|
+
for (const id of nodes) {
|
|
148
|
+
for (const entry of this.nodeRegistry.getNodeManifest(id) ?? []) {
|
|
149
|
+
const runner = this.runnerIdResolver?.(entry.addonId) ?? entry.addonId;
|
|
150
|
+
if (entry.addonId === runnerId || runner === runnerId)
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
135
156
|
get childRegistry() {
|
|
136
157
|
return this.localChildRegistry;
|
|
137
158
|
}
|
|
@@ -400,7 +421,16 @@ class MoleculerService {
|
|
|
400
421
|
// `broker.call('$hub-cap-fwd.forward', …)` discovers it here. Without it a
|
|
401
422
|
// remote agent's raw `${cap}.*` discovery 30s-deadlines (hub-local addon
|
|
402
423
|
// runners expose no Moleculer service). See hub-cap-forward.ts.
|
|
403
|
-
this.brokerSafe.createService((0, system_1.createHubCapForwardService)(onUnownedCall
|
|
424
|
+
this.brokerSafe.createService((0, system_1.createHubCapForwardService)(onUnownedCall, {
|
|
425
|
+
// The agent stamps WHO is calling (its child's runner id) into the
|
|
426
|
+
// envelope (D787). Believe it only when that runner is on the agent
|
|
427
|
+
// the request came from — a node's registered manifest lists the
|
|
428
|
+
// addons it hosts, and a grouped runner answers to its group id.
|
|
429
|
+
isCallerOnNode: (senderNodeId, callerRunnerId) => this.isRunnerOnNode(senderNodeId, callerRunnerId),
|
|
430
|
+
onRejectedCaller: (senderNodeId, callerRunnerId) => logger.warn('dropped an agent caller stamp the sender does not host', {
|
|
431
|
+
meta: { senderNodeId, callerRunnerId },
|
|
432
|
+
}),
|
|
433
|
+
}));
|
|
404
434
|
const registry = new system_1.LocalChildRegistry({
|
|
405
435
|
server: (0, system_1.createLocalTransport)().createServer(nodeId),
|
|
406
436
|
// This node's own id — lets the cap-call-out router serve a pin-to-self
|
package/dist/main.js
CHANGED
|
@@ -87,10 +87,13 @@ const ws_slow_consumer_guard_js_1 = require("./api/trpc/ws-slow-consumer-guard.j
|
|
|
87
87
|
const addon_route_jwt_gate_js_1 = require("./auth/addon-route-jwt-gate.js");
|
|
88
88
|
const addon_route_share_gate_js_1 = require("./auth/addon-route-share-gate.js");
|
|
89
89
|
const auth_rate_limit_1 = require("./auth/auth-rate-limit");
|
|
90
|
+
const external_link_intents_js_1 = require("./auth/external-link-intents.js");
|
|
90
91
|
const session_cookie_js_1 = require("./auth/session-cookie.js");
|
|
91
92
|
const session_mint_js_1 = require("./auth/session-mint.js");
|
|
92
93
|
const sso_finish_js_1 = require("./auth/sso-finish.js");
|
|
94
|
+
const sso_finish_deps_js_1 = require("./auth/sso-finish-deps.js");
|
|
93
95
|
const boot_config_1 = require("./boot/boot-config");
|
|
96
|
+
const platform_keychain_key_1 = require("./boot/platform-keychain-key");
|
|
94
97
|
const integration_id_backfill_1 = require("./boot/integration-id-backfill");
|
|
95
98
|
const post_boot_service_1 = require("./boot/post-boot.service");
|
|
96
99
|
const retired_public_url_env_1 = require("./boot/retired-public-url-env");
|
|
@@ -248,6 +251,15 @@ async function bootstrap() {
|
|
|
248
251
|
// forked, so no child can read it — and warned about once a logger exists
|
|
249
252
|
// (below). See `boot/retired-public-url-env.ts`.
|
|
250
253
|
const publicUrlWarning = (0, retired_public_url_env_1.dropRetiredHubAddressEnv)(process.env);
|
|
254
|
+
// A desktop hub's keychain master key arrives on an inherited pipe (D788).
|
|
255
|
+
// Read and closed before anything is forked, so no runner inherits it.
|
|
256
|
+
const platformKey = (0, platform_keychain_key_1.loadPlatformKeychainKey)();
|
|
257
|
+
if (platformKey.kind === 'loaded') {
|
|
258
|
+
console.log(`[keychain] master key provided by the ${platformKey.label}`);
|
|
259
|
+
}
|
|
260
|
+
else if (platformKey.kind === 'failed') {
|
|
261
|
+
console.warn(`[keychain] the desktop app's key pipe could not be read: ${platformKey.error}`);
|
|
262
|
+
}
|
|
251
263
|
// hub-main's own memory, on the record. Deliberately started HERE and not in
|
|
252
264
|
// the starter: the starter runs from the image's /opt closure, which only
|
|
253
265
|
// changes with a new image, while this entry is what `applyServerUpdate`
|
|
@@ -523,10 +535,14 @@ async function bootstrap() {
|
|
|
523
535
|
// minted by `auth.createHandoffCode`, redeemed by the viewer app via the
|
|
524
536
|
// public `auth.redeemHandoffCode`.
|
|
525
537
|
const handoffCodeService = new handoff_code_service_1.HandoffCodeService();
|
|
538
|
+
// Pending "link this sign-in account" intents (D782) — minted by
|
|
539
|
+
// `auth.beginExternalLink`, consumed once by `/api/auth/sso/finish`.
|
|
540
|
+
const externalLinkIntents = new external_link_intents_js_1.ExternalLinkIntents();
|
|
526
541
|
const buildRouter = () => (0, trpc_router_1.buildAppRouter)({
|
|
527
542
|
authService: app.get(auth_service_1.AuthService),
|
|
528
543
|
shareTokenService,
|
|
529
544
|
handoffCodeService,
|
|
545
|
+
externalLinkIntents,
|
|
530
546
|
configService: config,
|
|
531
547
|
featureService: app.get(feature_service_1.FeatureService),
|
|
532
548
|
loggingService,
|
|
@@ -810,19 +826,30 @@ async function bootstrap() {
|
|
|
810
826
|
// minted by THE session mint from the user record as it is now (D712) —
|
|
811
827
|
// `auth/sso-finish.ts` holds the rules, this route only maps the reply.
|
|
812
828
|
const ssoAuth = app.get(auth_service_1.AuthService);
|
|
829
|
+
const ssoLogger = app.get(logging_service_1.LoggingService).createLogger('sso-finish');
|
|
813
830
|
const ssoMinter = (0, session_mint_js_1.createSessionMinter)({
|
|
814
831
|
signer: ssoAuth,
|
|
815
832
|
directory: () => capabilityRegistry.getSingleton('user-management'),
|
|
816
833
|
});
|
|
817
834
|
fastify.get('/api/auth/sso/finish', async (request, reply) => {
|
|
818
|
-
const outcome = await (0, sso_finish_js_1.finishSsoLogin)(request.query.bridge, {
|
|
819
|
-
|
|
835
|
+
const outcome = await (0, sso_finish_js_1.finishSsoLogin)(request.query.bridge, (0, sso_finish_deps_js_1.createSsoFinishDeps)({
|
|
836
|
+
auth: ssoAuth,
|
|
837
|
+
registry: capabilityRegistry,
|
|
838
|
+
moleculer: app.get(moleculer_service_1.MoleculerService),
|
|
820
839
|
minter: ssoMinter,
|
|
821
|
-
|
|
840
|
+
linkIntents: externalLinkIntents,
|
|
841
|
+
logFailure: (error) => ssoLogger.error('SSO finish failed', {
|
|
842
|
+
meta: { error: error instanceof Error ? error.message : String(error) },
|
|
843
|
+
}),
|
|
844
|
+
}, request.headers.cookie),
|
|
845
|
+
// A popup sign-in (D791) returns through the admin-ui completion page.
|
|
846
|
+
{ popup: request.query.popup === '1' });
|
|
822
847
|
if (outcome.kind === 'error')
|
|
823
848
|
return reply.status(outcome.status).send(outcome.body);
|
|
824
849
|
reply.code(302);
|
|
825
|
-
|
|
850
|
+
// Admin-ui paths are relative to the hub's mount (HA ingress etc.).
|
|
851
|
+
const ssoMount = (0, spa_mount_1.parseForwardedPrefix)(request.headers['x-forwarded-prefix']) ?? '';
|
|
852
|
+
reply.header('Location', `${ssoMount}${outcome.location}`);
|
|
826
853
|
return reply.send('');
|
|
827
854
|
});
|
|
828
855
|
// ── Backup archive download ─────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.482",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"files": [
|
|
@@ -37,18 +37,18 @@
|
|
|
37
37
|
]
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
41
|
-
"@camstack/addon-agent-ui": "1.2.
|
|
42
|
-
"@camstack/addon-auth": "1.2.
|
|
40
|
+
"@camstack/addon-admin-ui": "1.2.355",
|
|
41
|
+
"@camstack/addon-agent-ui": "1.2.192",
|
|
42
|
+
"@camstack/addon-auth": "1.2.132",
|
|
43
43
|
"@camstack/addon-decoder-nodeav": "1.2.121",
|
|
44
44
|
"@camstack/addon-notifiers": "1.2.128",
|
|
45
|
-
"@camstack/addon-pipeline": "1.2.
|
|
46
|
-
"@camstack/addon-pipeline-orchestrator": "1.2.
|
|
47
|
-
"@camstack/addon-post-analysis": "1.2.
|
|
45
|
+
"@camstack/addon-pipeline": "1.2.346",
|
|
46
|
+
"@camstack/addon-pipeline-orchestrator": "1.2.298",
|
|
47
|
+
"@camstack/addon-post-analysis": "1.2.344",
|
|
48
48
|
"@camstack/sdk": "1.2.126",
|
|
49
|
-
"@camstack/system": "1.2.
|
|
50
|
-
"@camstack/types": "1.2.
|
|
51
|
-
"@camstack/ui-library": "1.2.
|
|
49
|
+
"@camstack/system": "1.2.377",
|
|
50
|
+
"@camstack/types": "1.2.328",
|
|
51
|
+
"@camstack/ui-library": "1.2.250",
|
|
52
52
|
"@fastify/compress": "^9.0.0",
|
|
53
53
|
"@fastify/cookie": "^11.0.2",
|
|
54
54
|
"@fastify/cors": "^11.2.0",
|