@camstack/server 1.2.592 → 1.2.594
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/addon-root-inventory.js +11 -50
- package/dist/agent/addon-scan.js +6 -44
- package/dist/agent/agent-addon-runtime.js +3 -8
- package/dist/agent/agent-addon-updates.js +12 -52
- package/dist/agent/agent-cap-dispatch-service.js +3 -5
- package/dist/agent/agent-config.js +7 -42
- package/dist/agent/agent-deploy-swap.js +5 -41
- package/dist/agent/agent-group-runner.js +8 -47
- package/dist/agent/agent-http-auth.js +10 -22
- package/dist/agent/agent-http.js +69 -115
- package/dist/agent/agent-hub-addon-update.js +10 -16
- package/dist/agent/agent-hub-registration.js +4 -7
- package/dist/agent/agent-infra-capabilities.js +5 -9
- package/dist/agent/agent-json-file.js +5 -42
- package/dist/agent/agent-mesh-gate.js +5 -8
- package/dist/agent/agent-node-certificates.js +25 -28
- package/dist/agent/agent-pairing-local.js +10 -47
- package/dist/agent/agent-pairing-routes.js +16 -20
- package/dist/agent/agent-pairing-store.js +18 -58
- package/dist/agent/agent-reconnect.js +1 -4
- package/dist/agent/agent-service.js +42 -78
- package/dist/agent/agent-sniffing-listener.js +11 -48
- package/dist/agent/agent-undeploy.js +5 -41
- package/dist/agent/agent-update-routes.js +14 -19
- package/dist/agent/agent-update-service.js +38 -80
- package/dist/agent/apply-model-distribution.js +1 -4
- package/dist/agent/apply-stage-dev-uploads.js +6 -9
- package/dist/agent/derive-hub-url.js +3 -8
- package/dist/agent/fetch-bundle-from-hub.js +6 -9
- package/dist/agent/hubs-found.js +6 -43
- package/dist/agent/infra-boot-guard.js +1 -4
- package/dist/agent/infra-boot-plan.js +2 -6
- package/dist/agent/main.js +158 -186
- package/dist/agent/register-agent-cap-dispatch.js +3 -6
- package/dist/agent/serial-queue.js +2 -7
- package/dist/agent-status-page.js +1 -4
- package/dist/api/addon-upload.js +22 -61
- package/dist/api/agent-addon-delivery.js +6 -11
- package/dist/api/auth-whoami.js +1 -4
- package/dist/api/backup-download-pump.js +5 -9
- package/dist/api/backup-download.js +7 -10
- package/dist/api/core/addon-settings.router.js +16 -19
- package/dist/api/core/agent-pairing.router.js +76 -79
- package/dist/api/core/agents.router.js +21 -24
- package/dist/api/core/auth-admin-tokens.procedures.js +48 -52
- package/dist/api/core/auth-device-cert.procedures.js +62 -67
- package/dist/api/core/auth-limits.js +16 -25
- package/dist/api/core/auth-reauth.procedures.js +32 -37
- package/dist/api/core/auth-revoke-credentials.procedures.js +22 -26
- package/dist/api/core/auth-sessions.procedures.js +52 -56
- package/dist/api/core/auth.router.js +300 -304
- package/dist/api/core/cap-providers.js +74 -121
- package/dist/api/core/capabilities.router.js +34 -37
- package/dist/api/core/capability-preference-writes.js +10 -17
- package/dist/api/core/certificates.router.js +106 -109
- package/dist/api/core/cluster-nodes.router.js +15 -18
- package/dist/api/core/collection-preference.js +2 -6
- package/dist/api/core/event-bus-proxy.router.js +34 -37
- package/dist/api/core/failure-contributions.js +4 -8
- package/dist/api/core/hwaccel.router.js +17 -20
- package/dist/api/core/integration-secrets.js +8 -12
- package/dist/api/core/lifecycle-job-runner.js +13 -49
- package/dist/api/core/live-events.router.js +29 -32
- package/dist/api/core/load-contributions.js +4 -8
- package/dist/api/core/logging-settings.js +30 -43
- package/dist/api/core/logs.router.js +40 -43
- package/dist/api/core/notifications.router.js +15 -18
- package/dist/api/core/public-event-filter.js +4 -8
- package/dist/api/core/repl.router.js +18 -21
- package/dist/api/core/request-census-settings.js +11 -16
- package/dist/api/core/server-management.provider.js +1 -4
- package/dist/api/core/server-name.js +9 -17
- package/dist/api/core/site-location.js +16 -24
- package/dist/api/core/stream-probe.router.js +21 -24
- package/dist/api/core/system-events.router.js +23 -26
- package/dist/api/cors/cors-policy.js +20 -29
- package/dist/api/cors/register-cors-policy.js +9 -14
- package/dist/api/deploy-stage-registry.js +6 -10
- package/dist/api/dev-uploads-bundle.js +12 -50
- package/dist/api/health/adaptive-probe.routes.js +9 -13
- package/dist/api/health/health.routes.js +8 -13
- package/dist/api/hub-cluster-url.js +3 -9
- package/dist/api/model-distributor.js +12 -50
- package/dist/api/oauth2/consent-page.js +10 -13
- package/dist/api/oauth2/oauth2-routes.js +17 -25
- package/dist/api/oauth2/private-host.js +1 -4
- package/dist/api/server-upload-propagate.js +15 -51
- package/dist/api/server-upload.js +13 -50
- package/dist/api/sse-no-compression.js +1 -4
- package/dist/api/static/frame-policy.js +4 -9
- package/dist/api/static/precompressed-asset.js +15 -57
- package/dist/api/static/spa-mount.js +8 -47
- package/dist/api/static/spa-static.js +5 -12
- package/dist/api/tarball-manifest.js +7 -44
- package/dist/api/trpc/cap-mount-helpers.js +7 -12
- package/dist/api/trpc/cap-route-error-formatter.js +10 -13
- package/dist/api/trpc/cap-router-runtime.js +40 -51
- package/dist/api/trpc/client-ip.js +9 -20
- package/dist/api/trpc/core-cap-bridge.js +32 -42
- package/dist/api/trpc/device-collection-dispatch.js +23 -27
- package/dist/api/trpc/device-config-secret-redaction.js +24 -40
- package/dist/api/trpc/generated-cap-mounts.js +219 -221
- package/dist/api/trpc/generated-cap-routers.js +4878 -4726
- package/dist/api/trpc/http-request-census-hook.js +8 -11
- package/dist/api/trpc/http-request-census.js +34 -41
- package/dist/api/trpc/http-request-census.singleton.js +4 -8
- package/dist/api/trpc/localized-schema-paths.js +1 -4
- package/dist/api/trpc/principal-visibility.js +14 -22
- package/dist/api/trpc/remote-addon-update-grant.js +1 -4
- package/dist/api/trpc/scope-access.js +10 -15
- package/dist/api/trpc/server-provided-caps.js +3 -7
- package/dist/api/trpc/share-view-access.js +6 -14
- package/dist/api/trpc/trpc-error-device-tags.js +3 -6
- package/dist/api/trpc/trpc-error-principal.js +2 -6
- package/dist/api/trpc/trpc.context.js +20 -25
- package/dist/api/trpc/trpc.middleware.js +38 -46
- package/dist/api/trpc/trpc.router.js +58 -99
- package/dist/api/trpc/user-error.js +5 -8
- package/dist/api/trpc/ws-request-census.js +17 -24
- package/dist/api/trpc/ws-slow-consumer-guard.js +11 -16
- package/dist/api/upload-auth.js +5 -9
- package/dist/auth/addon-route-jwt-gate.js +4 -7
- package/dist/auth/addon-route-share-gate.js +7 -12
- package/dist/auth/app-confirm-page.js +17 -22
- package/dist/auth/app-confirm-routes.js +26 -31
- package/dist/auth/app-confirmations.js +7 -44
- package/dist/auth/app-page-shell.js +17 -30
- package/dist/auth/app-page-strings.js +11 -17
- package/dist/auth/app-passkey-page.js +16 -21
- package/dist/auth/app-passkey-routes.js +37 -41
- package/dist/auth/app-refused-route.js +13 -18
- package/dist/auth/auth-network-policy.js +11 -15
- package/dist/auth/auth-provider-gate.js +4 -9
- package/dist/auth/auth-rate-limit.js +3 -7
- package/dist/auth/client-address.js +6 -9
- package/dist/auth/device-enroll-proofs.js +6 -43
- package/dist/auth/external-link-intents.js +3 -7
- package/dist/auth/hub-sessions.js +6 -12
- package/dist/auth/native-link-completions.js +7 -11
- package/dist/auth/native-return.js +9 -13
- package/dist/auth/passkey-provider.js +26 -32
- package/dist/auth/second-factors.js +6 -10
- package/dist/auth/session-connections.js +6 -12
- package/dist/auth/session-cookie.js +12 -23
- package/dist/auth/session-lifetimes.js +8 -12
- package/dist/auth/session-mint.js +4 -11
- package/dist/auth/sso-finish-app.js +13 -16
- package/dist/auth/sso-finish-deps.js +21 -27
- package/dist/auth/sso-finish.js +28 -38
- package/dist/auth/sso-routes.js +22 -25
- package/dist/auth/used-bridge-ids.js +1 -5
- package/dist/boot/boot-config.js +16 -55
- package/dist/boot/init-sequence.js +1 -4
- package/dist/boot/integration-id-backfill.js +3 -8
- package/dist/boot/platform-keychain-key.js +5 -42
- package/dist/boot/post-boot.service.js +14 -18
- package/dist/boot/reconcile-lifecycle-jobs.js +3 -6
- package/dist/boot/retired-public-url-env.js +4 -8
- package/dist/bootstrap-packages.js +1 -4
- package/dist/closure-node-modules.js +2 -38
- package/dist/closure-patches/patch-reach.json +6 -2
- package/dist/core/addon/addon-call-gateway.js +1 -5
- package/dist/core/addon/addon-package.service.js +83 -123
- package/dist/core/addon/addon-process-view.js +2 -6
- package/dist/core/addon/addon-realign-plan.js +1 -4
- package/dist/core/addon/addon-registry.service.js +254 -197
- package/dist/core/addon/addon-row-manifest.js +1 -4
- package/dist/core/addon/addon-search.service.js +3 -7
- package/dist/core/addon/addon-settings-provider.js +1 -3
- package/dist/core/addon/addon-uninstall.js +4 -11
- package/dist/core/addon/addon.tokens.js +2 -5
- package/dist/core/addon/device-meta-mirror.js +8 -13
- package/dist/core/addon/forked-custom-actions.js +6 -11
- package/dist/core/addon/integration-visibility.js +3 -6
- package/dist/core/addon/materialize-route-path.js +1 -4
- package/dist/core/addon/package-dir-utils.js +8 -45
- package/dist/core/addon/prune-misplaced-addons.js +5 -43
- package/dist/core/addon/runner-convergence.js +1 -4
- package/dist/core/addon/runner-serialization.js +1 -4
- package/dist/core/addon/runner-spawn-fanout.js +3 -8
- package/dist/core/addon/settings-door-wiring.js +5 -8
- package/dist/core/addon/single-flight-refresh.js +1 -4
- package/dist/core/addon-bridge/addon-bridge.service.js +7 -44
- package/dist/core/addon-pages/addon-pages.service.js +3 -40
- package/dist/core/addon-widgets/addon-widgets.service.js +4 -42
- package/dist/core/agent/agent-addon-backfill.js +9 -17
- package/dist/core/agent/agent-delivery-ledger.js +3 -7
- package/dist/core/agent/agent-liveness-event.js +2 -6
- package/dist/core/agent/agent-pairing-http.js +41 -45
- package/dist/core/agent/agent-registry.service.js +53 -92
- package/dist/core/agent/cluster-node-history-mirror.js +3 -7
- package/dist/core/agent/cluster-node-history-store.js +113 -58
- package/dist/core/agent/hub-agent-pairing.js +1 -5
- package/dist/core/agent/node-certificates-client.js +3 -6
- package/dist/core/agent/node-directory.js +1 -4
- package/dist/core/app-links/well-known-app-links.js +2 -6
- package/dist/core/auth/auth.service.js +3 -8
- package/dist/core/auth/handoff-code.service.js +11 -49
- package/dist/core/auth/record-kinds.js +35 -0
- package/dist/core/auth/share-token.service.js +174 -122
- package/dist/core/capability/capability.service.js +1 -5
- package/dist/core/config/config.schema.js +1 -6
- package/dist/core/config/config.service.js +2 -6
- package/dist/core/events/event-bus.service.js +3 -7
- package/dist/core/feature/feature.service.js +2 -6
- package/dist/core/lifecycle/lifecycle-runner.singleton.js +5 -10
- package/dist/core/lifecycle/lifecycle-state-machine.js +1 -5
- package/dist/core/logging/log-ring-buffer.js +1 -5
- package/dist/core/logging/logging.service.js +5 -9
- package/dist/core/logging/scoped-logger.js +1 -5
- package/dist/core/moleculer/agent-readiness-pull.js +1 -4
- package/dist/core/moleculer/agent-registration-request.js +3 -7
- package/dist/core/moleculer/cap-call-fn.js +39 -5
- package/dist/core/moleculer/cap-route-authority.js +3 -8
- package/dist/core/moleculer/hub-mesh-guard.js +5 -9
- package/dist/core/moleculer/moleculer.service.js +108 -119
- package/dist/core/network/network-quality.service.js +2 -6
- package/dist/core/notification/notification-wrapper.service.js +3 -7
- package/dist/core/notification/toast-wrapper.service.js +3 -7
- package/dist/core/provider/provider.tokens.js +1 -4
- package/dist/core/repl/repl-engine.service.js +4 -8
- package/dist/core/runner-health/runner-boot-check.js +7 -15
- package/dist/core/runner-health/update-preflight.js +9 -14
- package/dist/core/server-update/server-update.service.js +12 -49
- package/dist/core/server-update/system-apply-closure-patches.js +6 -42
- package/dist/core/server-update/system-ensure-prebuilds.js +3 -6
- package/dist/core/server-update/system-exec-npm.js +2 -5
- package/dist/core/storage/fs-storage-backend.js +1 -5
- package/dist/core/storage/storage-location-manager.js +1 -5
- package/dist/core/storage/storage.service.js +2 -6
- package/dist/core/streaming/ffmpeg-stream-info.js +3 -8
- package/dist/core/streaming/stream-probe.service.js +12 -16
- package/dist/core/streaming/ttl-cache.js +1 -5
- package/dist/core/topology/topology-emitter.service.js +19 -23
- package/dist/core/update-availability-emitter.js +3 -7
- package/dist/core/update-availability-store.js +8 -45
- package/dist/core/updates/agent-installed-packages.js +5 -9
- package/dist/core/updates/restart-marker.js +3 -8
- package/dist/core/updates/update-check-scheduler.js +13 -18
- package/dist/core/updates/wrapper-update-checker.js +3 -7
- package/dist/first-boot-addon-plan.js +2 -6
- package/dist/framework-nodepath.js +2 -38
- package/dist/i18n/request-lang.js +7 -11
- package/dist/launcher.js +30 -65
- package/dist/lifecycle-journal-path.js +2 -38
- package/dist/main.js +287 -324
- package/dist/manual-boot.js +120 -157
- package/dist/node-role.js +1 -4
- package/dist/package-inventory.js +7 -48
- package/dist/pending-restore.js +7 -43
- package/dist/single-copy-cleanup-runner.js +9 -45
- package/dist/single-copy-cleanup.js +7 -15
- package/dist/sqlite-restore.js +9 -48
- package/dist/starter.js +4 -39
- package/dist/ui-serving/lazy-ui-serving.js +3 -39
- package/package.json +13 -13
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Core-capability mesh bridge.
|
|
4
3
|
*
|
|
@@ -17,22 +16,13 @@
|
|
|
17
16
|
* service mounted on the hub node, `brokerTransportLink` resolves and
|
|
18
17
|
* routes the call exactly like any addon-provided cap.
|
|
19
18
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
exports.describeMeshOrigin = describeMeshOrigin;
|
|
28
|
-
exports.buildCoreCapService = buildCoreCapService;
|
|
29
|
-
const system_1 = require("@camstack/system");
|
|
30
|
-
const moleculer_1 = require("moleculer");
|
|
31
|
-
const remote_addon_update_grant_js_1 = require("./remote-addon-update-grant.js");
|
|
32
|
-
const http_request_census_singleton_js_1 = require("./http-request-census.singleton.js");
|
|
33
|
-
const server_provided_caps_js_1 = require("./server-provided-caps.js");
|
|
34
|
-
const trpc_context_js_1 = require("./trpc.context.js");
|
|
35
|
-
const trpc_middleware_js_1 = require("./trpc.middleware.js");
|
|
19
|
+
import { createCoreCapService } from '@camstack/system';
|
|
20
|
+
import { Errors } from 'moleculer';
|
|
21
|
+
import { authorizeRemoteUpdatePackage, } from './remote-addon-update-grant.js';
|
|
22
|
+
import { getHttpRequestCensus } from './http-request-census.singleton.js';
|
|
23
|
+
import { HUB_SERVICE_CAP_NAMES, capNameToNamespace } from './server-provided-caps.js';
|
|
24
|
+
import { createMeshTrpcContext } from './trpc.context.js';
|
|
25
|
+
import { createCallerFactory } from './trpc.middleware.js';
|
|
36
26
|
/**
|
|
37
27
|
* appRouter namespaces that back the CORE API surface and must be
|
|
38
28
|
* reachable cross-process: every server-provided hub-service cap
|
|
@@ -54,7 +44,7 @@ const trpc_middleware_js_1 = require("./trpc.middleware.js");
|
|
|
54
44
|
*/
|
|
55
45
|
const CORE_NAMESPACES = new Set([
|
|
56
46
|
// Service-backed cap routers (no addon provider) — derived, never copied.
|
|
57
|
-
...
|
|
47
|
+
...HUB_SERVICE_CAP_NAMES.map(capNameToNamespace),
|
|
58
48
|
// Hand-written single-impl core routers.
|
|
59
49
|
'capabilities',
|
|
60
50
|
'notifications',
|
|
@@ -71,18 +61,18 @@ const CORE_NAMESPACES = new Set([
|
|
|
71
61
|
* (they read their own settings through the settings door), so refusing them
|
|
72
62
|
* to an agent costs nothing and takes them off the LAN.
|
|
73
63
|
*/
|
|
74
|
-
|
|
64
|
+
export const REMOTE_DENIED_NAMESPACES = new Set([
|
|
75
65
|
'logs',
|
|
76
66
|
'addonSettingsRaw',
|
|
77
67
|
]);
|
|
78
68
|
/** Moleculer error `type` of a bridged call another node may not make. */
|
|
79
|
-
|
|
69
|
+
export const CORE_CAP_REMOTE_FORBIDDEN_TYPE = 'CORE_CAP_REMOTE_FORBIDDEN';
|
|
80
70
|
/**
|
|
81
71
|
* Hub-core procedure paths another node may call after {@link REMOTE_DENIED_NAMESPACES}.
|
|
82
72
|
* Seeded from armed transport census on the live hub (2026-10): remote agents
|
|
83
73
|
* (`mesh:<agent>`) called none of these; hub runners used this surface only.
|
|
84
74
|
*/
|
|
85
|
-
|
|
75
|
+
export const REMOTE_PRIVILEGE_ALLOWLIST = new Set([
|
|
86
76
|
'addons.listCapabilityProviders',
|
|
87
77
|
'nodes.topology',
|
|
88
78
|
'integrations.getByAddonId',
|
|
@@ -92,20 +82,20 @@ exports.REMOTE_PRIVILEGE_ALLOWLIST = new Set([
|
|
|
92
82
|
'system.getSiteLocation',
|
|
93
83
|
]);
|
|
94
84
|
/** The one path outside the allowlist a node may call, under {@link authorizeRemoteUpdatePackage}. */
|
|
95
|
-
|
|
85
|
+
export const REMOTE_UPDATE_PACKAGE_PATH = 'addons.updatePackage';
|
|
96
86
|
/**
|
|
97
87
|
* When true, remote bridged calls outside {@link REMOTE_PRIVILEGE_ALLOWLIST} are
|
|
98
88
|
* logged once per `(nodeId, path)` per process — still admin, not refused.
|
|
99
89
|
*/
|
|
100
|
-
|
|
90
|
+
export const REMOTE_PRIVILEGE_LOG_ONLY = false;
|
|
101
91
|
let remotePrivilegeLogSink = null;
|
|
102
92
|
/** Attach the warn sink used by {@link observeRemotePrivilege}. */
|
|
103
|
-
function setRemotePrivilegeLogSink(sink) {
|
|
93
|
+
export function setRemotePrivilegeLogSink(sink) {
|
|
104
94
|
remotePrivilegeLogSink = sink;
|
|
105
95
|
}
|
|
106
96
|
const remotePrivilegeObserved = new Set();
|
|
107
97
|
/** Clears dedupe state and the sink — tests only. */
|
|
108
|
-
function __resetRemotePrivilegeObservationForTests() {
|
|
98
|
+
export function __resetRemotePrivilegeObservationForTests() {
|
|
109
99
|
remotePrivilegeObserved.clear();
|
|
110
100
|
remotePrivilegeLogSink = null;
|
|
111
101
|
}
|
|
@@ -125,8 +115,8 @@ function warnRemotePrivilegeOnce(nodeId, path, message) {
|
|
|
125
115
|
* One warn per `(nodeId, path)` while {@link REMOTE_PRIVILEGE_LOG_ONLY} is on.
|
|
126
116
|
* Call only after {@link REMOTE_DENIED_NAMESPACES} has passed.
|
|
127
117
|
*/
|
|
128
|
-
function observeRemotePrivilege(nodeId, path) {
|
|
129
|
-
if (!
|
|
118
|
+
export function observeRemotePrivilege(nodeId, path) {
|
|
119
|
+
if (!REMOTE_PRIVILEGE_LOG_ONLY)
|
|
130
120
|
return;
|
|
131
121
|
warnRemotePrivilegeOnce(nodeId, path, `mesh privilege: node:${nodeId} called ${path} (log-only; still allowed)`);
|
|
132
122
|
}
|
|
@@ -134,7 +124,7 @@ function observeRemotePrivilege(nodeId, path) {
|
|
|
134
124
|
* One warn per `(nodeId, path)` when enforcement refuses a path not on
|
|
135
125
|
* {@link REMOTE_PRIVILEGE_ALLOWLIST}. Call immediately before throwing.
|
|
136
126
|
*/
|
|
137
|
-
function observeRemotePrivilegeRefused(nodeId, path) {
|
|
127
|
+
export function observeRemotePrivilegeRefused(nodeId, path) {
|
|
138
128
|
warnRemotePrivilegeOnce(nodeId, path, `mesh privilege: node:${nodeId} called ${path} (refused; not on grant)`);
|
|
139
129
|
}
|
|
140
130
|
/**
|
|
@@ -162,7 +152,7 @@ const CORE_CAP_NAMES = new Set([
|
|
|
162
152
|
* already-maintained {@link CORE_NAMESPACES} — a new addon cap is covered with
|
|
163
153
|
* zero changes here.
|
|
164
154
|
*/
|
|
165
|
-
function isCoreServiceCapName(capName) {
|
|
155
|
+
export function isCoreServiceCapName(capName) {
|
|
166
156
|
return CORE_CAP_NAMES.has(capName);
|
|
167
157
|
}
|
|
168
158
|
function isProcedureNode(value) {
|
|
@@ -222,7 +212,7 @@ function countMeshCall(census, path, origin) {
|
|
|
222
212
|
* principal. Never a token: there is none, the mesh context is trusted by
|
|
223
213
|
* construction (`createMeshTrpcContext`).
|
|
224
214
|
*/
|
|
225
|
-
function describeMeshOrigin(origin) {
|
|
215
|
+
export function describeMeshOrigin(origin) {
|
|
226
216
|
const node = origin.nodeId ?? 'unknown';
|
|
227
217
|
return origin.caller === null ? `mesh:${node}` : `mesh:${node} via ${origin.caller}`;
|
|
228
218
|
}
|
|
@@ -251,13 +241,13 @@ function resolveInvoker(caller, path) {
|
|
|
251
241
|
* Each call runs under a principal naming its origin (D802): `mesh` for the
|
|
252
242
|
* hub's own runners, `node:<nodeId>` for another node.
|
|
253
243
|
*/
|
|
254
|
-
function buildCoreCapService(appRouter, census =
|
|
255
|
-
const callerFactory =
|
|
244
|
+
export function buildCoreCapService(appRouter, census = getHttpRequestCensus(), options = {}) {
|
|
245
|
+
const callerFactory = createCallerFactory(appRouter);
|
|
256
246
|
const callers = new Map();
|
|
257
247
|
const callerFor = (principal) => {
|
|
258
248
|
let caller = callers.get(principal);
|
|
259
249
|
if (caller === undefined) {
|
|
260
|
-
caller = callerFactory(
|
|
250
|
+
caller = callerFactory(createMeshTrpcContext(principal));
|
|
261
251
|
callers.set(principal, caller);
|
|
262
252
|
}
|
|
263
253
|
return caller;
|
|
@@ -282,22 +272,22 @@ function buildCoreCapService(appRouter, census = (0, http_request_census_singlet
|
|
|
282
272
|
invoke: async (input, origin) => {
|
|
283
273
|
countMeshCall(census, path, origin);
|
|
284
274
|
const remoteNode = remoteOrigin(origin, options.ownNodeId);
|
|
285
|
-
if (remoteNode !== null &&
|
|
286
|
-
throw new
|
|
275
|
+
if (remoteNode !== null && REMOTE_DENIED_NAMESPACES.has(namespace)) {
|
|
276
|
+
throw new Errors.MoleculerClientError(`"${path}" is not callable from another node (caller "${remoteNode}")`, 403, CORE_CAP_REMOTE_FORBIDDEN_TYPE);
|
|
287
277
|
}
|
|
288
278
|
if (remoteNode !== null) {
|
|
289
|
-
if (
|
|
279
|
+
if (REMOTE_PRIVILEGE_LOG_ONLY) {
|
|
290
280
|
observeRemotePrivilege(remoteNode, path);
|
|
291
281
|
}
|
|
292
|
-
else if (path ===
|
|
293
|
-
const decision = await
|
|
282
|
+
else if (path === REMOTE_UPDATE_PACKAGE_PATH && options.remoteUpdateGrant) {
|
|
283
|
+
const decision = await authorizeRemoteUpdatePackage(remoteNode, input, options.remoteUpdateGrant);
|
|
294
284
|
if (!decision.allowed) {
|
|
295
|
-
throw new
|
|
285
|
+
throw new Errors.MoleculerClientError(`"${path}" refused for caller "${remoteNode}": ${decision.reason ?? 'not permitted'}`, 403, CORE_CAP_REMOTE_FORBIDDEN_TYPE);
|
|
296
286
|
}
|
|
297
287
|
}
|
|
298
|
-
else if (!
|
|
288
|
+
else if (!REMOTE_PRIVILEGE_ALLOWLIST.has(path)) {
|
|
299
289
|
observeRemotePrivilegeRefused(remoteNode, path);
|
|
300
|
-
throw new
|
|
290
|
+
throw new Errors.MoleculerClientError(`"${path}" is not on the remote privilege grant (caller "${remoteNode}")`, 403, CORE_CAP_REMOTE_FORBIDDEN_TYPE);
|
|
301
291
|
}
|
|
302
292
|
}
|
|
303
293
|
const invoke = resolveInvoker(callerFor(remoteNode === null ? 'mesh' : `node:${remoteNode}`), path);
|
|
@@ -307,7 +297,7 @@ function buildCoreCapService(appRouter, census = (0, http_request_census_singlet
|
|
|
307
297
|
},
|
|
308
298
|
});
|
|
309
299
|
}
|
|
310
|
-
return
|
|
300
|
+
return createCoreCapService({ actions });
|
|
311
301
|
}
|
|
312
302
|
/** The calling node when it is not the hub itself; `null` for the hub's own calls. */
|
|
313
303
|
function remoteOrigin(origin, ownNodeId) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* The per-device COLLECTION dispatcher — a capability whose providers are
|
|
4
3
|
* SOURCES of one device, listed beside each other.
|
|
@@ -60,12 +59,9 @@
|
|
|
60
59
|
* camera with one provider behaves exactly as it did before this mount
|
|
61
60
|
* existed, and it is pinned by spec.
|
|
62
61
|
*/
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const system_1 = require("@camstack/system");
|
|
67
|
-
const types_1 = require("@camstack/types");
|
|
68
|
-
const server_1 = require("@trpc/server");
|
|
62
|
+
import { CAP_METHOD_NOT_IMPLEMENTED } from '@camstack/system';
|
|
63
|
+
import { deviceExtensionCapability, EXTENSION_SLOT_LISTING_METHOD, expandCapMethods, isArrayOutputSchema, reservedSourceOwner, unreachableExtensionSlotRow, } from '@camstack/types';
|
|
64
|
+
import { TRPCError } from '@trpc/server';
|
|
69
65
|
/**
|
|
70
66
|
* The device-collection CONVENTION: a cap whose providers are sources declares
|
|
71
67
|
* this method, taking `{ deviceId }` and returning one row per source with a
|
|
@@ -73,7 +69,7 @@ const server_1 = require("@trpc/server");
|
|
|
73
69
|
* without a second registry of id prefixes — the provider that mints the ids
|
|
74
70
|
* is the one that names them.
|
|
75
71
|
*/
|
|
76
|
-
|
|
72
|
+
export const SOURCE_LISTING_METHOD = 'listSources';
|
|
77
73
|
/** The field on a source row that carries its id / clip-id namespace. */
|
|
78
74
|
const SOURCE_ID_FIELD = 'source';
|
|
79
75
|
/** The field on a source row that names the addon serving it (D555/D557). */
|
|
@@ -219,10 +215,10 @@ function isFanOutMethod(registry, capName, method) {
|
|
|
219
215
|
const def = registry.getDefinition(capName);
|
|
220
216
|
if (def === undefined)
|
|
221
217
|
return false;
|
|
222
|
-
const schema =
|
|
218
|
+
const schema = expandCapMethods(def)[method];
|
|
223
219
|
if (schema === undefined || schema.kind === 'subscription')
|
|
224
220
|
return false;
|
|
225
|
-
return
|
|
221
|
+
return isArrayOutputSchema(schema.output);
|
|
226
222
|
}
|
|
227
223
|
function errMessage(err) {
|
|
228
224
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -231,7 +227,7 @@ function errMessage(err) {
|
|
|
231
227
|
async function callSource(source, method, input, capName, deviceId) {
|
|
232
228
|
const fn = source.provider[method];
|
|
233
229
|
if (typeof fn !== 'function') {
|
|
234
|
-
throw new
|
|
230
|
+
throw new TRPCError({
|
|
235
231
|
code: 'NOT_IMPLEMENTED',
|
|
236
232
|
message: notImplementedMessage(capName, source.addonId, deviceId, method),
|
|
237
233
|
});
|
|
@@ -246,8 +242,8 @@ async function callSource(source, method, input, capName, deviceId) {
|
|
|
246
242
|
return await fn.call(source.provider, input);
|
|
247
243
|
}
|
|
248
244
|
catch (err) {
|
|
249
|
-
if (errMessage(err).includes(
|
|
250
|
-
throw new
|
|
245
|
+
if (errMessage(err).includes(CAP_METHOD_NOT_IMPLEMENTED)) {
|
|
246
|
+
throw new TRPCError({
|
|
251
247
|
code: 'NOT_IMPLEMENTED',
|
|
252
248
|
message: notImplementedMessage(capName, source.addonId, deviceId, method),
|
|
253
249
|
});
|
|
@@ -332,18 +328,18 @@ function unreachableSourceRow(addonId, reason) {
|
|
|
332
328
|
* behaviour, D554 decision 6).
|
|
333
329
|
*/
|
|
334
330
|
const CAP_FAILURE_ROWS = {
|
|
335
|
-
[
|
|
336
|
-
[
|
|
331
|
+
[deviceExtensionCapability.name]: {
|
|
332
|
+
[EXTENSION_SLOT_LISTING_METHOD]: unreachableExtensionSlotRow,
|
|
337
333
|
},
|
|
338
334
|
};
|
|
339
335
|
function failureRowFor(capName, method) {
|
|
340
336
|
const own = CAP_FAILURE_ROWS[capName]?.[method];
|
|
341
337
|
if (own !== undefined)
|
|
342
338
|
return own;
|
|
343
|
-
return method ===
|
|
339
|
+
return method === SOURCE_LISTING_METHOD ? unreachableSourceRow : null;
|
|
344
340
|
}
|
|
345
341
|
async function fanOut(sources, method, input, capName, deviceId, logger) {
|
|
346
|
-
const isSourceListing = method ===
|
|
342
|
+
const isSourceListing = method === SOURCE_LISTING_METHOD;
|
|
347
343
|
const failureRow = failureRowFor(capName, method);
|
|
348
344
|
const settled = await Promise.allSettled(sources.map(async (source) => await callSource(source, method, input, capName, deviceId)));
|
|
349
345
|
const rows = [];
|
|
@@ -403,7 +399,7 @@ function finishRows(source, rows, isSourceListing, capName, deviceId, logger) {
|
|
|
403
399
|
}
|
|
404
400
|
const id = Reflect.get(row, SOURCE_ID_FIELD);
|
|
405
401
|
if (typeof id === 'string') {
|
|
406
|
-
const owner =
|
|
402
|
+
const owner = reservedSourceOwner(capName, id);
|
|
407
403
|
if (owner !== null && owner !== source.addonId) {
|
|
408
404
|
// A RESERVED id (D625) is the DEFAULT of a picker, expressed as a
|
|
409
405
|
// value rather than as a sort order. A second row spelling it would
|
|
@@ -413,7 +409,7 @@ function finishRows(source, rows, isSourceListing, capName, deviceId, logger) {
|
|
|
413
409
|
// and emptying a camera's picker over one bad row is worse than the
|
|
414
410
|
// bad row. The guard in `scripts/` refuses the same spelling at build
|
|
415
411
|
// time; this is what happens when an old build reaches a new hub.
|
|
416
|
-
logger?.warn(`${capName}.${
|
|
412
|
+
logger?.warn(`${capName}.${SOURCE_LISTING_METHOD}: source id "${id}" is RESERVED for "${owner}" — this addon's row is dropped`, {
|
|
417
413
|
tags: { deviceId, addonId: source.addonId },
|
|
418
414
|
meta: { capability: capName, sourceId: id, reservedFor: owner },
|
|
419
415
|
});
|
|
@@ -427,7 +423,7 @@ function finishRows(source, rows, isSourceListing, capName, deviceId, logger) {
|
|
|
427
423
|
async function claimsOf(sources, capName, deviceId, logger) {
|
|
428
424
|
const claimed = new Map();
|
|
429
425
|
const unknownClaims = [];
|
|
430
|
-
const settled = await Promise.allSettled(sources.map(async (source) => await callSource(source,
|
|
426
|
+
const settled = await Promise.allSettled(sources.map(async (source) => await callSource(source, SOURCE_LISTING_METHOD, { deviceId }, capName, deviceId)));
|
|
431
427
|
for (const [index, result] of settled.entries()) {
|
|
432
428
|
const source = sources[index];
|
|
433
429
|
if (source === undefined)
|
|
@@ -435,7 +431,7 @@ async function claimsOf(sources, capName, deviceId, logger) {
|
|
|
435
431
|
if (result.status === 'rejected' || !Array.isArray(result.value)) {
|
|
436
432
|
// It could not name its sources, so it cannot be excluded on that basis
|
|
437
433
|
// either — a read that FAILS must not destroy work (D49).
|
|
438
|
-
logger?.warn(`${capName}.${
|
|
434
|
+
logger?.warn(`${capName}.${SOURCE_LISTING_METHOD}: source could not name its ids — routing falls back to it only if it is the only such source`, {
|
|
439
435
|
tags: { deviceId, addonId: source.addonId },
|
|
440
436
|
meta: {
|
|
441
437
|
capability: capName,
|
|
@@ -501,7 +497,7 @@ async function dispatchScalar(sources, method, input, capName, deviceId, logger)
|
|
|
501
497
|
tags: { deviceId },
|
|
502
498
|
meta: { capability: capName, method, knownSources: known, input },
|
|
503
499
|
});
|
|
504
|
-
throw new
|
|
500
|
+
throw new TRPCError({
|
|
505
501
|
code: 'BAD_REQUEST',
|
|
506
502
|
message: `Capability "${capName}.${method}": no source of device ${deviceId} claims ` +
|
|
507
503
|
`${JSON.stringify(input)}. Sources: ${known.length > 0 ? known.join(', ') : '(none named)'}. ` +
|
|
@@ -526,7 +522,7 @@ function pinnedProvider(sources, input, capName, deviceId) {
|
|
|
526
522
|
const picked = sources.filter((source) => source.addonId === pin);
|
|
527
523
|
if (picked.length > 0)
|
|
528
524
|
return picked;
|
|
529
|
-
throw new
|
|
525
|
+
throw new TRPCError({
|
|
530
526
|
code: 'BAD_REQUEST',
|
|
531
527
|
message: `Capability "${capName}": device ${deviceId} is not bound to provider "${pin}". ` +
|
|
532
528
|
`Bound providers: ${sources.map((source) => source.addonId).join(', ') || '(none)'}. ` +
|
|
@@ -541,7 +537,7 @@ function pinnedProvider(sources, input, capName, deviceId) {
|
|
|
541
537
|
* Returns null without a registry (the router then surfaces
|
|
542
538
|
* PRECONDITION_FAILED), exactly as the singleton dispatcher does.
|
|
543
539
|
*/
|
|
544
|
-
function requireDeviceCollection(registry, capName, logger) {
|
|
540
|
+
export function requireDeviceCollection(registry, capName, logger) {
|
|
545
541
|
if (!registry)
|
|
546
542
|
return null;
|
|
547
543
|
const dispatcher = new Proxy({}, {
|
|
@@ -551,7 +547,7 @@ function requireDeviceCollection(registry, capName, logger) {
|
|
|
551
547
|
return async (input) => {
|
|
552
548
|
const deviceId = input !== null && typeof input === 'object' ? Reflect.get(input, 'deviceId') : undefined;
|
|
553
549
|
if (typeof deviceId !== 'number') {
|
|
554
|
-
throw new
|
|
550
|
+
throw new TRPCError({
|
|
555
551
|
code: 'BAD_REQUEST',
|
|
556
552
|
message: `${capName}.${prop}: input must carry numeric "deviceId"`,
|
|
557
553
|
});
|
|
@@ -559,7 +555,7 @@ function requireDeviceCollection(registry, capName, logger) {
|
|
|
559
555
|
const bound = await sourcesForDevice(registry, capName, deviceId, logger);
|
|
560
556
|
const sources = pinnedProvider(bound, input, capName, deviceId);
|
|
561
557
|
if (sources.length === 0) {
|
|
562
|
-
throw new
|
|
558
|
+
throw new TRPCError({
|
|
563
559
|
code: 'PRECONDITION_FAILED',
|
|
564
560
|
message: `Capability "${capName}" not registered for device ${deviceId}`,
|
|
565
561
|
});
|
|
@@ -580,7 +576,7 @@ function requireDeviceCollection(registry, capName, logger) {
|
|
|
580
576
|
const answered = await callSource(first, prop, input, capName, deviceId);
|
|
581
577
|
if (!Array.isArray(answered))
|
|
582
578
|
return answered;
|
|
583
|
-
return finishRows(first, answered, prop ===
|
|
579
|
+
return finishRows(first, answered, prop === SOURCE_LISTING_METHOD, capName, deviceId, logger);
|
|
584
580
|
}
|
|
585
581
|
return await fanOut(sources, prop, input, capName, deviceId, logger);
|
|
586
582
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Device-config secret redaction for NON-ADMIN principals.
|
|
4
3
|
*
|
|
@@ -39,26 +38,11 @@
|
|
|
39
38
|
* Pure module — no tRPC machinery — so the spec exercises it directly (same
|
|
40
39
|
* pattern as `scope-access.ts` and `share-view-access.ts`).
|
|
41
40
|
*/
|
|
42
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
-
exports.NON_ADMIN_CONFIG_REDACTED_METHODS = exports.REDACTED_SECRET = void 0;
|
|
44
|
-
exports.isSecretConfigKey = isSecretConfigKey;
|
|
45
|
-
exports.redactDeviceInfoSecrets = redactDeviceInfoSecrets;
|
|
46
|
-
exports.redactDeviceInfoRecordSecrets = redactDeviceInfoRecordSecrets;
|
|
47
|
-
exports.redactSettingsSections = redactSettingsSections;
|
|
48
|
-
exports.redactSettingsAggregate = redactSettingsAggregate;
|
|
49
|
-
exports.redactSettingsForm = redactSettingsForm;
|
|
50
|
-
exports.redactExportSetup = redactExportSetup;
|
|
51
|
-
exports.redactDeepSecrets = redactDeepSecrets;
|
|
52
|
-
exports.redactStatusAggregateSetup = redactStatusAggregateSetup;
|
|
53
|
-
exports.redactStatusAggregateBatchSetup = redactStatusAggregateBatchSetup;
|
|
54
|
-
exports.redactConfigEntries = redactConfigEntries;
|
|
55
|
-
exports.redactStreamUrlSecrets = redactStreamUrlSecrets;
|
|
56
|
-
exports.maskUrlCredentialsDeep = maskUrlCredentialsDeep;
|
|
57
41
|
/** What a non-admin sees instead of the value. Matches the `***` convention
|
|
58
42
|
* `maskUrlCredentials` already uses for credentials in log output, so one
|
|
59
43
|
* redaction marker means one thing across the system. */
|
|
60
|
-
|
|
61
|
-
|
|
44
|
+
import { maskUrlCredentials } from '@camstack/types';
|
|
45
|
+
export const REDACTED_SECRET = '***';
|
|
62
46
|
/**
|
|
63
47
|
* Config keys whose VALUE is a credential.
|
|
64
48
|
*
|
|
@@ -79,7 +63,7 @@ const SECRET_KEY_SUBSTRINGS = [
|
|
|
79
63
|
'api_key',
|
|
80
64
|
];
|
|
81
65
|
/** True when a config key names a credential. Case-insensitive substring match. */
|
|
82
|
-
function isSecretConfigKey(key) {
|
|
66
|
+
export function isSecretConfigKey(key) {
|
|
83
67
|
const lower = key.toLowerCase();
|
|
84
68
|
return SECRET_KEY_SUBSTRINGS.some((needle) => lower.includes(needle));
|
|
85
69
|
}
|
|
@@ -103,7 +87,7 @@ function declaredPathsOf(row) {
|
|
|
103
87
|
/** Withhold every non-empty string at or below `value` (a declared secret node). */
|
|
104
88
|
function withholdAll(value) {
|
|
105
89
|
if (typeof value === 'string')
|
|
106
|
-
return value.length > 0 ?
|
|
90
|
+
return value.length > 0 ? REDACTED_SECRET : value;
|
|
107
91
|
if (Array.isArray(value))
|
|
108
92
|
return value.map(withholdAll);
|
|
109
93
|
if (!isRecord(value))
|
|
@@ -140,7 +124,7 @@ function withholdPath(value, segments) {
|
|
|
140
124
|
*/
|
|
141
125
|
function backstop(value) {
|
|
142
126
|
if (typeof value === 'string') {
|
|
143
|
-
return value.includes('://') ?
|
|
127
|
+
return value.includes('://') ? maskUrlCredentials(value) : value;
|
|
144
128
|
}
|
|
145
129
|
if (Array.isArray(value))
|
|
146
130
|
return value.map(backstop);
|
|
@@ -150,7 +134,7 @@ function backstop(value) {
|
|
|
150
134
|
for (const [key, child] of Object.entries(value)) {
|
|
151
135
|
out[key] =
|
|
152
136
|
isSecretConfigKey(key) && typeof child === 'string' && child.length > 0
|
|
153
|
-
?
|
|
137
|
+
? REDACTED_SECRET
|
|
154
138
|
: backstop(child);
|
|
155
139
|
}
|
|
156
140
|
return out;
|
|
@@ -174,7 +158,7 @@ function redactConfigBlob(config, declared) {
|
|
|
174
158
|
* Anything that is not a device row passes through untouched — a route that
|
|
175
159
|
* errored upstream must not be rewritten into a plausible-looking shape.
|
|
176
160
|
*/
|
|
177
|
-
function redactDeviceInfoSecrets(data) {
|
|
161
|
+
export function redactDeviceInfoSecrets(data) {
|
|
178
162
|
if (Array.isArray(data))
|
|
179
163
|
return data.map((row) => redactDeviceInfoSecrets(row));
|
|
180
164
|
if (!isRecord(data))
|
|
@@ -193,7 +177,7 @@ function redactDeviceInfoSecrets(data) {
|
|
|
193
177
|
* its own, so the function returns early having redacted nothing. A batch of a
|
|
194
178
|
* single-device read must never be the ungated way to ask the same question.
|
|
195
179
|
*/
|
|
196
|
-
function redactDeviceInfoRecordSecrets(data) {
|
|
180
|
+
export function redactDeviceInfoRecordSecrets(data) {
|
|
197
181
|
if (!isRecord(data))
|
|
198
182
|
return data;
|
|
199
183
|
const out = {};
|
|
@@ -205,7 +189,7 @@ function redactDeviceInfoRecordSecrets(data) {
|
|
|
205
189
|
* Project a `SettingsSchemaWithValues` (`{ sections: [{ fields: [...] }] }`).
|
|
206
190
|
* Used by `getDeviceSettingsAggregate` and `getSettingsSchema`.
|
|
207
191
|
*/
|
|
208
|
-
function redactSettingsSections(data) {
|
|
192
|
+
export function redactSettingsSections(data) {
|
|
209
193
|
if (!isRecord(data))
|
|
210
194
|
return data;
|
|
211
195
|
const sections = data['sections'];
|
|
@@ -230,7 +214,7 @@ function redactSettingsSections(data) {
|
|
|
230
214
|
* Project the combined aggregate, whose settings live one level down under
|
|
231
215
|
* `settings`. The live half carries no config blob.
|
|
232
216
|
*/
|
|
233
|
-
function redactSettingsAggregate(data) {
|
|
217
|
+
export function redactSettingsAggregate(data) {
|
|
234
218
|
if (!isRecord(data))
|
|
235
219
|
return data;
|
|
236
220
|
const settings = data['settings'];
|
|
@@ -275,8 +259,8 @@ function redactFormValue(value, secret, itemFields) {
|
|
|
275
259
|
if (value.length === 0)
|
|
276
260
|
return value;
|
|
277
261
|
if (secret)
|
|
278
|
-
return
|
|
279
|
-
return value.includes('://') ?
|
|
262
|
+
return REDACTED_SECRET;
|
|
263
|
+
return value.includes('://') ? maskUrlCredentials(value) : value;
|
|
280
264
|
}
|
|
281
265
|
if (Array.isArray(value))
|
|
282
266
|
return value.map((el) => redactFormValue(el, secret, itemFields));
|
|
@@ -318,7 +302,7 @@ function redactFormField(field) {
|
|
|
318
302
|
// The HomeKit QR caption spells the setup code out in prose.
|
|
319
303
|
const caption = field['caption'];
|
|
320
304
|
return field['type'] === 'qr-code' && typeof caption === 'string' && caption.length > 0
|
|
321
|
-
? { ...redacted, caption:
|
|
305
|
+
? { ...redacted, caption: REDACTED_SECRET }
|
|
322
306
|
: redacted;
|
|
323
307
|
}
|
|
324
308
|
/**
|
|
@@ -335,7 +319,7 @@ function redactFormField(field) {
|
|
|
335
319
|
* and `updateGlobalSettings` / `updateDeviceSettings` are admin-only, so a
|
|
336
320
|
* redacted value can never be written back (D634).
|
|
337
321
|
*/
|
|
338
|
-
function redactSettingsForm(data) {
|
|
322
|
+
export function redactSettingsForm(data) {
|
|
339
323
|
if (!isRecord(data))
|
|
340
324
|
return data;
|
|
341
325
|
const sections = data['sections'];
|
|
@@ -360,7 +344,7 @@ function redactSettingsForm(data) {
|
|
|
360
344
|
* panel renders it, so a non-admin keeps the link state and counts and never
|
|
361
345
|
* receives the block (D634 r2). One object or a fanned-out array.
|
|
362
346
|
*/
|
|
363
|
-
function redactExportSetup(data) {
|
|
347
|
+
export function redactExportSetup(data) {
|
|
364
348
|
if (Array.isArray(data))
|
|
365
349
|
return data.map((row) => redactExportSetup(row));
|
|
366
350
|
if (!isRecord(data) || !('setup' in data))
|
|
@@ -375,7 +359,7 @@ function redactExportSetup(data) {
|
|
|
375
359
|
* `access_token` and `?token=` picture URLs), an integration's `info`, a
|
|
376
360
|
* broker list's `url` (D634 r3).
|
|
377
361
|
*/
|
|
378
|
-
function redactDeepSecrets(data) {
|
|
362
|
+
export function redactDeepSecrets(data) {
|
|
379
363
|
return redactFormValue(data, false, undefined);
|
|
380
364
|
}
|
|
381
365
|
/** The status-aggregate entry that carries export pairing material. */
|
|
@@ -385,13 +369,13 @@ const DEVICE_EXPORT_CAP = 'device-export';
|
|
|
385
369
|
* `device-export` entry loses its `setup` (D634 r3 — defence in depth; the
|
|
386
370
|
* aggregation itself no longer asks a system collection cap per device).
|
|
387
371
|
*/
|
|
388
|
-
function redactStatusAggregateSetup(data) {
|
|
372
|
+
export function redactStatusAggregateSetup(data) {
|
|
389
373
|
if (!isRecord(data) || !(DEVICE_EXPORT_CAP in data))
|
|
390
374
|
return data;
|
|
391
375
|
return { ...data, [DEVICE_EXPORT_CAP]: redactExportSetup(data[DEVICE_EXPORT_CAP]) };
|
|
392
376
|
}
|
|
393
377
|
/** Project a batch status aggregate (`Record<deviceId, Record<capName, status>>`). */
|
|
394
|
-
function redactStatusAggregateBatchSetup(data) {
|
|
378
|
+
export function redactStatusAggregateBatchSetup(data) {
|
|
395
379
|
if (!isRecord(data))
|
|
396
380
|
return data;
|
|
397
381
|
const out = {};
|
|
@@ -401,7 +385,7 @@ function redactStatusAggregateBatchSetup(data) {
|
|
|
401
385
|
return out;
|
|
402
386
|
}
|
|
403
387
|
/** Project a flat `ConfigEntry[]` — `getConfigSchema`'s `{ key, value }` rows. */
|
|
404
|
-
function redactConfigEntries(data) {
|
|
388
|
+
export function redactConfigEntries(data) {
|
|
405
389
|
if (!Array.isArray(data))
|
|
406
390
|
return data;
|
|
407
391
|
return data.map(redactFormField);
|
|
@@ -428,7 +412,7 @@ const URL_FIELD_KEYS = ['url', 'mutedUrl'];
|
|
|
428
412
|
* (`codec`, `label`) and a non-string value pass through. Non-row payloads
|
|
429
413
|
* pass through untouched — an upstream error must not be rewritten.
|
|
430
414
|
*/
|
|
431
|
-
function redactStreamUrlSecrets(data) {
|
|
415
|
+
export function redactStreamUrlSecrets(data) {
|
|
432
416
|
if (Array.isArray(data))
|
|
433
417
|
return data.map((row) => redactStreamUrlSecrets(row));
|
|
434
418
|
if (!isRecord(data))
|
|
@@ -437,7 +421,7 @@ function redactStreamUrlSecrets(data) {
|
|
|
437
421
|
for (const [key, value] of Object.entries(data)) {
|
|
438
422
|
out[key] =
|
|
439
423
|
URL_FIELD_KEYS.includes(key) && typeof value === 'string' && value.length > 0
|
|
440
|
-
?
|
|
424
|
+
? maskUrlCredentials(value)
|
|
441
425
|
: value;
|
|
442
426
|
}
|
|
443
427
|
return out;
|
|
@@ -451,9 +435,9 @@ function redactStreamUrlSecrets(data) {
|
|
|
451
435
|
* any non-admin (D783). Only strings holding `://` are touched; no key is
|
|
452
436
|
* judged by its name here, so `eventTypeTokens` & co. keep their values.
|
|
453
437
|
*/
|
|
454
|
-
function maskUrlCredentialsDeep(data) {
|
|
438
|
+
export function maskUrlCredentialsDeep(data) {
|
|
455
439
|
if (typeof data === 'string')
|
|
456
|
-
return data.includes('://') ?
|
|
440
|
+
return data.includes('://') ? maskUrlCredentials(data) : data;
|
|
457
441
|
if (Array.isArray(data))
|
|
458
442
|
return data.map(maskUrlCredentialsDeep);
|
|
459
443
|
if (!isRecord(data))
|
|
@@ -480,7 +464,7 @@ function maskUrlCredentialsDeep(data) {
|
|
|
480
464
|
* `device-config-secret-redaction.spec.ts` pins the list so the addition is a
|
|
481
465
|
* failing test rather than a silent leak.
|
|
482
466
|
*/
|
|
483
|
-
|
|
467
|
+
export const NON_ADMIN_CONFIG_REDACTED_METHODS = new Map([
|
|
484
468
|
// ── Addon settings forms (D634) — hydrated with the STORED secret ──
|
|
485
469
|
['addonSettings.getGlobalSettings', redactSettingsForm],
|
|
486
470
|
['addonSettings.getIntegrationSettings', redactSettingsForm],
|