@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
|
* Reads the hub's human name (D861) from the one row that holds it,
|
|
4
3
|
* `system-settings` / `server.name`, and falls back to the machine hostname.
|
|
@@ -12,44 +11,37 @@
|
|
|
12
11
|
* hub that looks unnamed because its store is down is not mistaken for one that
|
|
13
12
|
* was never named.
|
|
14
13
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
};
|
|
18
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.ServerNameReader = void 0;
|
|
20
|
-
exports.serverNameReaderFor = serverNameReaderFor;
|
|
21
|
-
const node_os_1 = __importDefault(require("node:os"));
|
|
22
|
-
const types_1 = require("@camstack/types");
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import { errMsg, resolveServerName, SERVER_NAME_SETTING_KEY } from '@camstack/types';
|
|
23
16
|
const SYSTEM_SETTINGS_COLLECTION = 'system-settings';
|
|
24
|
-
class ServerNameReader {
|
|
17
|
+
export class ServerNameReader {
|
|
25
18
|
deps;
|
|
26
19
|
constructor(deps) {
|
|
27
20
|
this.deps = deps;
|
|
28
21
|
}
|
|
29
22
|
async read() {
|
|
30
|
-
const hostname = (this.deps.hostname ??
|
|
23
|
+
const hostname = (this.deps.hostname ?? os.hostname)();
|
|
31
24
|
try {
|
|
32
25
|
const stored = await this.deps.store.get({
|
|
33
26
|
collection: SYSTEM_SETTINGS_COLLECTION,
|
|
34
|
-
key:
|
|
27
|
+
key: SERVER_NAME_SETTING_KEY,
|
|
35
28
|
});
|
|
36
|
-
return
|
|
29
|
+
return resolveServerName(stored, hostname);
|
|
37
30
|
}
|
|
38
31
|
catch (err) {
|
|
39
32
|
this.deps.logger?.warn('server name read failed - answering the hostname default', {
|
|
40
|
-
meta: { error:
|
|
33
|
+
meta: { error: errMsg(err) },
|
|
41
34
|
});
|
|
42
|
-
return
|
|
35
|
+
return resolveServerName(undefined, hostname);
|
|
43
36
|
}
|
|
44
37
|
}
|
|
45
38
|
}
|
|
46
|
-
exports.ServerNameReader = ServerNameReader;
|
|
47
39
|
/**
|
|
48
40
|
* A reader that resolves the `settings-store` cap lazily from the registry (it
|
|
49
41
|
* boots after the routers are built). Without a registry, or before the cap is
|
|
50
42
|
* up, the read throws and {@link ServerNameReader} answers the hostname default.
|
|
51
43
|
*/
|
|
52
|
-
function serverNameReaderFor(registry, logger) {
|
|
44
|
+
export function serverNameReaderFor(registry, logger) {
|
|
53
45
|
return new ServerNameReader({
|
|
54
46
|
store: {
|
|
55
47
|
get: async (input) => {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Site location — the installation's coordinates, as a fact of the SITE.
|
|
4
3
|
*
|
|
@@ -45,15 +44,9 @@
|
|
|
45
44
|
* and a derivation must never overwrite a typed value, which is why
|
|
46
45
|
* `SiteLocationService.detect` refuses when `source === 'operator-set'`.
|
|
47
46
|
*/
|
|
48
|
-
|
|
49
|
-
exports.SiteLocationService = exports.GEO_IP_ENDPOINTS = exports.GEO_IP_TIMEOUT_MS = exports.SITE_LOCATION_KEY = void 0;
|
|
50
|
-
exports.isUsableCoordinate = isUsableCoordinate;
|
|
51
|
-
exports.toStatus = toStatus;
|
|
52
|
-
exports.parseGeoIpPayload = parseGeoIpPayload;
|
|
53
|
-
exports.geolocatePublicIp = geolocatePublicIp;
|
|
54
|
-
const types_1 = require("@camstack/types");
|
|
47
|
+
import { errMsg } from '@camstack/types';
|
|
55
48
|
/** The `system-settings` row this module owns. */
|
|
56
|
-
|
|
49
|
+
export const SITE_LOCATION_KEY = 'site-location';
|
|
57
50
|
const SYSTEM_SETTINGS_COLLECTION = 'system-settings';
|
|
58
51
|
/**
|
|
59
52
|
* The addon store the coordinates used to live in. Read ONCE, lazily, to
|
|
@@ -70,7 +63,7 @@ const LEGACY_LNG_KEY = 'siteLongitude';
|
|
|
70
63
|
* is long enough for a cold TLS handshake to a CDN and short enough that a
|
|
71
64
|
* captive-portal black hole does not look like a hang.
|
|
72
65
|
*/
|
|
73
|
-
|
|
66
|
+
export const GEO_IP_TIMEOUT_MS = 4_000;
|
|
74
67
|
/**
|
|
75
68
|
* No-key, HTTPS geo-IP endpoints, tried in order until one parses.
|
|
76
69
|
*
|
|
@@ -79,7 +72,7 @@ exports.GEO_IP_TIMEOUT_MS = 4_000;
|
|
|
79
72
|
* and the request carries nothing but the hub's IP — which the endpoint would
|
|
80
73
|
* see regardless, since seeing it is the entire mechanism.
|
|
81
74
|
*/
|
|
82
|
-
|
|
75
|
+
export const GEO_IP_ENDPOINTS = [
|
|
83
76
|
'https://ipinfo.io/json',
|
|
84
77
|
'https://ipapi.co/json/',
|
|
85
78
|
];
|
|
@@ -89,7 +82,7 @@ exports.GEO_IP_ENDPOINTS = [
|
|
|
89
82
|
* is treated as absent rather than clamped: a wrong-but-plausible coordinate
|
|
90
83
|
* produces a wrong sunrise silently, which is worse than the declared fallback.
|
|
91
84
|
*/
|
|
92
|
-
function isUsableCoordinate(lat, lng) {
|
|
85
|
+
export function isUsableCoordinate(lat, lng) {
|
|
93
86
|
if (typeof lat !== 'number' || typeof lng !== 'number')
|
|
94
87
|
return false;
|
|
95
88
|
if (!Number.isFinite(lat) || !Number.isFinite(lng))
|
|
@@ -99,7 +92,7 @@ function isUsableCoordinate(lat, lng) {
|
|
|
99
92
|
return lat !== 0 || lng !== 0;
|
|
100
93
|
}
|
|
101
94
|
/** Project a stored row onto the wire shape. */
|
|
102
|
-
function toStatus(record) {
|
|
95
|
+
export function toStatus(record) {
|
|
103
96
|
if (record === null) {
|
|
104
97
|
return { location: null, derivationAttemptedAt: null, derivationError: null };
|
|
105
98
|
}
|
|
@@ -152,7 +145,7 @@ function asRecord(raw) {
|
|
|
152
145
|
* three field names, and an adapter per vendor is how a 30-line fetch becomes a
|
|
153
146
|
* folder.
|
|
154
147
|
*/
|
|
155
|
-
function parseGeoIpPayload(payload) {
|
|
148
|
+
export function parseGeoIpPayload(payload) {
|
|
156
149
|
if (payload === null || typeof payload !== 'object')
|
|
157
150
|
return null;
|
|
158
151
|
const obj = payload;
|
|
@@ -183,12 +176,12 @@ function parseGeoIpPayload(payload) {
|
|
|
183
176
|
* Every failure mode collapses to `null` plus a log line: this is a *default*,
|
|
184
177
|
* and a default that throws would take down whatever asked for it.
|
|
185
178
|
*/
|
|
186
|
-
async function geolocatePublicIp(fetchImpl, logger, endpoints =
|
|
179
|
+
export async function geolocatePublicIp(fetchImpl, logger, endpoints = GEO_IP_ENDPOINTS) {
|
|
187
180
|
for (const url of endpoints) {
|
|
188
181
|
try {
|
|
189
182
|
const response = await fetchImpl(url, {
|
|
190
183
|
headers: { Accept: 'application/json' },
|
|
191
|
-
signal: AbortSignal.timeout(
|
|
184
|
+
signal: AbortSignal.timeout(GEO_IP_TIMEOUT_MS),
|
|
192
185
|
});
|
|
193
186
|
if (!response.ok) {
|
|
194
187
|
logger?.debug('geo-IP endpoint refused', { meta: { url, status: response.status } });
|
|
@@ -200,7 +193,7 @@ async function geolocatePublicIp(fetchImpl, logger, endpoints = exports.GEO_IP_E
|
|
|
200
193
|
logger?.debug('geo-IP endpoint answered without usable coordinates', { meta: { url } });
|
|
201
194
|
}
|
|
202
195
|
catch (err) {
|
|
203
|
-
logger?.debug('geo-IP endpoint failed', { meta: { url, error:
|
|
196
|
+
logger?.debug('geo-IP endpoint failed', { meta: { url, error: errMsg(err) } });
|
|
204
197
|
}
|
|
205
198
|
}
|
|
206
199
|
return null;
|
|
@@ -213,7 +206,7 @@ async function geolocatePublicIp(fetchImpl, logger, endpoints = exports.GEO_IP_E
|
|
|
213
206
|
* `derivationAttemptedAt` bounds them across requests and across restarts. The
|
|
214
207
|
* durable marker is the real guarantee; the in-process one only stops a burst.
|
|
215
208
|
*/
|
|
216
|
-
class SiteLocationService {
|
|
209
|
+
export class SiteLocationService {
|
|
217
210
|
deps;
|
|
218
211
|
static inFlight = null;
|
|
219
212
|
constructor(deps) {
|
|
@@ -230,7 +223,7 @@ class SiteLocationService {
|
|
|
230
223
|
try {
|
|
231
224
|
return asRecord(await this.deps.store.get({
|
|
232
225
|
collection: SYSTEM_SETTINGS_COLLECTION,
|
|
233
|
-
key:
|
|
226
|
+
key: SITE_LOCATION_KEY,
|
|
234
227
|
}));
|
|
235
228
|
}
|
|
236
229
|
catch (err) {
|
|
@@ -238,14 +231,14 @@ class SiteLocationService {
|
|
|
238
231
|
// the derivation off to overwrite a value that exists. Rethrow-as-null is
|
|
239
232
|
// exactly the D49 shape this repo has been bitten by, so the caller is
|
|
240
233
|
// told instead.
|
|
241
|
-
this.deps.logger?.warn('site location read failed', { meta: { error:
|
|
234
|
+
this.deps.logger?.warn('site location read failed', { meta: { error: errMsg(err) } });
|
|
242
235
|
throw err;
|
|
243
236
|
}
|
|
244
237
|
}
|
|
245
238
|
async write(record) {
|
|
246
239
|
await this.deps.store.set({
|
|
247
240
|
collection: SYSTEM_SETTINGS_COLLECTION,
|
|
248
|
-
key:
|
|
241
|
+
key: SITE_LOCATION_KEY,
|
|
249
242
|
value: record,
|
|
250
243
|
});
|
|
251
244
|
}
|
|
@@ -275,7 +268,7 @@ class SiteLocationService {
|
|
|
275
268
|
}
|
|
276
269
|
catch (err) {
|
|
277
270
|
this.deps.logger?.debug('legacy site coordinates read failed', {
|
|
278
|
-
meta: { error:
|
|
271
|
+
meta: { error: errMsg(err) },
|
|
279
272
|
});
|
|
280
273
|
return null;
|
|
281
274
|
}
|
|
@@ -340,7 +333,7 @@ class SiteLocationService {
|
|
|
340
333
|
// The marker is what makes it one-shot. Losing it costs a retry on the
|
|
341
334
|
// next read, never a wrong value.
|
|
342
335
|
this.deps.logger?.warn('site location failure marker could not be persisted', {
|
|
343
|
-
meta: { error:
|
|
336
|
+
meta: { error: errMsg(err) },
|
|
344
337
|
});
|
|
345
338
|
}
|
|
346
339
|
return toStatus(failure);
|
|
@@ -386,4 +379,3 @@ class SiteLocationService {
|
|
|
386
379
|
return this.derive();
|
|
387
380
|
}
|
|
388
381
|
}
|
|
389
|
-
exports.SiteLocationService = SiteLocationService;
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createStreamProbeRouter = createStreamProbeRouter;
|
|
4
1
|
/**
|
|
5
2
|
* Stream probe router — fixed core API (not a capability).
|
|
6
3
|
*
|
|
@@ -11,32 +8,32 @@ exports.createStreamProbeRouter = createStreamProbeRouter;
|
|
|
11
8
|
* Previously these endpoints lived under `streaming.probeStream` — moved
|
|
12
9
|
* here as part of eliminating the `streaming` aggregator capability.
|
|
13
10
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const ProbedStreamSchema =
|
|
18
|
-
width:
|
|
19
|
-
height:
|
|
20
|
-
codec:
|
|
21
|
-
fps:
|
|
22
|
-
bitrateKbps:
|
|
23
|
-
quality:
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import { classifyStream } from '@camstack/types';
|
|
13
|
+
import { trpcRouter, adminProcedure } from '../trpc/trpc.middleware.js';
|
|
14
|
+
const ProbedStreamSchema = z.object({
|
|
15
|
+
width: z.number().optional(),
|
|
16
|
+
height: z.number().optional(),
|
|
17
|
+
codec: z.string().optional(),
|
|
18
|
+
fps: z.number().optional(),
|
|
19
|
+
bitrateKbps: z.number().optional(),
|
|
20
|
+
quality: z.enum(['high', 'mid', 'low']),
|
|
24
21
|
});
|
|
25
|
-
const FieldProbeResultSchema =
|
|
26
|
-
status:
|
|
27
|
-
labels:
|
|
28
|
-
error:
|
|
22
|
+
const FieldProbeResultSchema = z.object({
|
|
23
|
+
status: z.enum(['ok', 'error']),
|
|
24
|
+
labels: z.array(z.string()).optional(),
|
|
25
|
+
error: z.string().optional(),
|
|
29
26
|
});
|
|
30
|
-
function createStreamProbeRouter(sp) {
|
|
31
|
-
return
|
|
32
|
-
probe:
|
|
33
|
-
.input(
|
|
27
|
+
export function createStreamProbeRouter(sp) {
|
|
28
|
+
return trpcRouter({
|
|
29
|
+
probe: adminProcedure
|
|
30
|
+
.input(z.object({ url: z.string(), force: z.boolean().optional() }))
|
|
34
31
|
.output(ProbedStreamSchema)
|
|
35
32
|
.mutation(async ({ input }) => {
|
|
36
33
|
if (!sp)
|
|
37
34
|
throw new Error('StreamProbeService not available');
|
|
38
35
|
const metadata = await sp.probe(input.url, { force: input.force });
|
|
39
|
-
return { ...metadata, quality:
|
|
36
|
+
return { ...metadata, quality: classifyStream(metadata) };
|
|
40
37
|
}),
|
|
41
38
|
/**
|
|
42
39
|
* Generic field probe — decides stream vs HTTP reachability based on
|
|
@@ -46,8 +43,8 @@ function createStreamProbeRouter(sp) {
|
|
|
46
43
|
* that need the kernel's probe without re-implementing ffprobe /
|
|
47
44
|
* HTTP fetch in every addon.
|
|
48
45
|
*/
|
|
49
|
-
probeField:
|
|
50
|
-
.input(
|
|
46
|
+
probeField: adminProcedure
|
|
47
|
+
.input(z.object({ key: z.string(), value: z.unknown() }))
|
|
51
48
|
.output(FieldProbeResultSchema)
|
|
52
49
|
.mutation(async ({ input }) => {
|
|
53
50
|
if (!sp)
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createSystemEventsRouter = createSystemEventsRouter;
|
|
4
1
|
/**
|
|
5
2
|
* System events router — fixed core API (not a capability).
|
|
6
3
|
*
|
|
@@ -9,10 +6,10 @@ exports.createSystemEventsRouter = createSystemEventsRouter;
|
|
|
9
6
|
* All filters are applied server-side by the SystemEventBus — the client
|
|
10
7
|
* only receives matching events.
|
|
11
8
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { trpcRouter, protectedProcedure, iterableSubscription } from '../trpc/trpc.middleware.js';
|
|
11
|
+
import { scopedEventVisibility } from '../trpc/principal-visibility.js';
|
|
12
|
+
import { isPublicEvent, publicRecent } from './public-event-filter.js';
|
|
16
13
|
function serialize(e) {
|
|
17
14
|
return {
|
|
18
15
|
id: e.id,
|
|
@@ -33,20 +30,20 @@ function serialize(e) {
|
|
|
33
30
|
* Category handling is split (asymmetric): see the per-method schemas
|
|
34
31
|
* below.
|
|
35
32
|
*/
|
|
36
|
-
const ScopeFieldsSchema =
|
|
33
|
+
const ScopeFieldsSchema = z.object({
|
|
37
34
|
/** Legacy source filter (exact type+id match). */
|
|
38
|
-
source:
|
|
35
|
+
source: z
|
|
39
36
|
.object({
|
|
40
|
-
type:
|
|
41
|
-
id:
|
|
37
|
+
type: z.string(),
|
|
38
|
+
id: z.union([z.string(), z.number()]),
|
|
42
39
|
})
|
|
43
40
|
.optional(),
|
|
44
41
|
/** Agent/node filter (prefix match: 'hub' matches 'hub/pipeline'). */
|
|
45
|
-
agentId:
|
|
42
|
+
agentId: z.string().optional(),
|
|
46
43
|
/** Addon filter. Matches source.addonId or source.id when type='addon'. */
|
|
47
|
-
addonId:
|
|
44
|
+
addonId: z.string().optional(),
|
|
48
45
|
/** Device filter. Matches source.deviceId or source.id when type='device'. */
|
|
49
|
-
deviceId:
|
|
46
|
+
deviceId: z.number().optional(),
|
|
50
47
|
});
|
|
51
48
|
/**
|
|
52
49
|
* `getRecent` accepts a category array so the UI can drive a
|
|
@@ -61,8 +58,8 @@ const ScopeFieldsSchema = zod_1.z.object({
|
|
|
61
58
|
* `category` field as `string | string[]`.
|
|
62
59
|
*/
|
|
63
60
|
const GetRecentInputSchema = ScopeFieldsSchema.extend({
|
|
64
|
-
category:
|
|
65
|
-
limit:
|
|
61
|
+
category: z.union([z.string(), z.array(z.string())]).optional(),
|
|
62
|
+
limit: z.number().int().min(1).max(500).optional(),
|
|
66
63
|
});
|
|
67
64
|
/**
|
|
68
65
|
* `subscribe` keeps category as a single string — a WIRE choice now, not a bus
|
|
@@ -78,17 +75,17 @@ const GetRecentInputSchema = ScopeFieldsSchema.extend({
|
|
|
78
75
|
* wildcard and narrow client-side.
|
|
79
76
|
*/
|
|
80
77
|
const SubscribeInputSchema = ScopeFieldsSchema.extend({
|
|
81
|
-
category:
|
|
78
|
+
category: z.string().optional(),
|
|
82
79
|
});
|
|
83
|
-
function createSystemEventsRouter(eb) {
|
|
84
|
-
return
|
|
85
|
-
getRecent:
|
|
80
|
+
export function createSystemEventsRouter(eb) {
|
|
81
|
+
return trpcRouter({
|
|
82
|
+
getRecent: protectedProcedure.input(GetRecentInputSchema).query(({ input, ctx }) => {
|
|
86
83
|
// SECURITY (F0.5): filter the history to what a device-restricted scoped
|
|
87
84
|
// token may view. null ⇒ admin / broad category-view operator ⇒ unfiltered.
|
|
88
85
|
// An owner-only category (D224) is not history anyone may read. Both
|
|
89
86
|
// projections run BEFORE the limit, so N asked is N public rows answered.
|
|
90
|
-
const keep =
|
|
91
|
-
return
|
|
87
|
+
const keep = scopedEventVisibility(ctx.user, ctx.deviceScopeLookup);
|
|
88
|
+
return publicRecent(eb, {
|
|
92
89
|
...(input.source ? { source: input.source } : {}),
|
|
93
90
|
...(input.agentId ? { agentId: input.agentId } : {}),
|
|
94
91
|
...(input.addonId ? { addonId: input.addonId } : {}),
|
|
@@ -96,11 +93,11 @@ function createSystemEventsRouter(eb) {
|
|
|
96
93
|
...(input.category ? { category: input.category } : {}),
|
|
97
94
|
}, input.limit, keep).map(serialize);
|
|
98
95
|
}),
|
|
99
|
-
subscribe:
|
|
96
|
+
subscribe: protectedProcedure.input(SubscribeInputSchema).subscription(({ input, ctx }) => {
|
|
100
97
|
// SECURITY (F0.5): a device-restricted scoped token must not receive every
|
|
101
98
|
// camera's live events. Same projection as `getRecent`.
|
|
102
|
-
const keep =
|
|
103
|
-
return
|
|
99
|
+
const keep = scopedEventVisibility(ctx.user, ctx.deviceScopeLookup);
|
|
100
|
+
return iterableSubscription((push) => {
|
|
104
101
|
return eb.subscribe({
|
|
105
102
|
...(input.source ? { source: input.source } : {}),
|
|
106
103
|
...(input.agentId ? { agentId: input.agentId } : {}),
|
|
@@ -108,7 +105,7 @@ function createSystemEventsRouter(eb) {
|
|
|
108
105
|
...(input.deviceId !== undefined ? { deviceId: input.deviceId } : {}),
|
|
109
106
|
...(input.category ? { category: input.category } : {}),
|
|
110
107
|
}, (event) => {
|
|
111
|
-
if (!
|
|
108
|
+
if (!isPublicEvent(event))
|
|
112
109
|
return;
|
|
113
110
|
if (!keep || keep(event))
|
|
114
111
|
push(serialize(event));
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* cors-policy — which browser origins may READ this hub's responses (D707).
|
|
4
3
|
*
|
|
@@ -32,23 +31,15 @@
|
|
|
32
31
|
*
|
|
33
32
|
* Pure: no Fastify, no I/O — `register-cors-policy.ts` is the glue.
|
|
34
33
|
*/
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.CORS_PREFLIGHT_MAX_AGE_SEC = exports.CORS_EXPOSED_HEADERS = exports.CORS_ALLOWED_HEADERS = exports.CORS_ALLOWED_METHODS = exports.OPAQUE_ORIGIN = exports.VIEWER_PWA_ORIGIN = void 0;
|
|
37
|
-
exports.normalizeOrigin = normalizeOrigin;
|
|
38
|
-
exports.parseConfiguredOrigins = parseConfiguredOrigins;
|
|
39
|
-
exports.builtInOrigins = builtInOrigins;
|
|
40
|
-
exports.judgeCorsOrigin = judgeCorsOrigin;
|
|
41
|
-
exports.corsResponseHeaders = corsResponseHeaders;
|
|
42
|
-
exports.appendVary = appendVary;
|
|
43
34
|
/** The hosted viewer PWA (operator decision 2026-10-06). */
|
|
44
|
-
|
|
35
|
+
export const VIEWER_PWA_ORIGIN = 'https://camstack.zentik.app';
|
|
45
36
|
/**
|
|
46
37
|
* The serialisation of an opaque origin. The native app's OTA embed
|
|
47
38
|
* (`assets/embed/camstack-embed.html`, loaded from `file://`) sends it.
|
|
48
39
|
*/
|
|
49
|
-
|
|
40
|
+
export const OPAQUE_ORIGIN = 'null';
|
|
50
41
|
/** Every method a hub route answers. */
|
|
51
|
-
|
|
42
|
+
export const CORS_ALLOWED_METHODS = 'GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS';
|
|
52
43
|
/**
|
|
53
44
|
* Request headers a cross-origin caller may send. `authorization` is the
|
|
54
45
|
* whole point; `range` is recorded-media seeking; the `trpc-*` / `x-trpc-*`
|
|
@@ -56,25 +47,25 @@ exports.CORS_ALLOWED_METHODS = 'GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS';
|
|
|
56
47
|
* the cookie→bearer exchange is a same-origin mechanism and a cross-origin
|
|
57
48
|
* page has no business asking for it.
|
|
58
49
|
*/
|
|
59
|
-
|
|
50
|
+
export const CORS_ALLOWED_HEADERS = 'authorization, content-type, range, if-range, if-none-match, cache-control, trpc-accept, x-trpc-source, x-trpc-batch-mode';
|
|
60
51
|
/**
|
|
61
52
|
* Response headers a cross-origin script may read. The byte-range trio is
|
|
62
53
|
* recorded playback (a seek reads `content-range`); `content-disposition` is
|
|
63
54
|
* export downloads; `retry-after` is the rate limiter; the `x-camstack-*`
|
|
64
55
|
* trio is the clip-still contract (a 204 that says WHY there is no image).
|
|
65
56
|
*/
|
|
66
|
-
|
|
57
|
+
export const CORS_EXPOSED_HEADERS = 'content-range, accept-ranges, content-length, content-disposition, etag, retry-after, x-camstack-reason, x-camstack-reason-code, x-camstack-disposition';
|
|
67
58
|
/** Preflight cache. Chromium caps at 7200 s; Firefox honours up to 86400. */
|
|
68
|
-
|
|
59
|
+
export const CORS_PREFLIGHT_MAX_AGE_SEC = 7200;
|
|
69
60
|
/**
|
|
70
61
|
* The canonical serialisation of an origin (`scheme://host[:port]`, lower
|
|
71
62
|
* case, default port dropped), or `null` when `raw` is not an http(s) origin.
|
|
72
63
|
* `null` (the opaque origin) is accepted verbatim.
|
|
73
64
|
*/
|
|
74
|
-
function normalizeOrigin(raw) {
|
|
65
|
+
export function normalizeOrigin(raw) {
|
|
75
66
|
const trimmed = raw.trim();
|
|
76
|
-
if (trimmed ===
|
|
77
|
-
return
|
|
67
|
+
if (trimmed === OPAQUE_ORIGIN)
|
|
68
|
+
return OPAQUE_ORIGIN;
|
|
78
69
|
let url;
|
|
79
70
|
try {
|
|
80
71
|
url = new URL(trimmed);
|
|
@@ -94,7 +85,7 @@ function normalizeOrigin(raw) {
|
|
|
94
85
|
return url.origin;
|
|
95
86
|
}
|
|
96
87
|
/** Read the configured value (array from YAML, already split from env). */
|
|
97
|
-
function parseConfiguredOrigins(value) {
|
|
88
|
+
export function parseConfiguredOrigins(value) {
|
|
98
89
|
if (!Array.isArray(value))
|
|
99
90
|
return { origins: [], rejected: [] };
|
|
100
91
|
const origins = [];
|
|
@@ -113,8 +104,8 @@ function parseConfiguredOrigins(value) {
|
|
|
113
104
|
return { origins, rejected };
|
|
114
105
|
}
|
|
115
106
|
/** The built-in entries. */
|
|
116
|
-
function builtInOrigins() {
|
|
117
|
-
return [
|
|
107
|
+
export function builtInOrigins() {
|
|
108
|
+
return [VIEWER_PWA_ORIGIN, OPAQUE_ORIGIN];
|
|
118
109
|
}
|
|
119
110
|
/** The first value of a possibly comma-joined forwarding header, lower case. */
|
|
120
111
|
function firstHost(value) {
|
|
@@ -125,7 +116,7 @@ function firstHost(value) {
|
|
|
125
116
|
}
|
|
126
117
|
/** `host[:port]` of an http(s) origin, or null. */
|
|
127
118
|
function hostOfOrigin(origin) {
|
|
128
|
-
if (origin ===
|
|
119
|
+
if (origin === OPAQUE_ORIGIN)
|
|
129
120
|
return null;
|
|
130
121
|
try {
|
|
131
122
|
return new URL(origin).host.toLowerCase();
|
|
@@ -138,7 +129,7 @@ function hostOfOrigin(origin) {
|
|
|
138
129
|
* Judge one request's `Origin`. `listed` is the full allowlist (built-ins and
|
|
139
130
|
* the operator's extras), already normalised.
|
|
140
131
|
*/
|
|
141
|
-
function judgeCorsOrigin(facts, listed) {
|
|
132
|
+
export function judgeCorsOrigin(facts, listed) {
|
|
142
133
|
const raw = facts.origin;
|
|
143
134
|
if (raw === undefined || raw.length === 0)
|
|
144
135
|
return { kind: 'no-origin' };
|
|
@@ -162,25 +153,25 @@ function judgeCorsOrigin(facts, listed) {
|
|
|
162
153
|
* page, and `Vary: Origin` (set by the glue on every response) keeps a cache
|
|
163
154
|
* from serving one origin's answer to another.
|
|
164
155
|
*/
|
|
165
|
-
function corsResponseHeaders(origin, preflight) {
|
|
156
|
+
export function corsResponseHeaders(origin, preflight) {
|
|
166
157
|
const base = {
|
|
167
158
|
'access-control-allow-origin': origin,
|
|
168
|
-
'access-control-expose-headers':
|
|
159
|
+
'access-control-expose-headers': CORS_EXPOSED_HEADERS,
|
|
169
160
|
};
|
|
170
161
|
if (preflight === null)
|
|
171
162
|
return base;
|
|
172
163
|
return {
|
|
173
164
|
...base,
|
|
174
|
-
'access-control-allow-methods':
|
|
175
|
-
'access-control-allow-headers':
|
|
176
|
-
'access-control-max-age': String(
|
|
165
|
+
'access-control-allow-methods': CORS_ALLOWED_METHODS,
|
|
166
|
+
'access-control-allow-headers': CORS_ALLOWED_HEADERS,
|
|
167
|
+
'access-control-max-age': String(CORS_PREFLIGHT_MAX_AGE_SEC),
|
|
177
168
|
// A public origin (the PWA) calling a LAN hub is a "private network"
|
|
178
169
|
// request to Chromium; its preflight must be answered explicitly.
|
|
179
170
|
...(preflight.requestPrivateNetwork ? { 'access-control-allow-private-network': 'true' } : {}),
|
|
180
171
|
};
|
|
181
172
|
}
|
|
182
173
|
/** Append `token` to a `Vary` value unless it is already there. */
|
|
183
|
-
function appendVary(existing, token) {
|
|
174
|
+
export function appendVary(existing, token) {
|
|
184
175
|
if (existing === undefined || existing.trim().length === 0)
|
|
185
176
|
return token;
|
|
186
177
|
const parts = existing.split(',').map((p) => p.trim().toLowerCase());
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createCorsPolicy = createCorsPolicy;
|
|
4
|
-
exports.registerCorsPolicy = registerCorsPolicy;
|
|
5
|
-
exports.refuseForeignWsUpgrade = refuseForeignWsUpgrade;
|
|
6
|
-
const cors_policy_js_1 = require("./cors-policy.js");
|
|
1
|
+
import { appendVary, builtInOrigins, corsResponseHeaders, judgeCorsOrigin, parseConfiguredOrigins, } from './cors-policy.js';
|
|
7
2
|
/** Distinct refused origins remembered for log de-duplication. */
|
|
8
3
|
const MAX_LOGGED_DENIALS = 256;
|
|
9
4
|
function headerValue(value) {
|
|
@@ -18,8 +13,8 @@ function varyText(value) {
|
|
|
18
13
|
return value.filter((v) => typeof v === 'string').join(', ');
|
|
19
14
|
return undefined;
|
|
20
15
|
}
|
|
21
|
-
function createCorsPolicy(deps) {
|
|
22
|
-
const builtIns =
|
|
16
|
+
export function createCorsPolicy(deps) {
|
|
17
|
+
const builtIns = builtInOrigins();
|
|
23
18
|
let lastConfigured = Symbol('unset');
|
|
24
19
|
let listed = new Set(builtIns);
|
|
25
20
|
const loggedDenials = new Set();
|
|
@@ -28,7 +23,7 @@ function createCorsPolicy(deps) {
|
|
|
28
23
|
if (configured === lastConfigured)
|
|
29
24
|
return listed;
|
|
30
25
|
lastConfigured = configured;
|
|
31
|
-
const parsed =
|
|
26
|
+
const parsed = parseConfiguredOrigins(configured);
|
|
32
27
|
if (parsed.rejected.length > 0) {
|
|
33
28
|
deps.logger.warn('cors: ignored configured origins that are not http(s) origins', {
|
|
34
29
|
meta: { rejected: parsed.rejected, setting: 'server.corsOrigins' },
|
|
@@ -39,7 +34,7 @@ function createCorsPolicy(deps) {
|
|
|
39
34
|
};
|
|
40
35
|
return {
|
|
41
36
|
judge(headers) {
|
|
42
|
-
const verdict =
|
|
37
|
+
const verdict = judgeCorsOrigin({
|
|
43
38
|
origin: headerValue(headers.origin),
|
|
44
39
|
host: headerValue(headers.host),
|
|
45
40
|
forwardedHost: headerValue(headers['x-forwarded-host']),
|
|
@@ -64,17 +59,17 @@ function createCorsPolicy(deps) {
|
|
|
64
59
|
* answered here with 204 (as before D707) — with grants only for an allowed
|
|
65
60
|
* origin, so a refused preflight fails in the browser.
|
|
66
61
|
*/
|
|
67
|
-
function registerCorsPolicy(fastify, policy) {
|
|
62
|
+
export function registerCorsPolicy(fastify, policy) {
|
|
68
63
|
fastify.addHook('onRequest', async (request, reply) => {
|
|
69
64
|
// The answer depends on Origin for every route, so every response says so
|
|
70
65
|
// — a cached same-origin answer must never be replayed to another origin.
|
|
71
|
-
const vary =
|
|
66
|
+
const vary = appendVary(varyText(reply.getHeader('vary')), 'Origin');
|
|
72
67
|
reply.header('vary', vary);
|
|
73
68
|
reply.raw.setHeader('vary', vary);
|
|
74
69
|
const verdict = policy.judge(request.headers);
|
|
75
70
|
const isPreflight = request.method === 'OPTIONS' && request.headers['access-control-request-method'] !== undefined;
|
|
76
71
|
if (verdict.kind === 'allowed') {
|
|
77
|
-
const headers =
|
|
72
|
+
const headers = corsResponseHeaders(verdict.origin, isPreflight
|
|
78
73
|
? {
|
|
79
74
|
requestPrivateNetwork: headerValue(request.headers['access-control-request-private-network']) === 'true',
|
|
80
75
|
}
|
|
@@ -99,7 +94,7 @@ function registerCorsPolicy(fastify, policy) {
|
|
|
99
94
|
* Node client, an agent) passes — Origin is the browser's signal, and auth is
|
|
100
95
|
* the bearer in `connectionParams` either way.
|
|
101
96
|
*/
|
|
102
|
-
function refuseForeignWsUpgrade(policy, headers, socket) {
|
|
97
|
+
export function refuseForeignWsUpgrade(policy, headers, socket) {
|
|
103
98
|
const verdict = policy.judge(headers);
|
|
104
99
|
if (verdict.kind !== 'denied')
|
|
105
100
|
return false;
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DeployStageRegistry = void 0;
|
|
4
|
-
const node_crypto_1 = require("node:crypto");
|
|
1
|
+
import { createHash, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
5
2
|
/**
|
|
6
3
|
* In-memory, single-use, TTL-bounded store of staged addon tarballs the hub
|
|
7
4
|
* serves to agents over HTTP. One entry per deploy target; the token is
|
|
8
5
|
* consumed on the first successful read so a bundle URL is never reusable.
|
|
9
6
|
*/
|
|
10
|
-
class DeployStageRegistry {
|
|
7
|
+
export class DeployStageRegistry {
|
|
11
8
|
entries = new Map();
|
|
12
9
|
ttlMs;
|
|
13
10
|
now;
|
|
@@ -18,9 +15,9 @@ class DeployStageRegistry {
|
|
|
18
15
|
stage(buffer) {
|
|
19
16
|
// Reclaim abandoned (never-consumed) entries before inserting a new one.
|
|
20
17
|
this.sweepExpired();
|
|
21
|
-
const id =
|
|
22
|
-
const token =
|
|
23
|
-
const sha256 =
|
|
18
|
+
const id = randomUUID();
|
|
19
|
+
const token = randomUUID();
|
|
20
|
+
const sha256 = createHash('sha256').update(buffer).digest('hex');
|
|
24
21
|
this.entries.set(id, { token, buffer, sha256, expiresAt: this.now() + this.ttlMs });
|
|
25
22
|
return { id, token, sha256, bytes: buffer.length };
|
|
26
23
|
}
|
|
@@ -35,7 +32,7 @@ class DeployStageRegistry {
|
|
|
35
32
|
// Constant-time comparison: different byte lengths → not equal (no timing oracle).
|
|
36
33
|
const entryBuf = Buffer.from(entry.token);
|
|
37
34
|
const tokenBuf = Buffer.from(token);
|
|
38
|
-
if (entryBuf.length !== tokenBuf.length || !
|
|
35
|
+
if (entryBuf.length !== tokenBuf.length || !timingSafeEqual(entryBuf, tokenBuf))
|
|
39
36
|
return null;
|
|
40
37
|
this.entries.delete(id); // single-use
|
|
41
38
|
return entry.buffer;
|
|
@@ -57,4 +54,3 @@ class DeployStageRegistry {
|
|
|
57
54
|
this.entries.delete(id);
|
|
58
55
|
}
|
|
59
56
|
}
|
|
60
|
-
exports.DeployStageRegistry = DeployStageRegistry;
|