@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
|
* The WebSocket plane of the transport census — hooked PER OPERATION.
|
|
4
3
|
*
|
|
@@ -51,13 +50,8 @@
|
|
|
51
50
|
* {@link UNKNOWN} instead: a call this instrument saw is never dropped for
|
|
52
51
|
* want of a name.
|
|
53
52
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
exports.parseTrpcWsFrame = parseTrpcWsFrame;
|
|
57
|
-
exports.attachWsCensusSession = attachWsCensusSession;
|
|
58
|
-
exports.identifyWsCensusSession = identifyWsCensusSession;
|
|
59
|
-
const client_ip_js_1 = require("./client-ip.js");
|
|
60
|
-
const http_request_census_js_1 = require("./http-request-census.js");
|
|
53
|
+
import { extractClientIp, extractUserAgent } from './client-ip.js';
|
|
54
|
+
import { BATCH_OVERFLOW_PROCEDURE, MAX_BATCH_PROCEDURES, UNKNOWN, } from './http-request-census.js';
|
|
61
55
|
/** Keepalive frames the tRPC adapter answers before parsing. Not operations. */
|
|
62
56
|
const KEEPALIVE_FRAMES = new Set(['PING', 'PONG']);
|
|
63
57
|
/**
|
|
@@ -66,7 +60,7 @@ const KEEPALIVE_FRAMES = new Set(['PING', 'PONG']);
|
|
|
66
60
|
* tRPC rejects such a frame, but it still ARRIVED, and a census that drops it
|
|
67
61
|
* has a hole in the total the plane comparison rests on.
|
|
68
62
|
*/
|
|
69
|
-
|
|
63
|
+
export const WS_PATHLESS_PROCEDURE = '(ws-no-path)';
|
|
70
64
|
/**
|
|
71
65
|
* Operations held for a connection whose principal has not resolved yet.
|
|
72
66
|
*
|
|
@@ -75,7 +69,7 @@ exports.WS_PATHLESS_PROCEDURE = '(ws-no-path)';
|
|
|
75
69
|
* without limit. Past it, operations are counted immediately under
|
|
76
70
|
* {@link UNKNOWN} rather than dropped.
|
|
77
71
|
*/
|
|
78
|
-
|
|
72
|
+
export const MAX_PENDING_WS_OPERATIONS = 128;
|
|
79
73
|
const NO_OPERATIONS = { calls: [], subscriptions: [], stops: 0 };
|
|
80
74
|
/**
|
|
81
75
|
* Read the operations out of one raw client frame.
|
|
@@ -84,7 +78,7 @@ const NO_OPERATIONS = { calls: [], subscriptions: [], stops: 0 };
|
|
|
84
78
|
* every one of them is a place where the count could be wrong in the direction
|
|
85
79
|
* that exonerates this plane.
|
|
86
80
|
*/
|
|
87
|
-
function parseTrpcWsFrame(raw) {
|
|
81
|
+
export function parseTrpcWsFrame(raw) {
|
|
88
82
|
const text = raw.trim();
|
|
89
83
|
if (text.length === 0)
|
|
90
84
|
return NO_OPERATIONS;
|
|
@@ -108,7 +102,7 @@ function parseTrpcWsFrame(raw) {
|
|
|
108
102
|
}
|
|
109
103
|
if (method !== 'query' && method !== 'mutation' && method !== 'subscription')
|
|
110
104
|
continue;
|
|
111
|
-
if (calls.length + subscriptions.length >=
|
|
105
|
+
if (calls.length + subscriptions.length >= MAX_BATCH_PROCEDURES) {
|
|
112
106
|
overflowed = true;
|
|
113
107
|
continue;
|
|
114
108
|
}
|
|
@@ -119,7 +113,7 @@ function parseTrpcWsFrame(raw) {
|
|
|
119
113
|
calls.push(path);
|
|
120
114
|
}
|
|
121
115
|
if (overflowed)
|
|
122
|
-
calls.push(
|
|
116
|
+
calls.push(BATCH_OVERFLOW_PROCEDURE);
|
|
123
117
|
return { calls, subscriptions, stops };
|
|
124
118
|
}
|
|
125
119
|
function parseJsonSafely(text) {
|
|
@@ -134,12 +128,12 @@ function parseJsonSafely(text) {
|
|
|
134
128
|
}
|
|
135
129
|
function readPath(params) {
|
|
136
130
|
if (typeof params !== 'object' || params === null)
|
|
137
|
-
return
|
|
131
|
+
return WS_PATHLESS_PROCEDURE;
|
|
138
132
|
const path = Reflect.get(params, 'path');
|
|
139
|
-
return typeof path === 'string' && path.length > 0 ? path :
|
|
133
|
+
return typeof path === 'string' && path.length > 0 ? path : WS_PATHLESS_PROCEDURE;
|
|
140
134
|
}
|
|
141
135
|
/** One connection's census state: its identity, and anything held for it. */
|
|
142
|
-
class WsCensusSession {
|
|
136
|
+
export class WsCensusSession {
|
|
143
137
|
census;
|
|
144
138
|
userAgent;
|
|
145
139
|
ip;
|
|
@@ -149,8 +143,8 @@ class WsCensusSession {
|
|
|
149
143
|
closed = false;
|
|
150
144
|
constructor(census, req) {
|
|
151
145
|
this.census = census;
|
|
152
|
-
this.userAgent =
|
|
153
|
-
this.ip =
|
|
146
|
+
this.userAgent = extractUserAgent(req);
|
|
147
|
+
this.ip = extractClientIp(req);
|
|
154
148
|
}
|
|
155
149
|
/**
|
|
156
150
|
* Count one raw client frame.
|
|
@@ -175,9 +169,9 @@ class WsCensusSession {
|
|
|
175
169
|
this.flushFrame(operations, this.principal);
|
|
176
170
|
return;
|
|
177
171
|
}
|
|
178
|
-
if (this.pending.length >=
|
|
172
|
+
if (this.pending.length >= MAX_PENDING_WS_OPERATIONS) {
|
|
179
173
|
// Never dropped — attributed to the named bucket instead.
|
|
180
|
-
this.flushFrame(operations,
|
|
174
|
+
this.flushFrame(operations, UNKNOWN);
|
|
181
175
|
return;
|
|
182
176
|
}
|
|
183
177
|
this.pending.push(operations);
|
|
@@ -194,7 +188,7 @@ class WsCensusSession {
|
|
|
194
188
|
if (this.closed)
|
|
195
189
|
return;
|
|
196
190
|
this.closed = true;
|
|
197
|
-
this.drain(this.principal ??
|
|
191
|
+
this.drain(this.principal ?? UNKNOWN);
|
|
198
192
|
}
|
|
199
193
|
drain(principal) {
|
|
200
194
|
if (this.pending.length === 0)
|
|
@@ -227,7 +221,6 @@ class WsCensusSession {
|
|
|
227
221
|
}
|
|
228
222
|
}
|
|
229
223
|
}
|
|
230
|
-
exports.WsCensusSession = WsCensusSession;
|
|
231
224
|
/**
|
|
232
225
|
* Sessions by socket.
|
|
233
226
|
*
|
|
@@ -243,7 +236,7 @@ const sessions = new WeakMap();
|
|
|
243
236
|
* Registered from `main.ts` on the same `wss` `applyWSSHandler` is given. Both
|
|
244
237
|
* listeners see every frame; ours only counts.
|
|
245
238
|
*/
|
|
246
|
-
function attachWsCensusSession(census, client, req, logger) {
|
|
239
|
+
export function attachWsCensusSession(census, client, req, logger) {
|
|
247
240
|
census.observeWsConnection();
|
|
248
241
|
const session = new WsCensusSession(census, req);
|
|
249
242
|
sessions.set(client, session);
|
|
@@ -280,7 +273,7 @@ function attachWsCensusSession(census, client, req, logger) {
|
|
|
280
273
|
* harness or a future adapter that builds a context without a census session
|
|
281
274
|
* does not have to know this exists.
|
|
282
275
|
*/
|
|
283
|
-
function identifyWsCensusSession(client, principal) {
|
|
276
|
+
export function identifyWsCensusSession(client, principal) {
|
|
284
277
|
sessions.get(client)?.identify(principal);
|
|
285
278
|
}
|
|
286
279
|
/** Decode a `ws` frame payload to text. Returns `null` for a shape that cannot
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WsSlowConsumerGuard = exports.WS_SLOW_CONSUMER_POLL_MS = exports.WS_SLOW_CONSUMER_GRACE_MS = exports.WS_SLOW_CONSUMER_MAX_BUFFERED_BYTES = void 0;
|
|
4
|
-
exports.wsConnectionFacts = wsConnectionFacts;
|
|
5
|
-
const client_ip_js_1 = require("./client-ip.js");
|
|
1
|
+
import { extractClientIp, extractUserAgent } from './client-ip.js';
|
|
6
2
|
/** Queued bytes above which a client is a slow consumer. */
|
|
7
|
-
|
|
3
|
+
export const WS_SLOW_CONSUMER_MAX_BUFFERED_BYTES = 16 * 1024 * 1024;
|
|
8
4
|
/** How long the queue may stay above the bound before the socket is terminated. */
|
|
9
|
-
|
|
5
|
+
export const WS_SLOW_CONSUMER_GRACE_MS = 30_000;
|
|
10
6
|
/** How often every connection's queue is read. A getter per socket; free. */
|
|
11
|
-
|
|
7
|
+
export const WS_SLOW_CONSUMER_POLL_MS = 5_000;
|
|
12
8
|
/** WebSocket close code for a socket that was never closed cleanly. */
|
|
13
9
|
const ABNORMAL_CLOSURE = 1006;
|
|
14
10
|
const UNIDENTIFIED = '(unidentified)';
|
|
15
|
-
class WsSlowConsumerGuard {
|
|
11
|
+
export class WsSlowConsumerGuard {
|
|
16
12
|
connections = new Set();
|
|
17
13
|
bySocket = new WeakMap();
|
|
18
14
|
logger;
|
|
@@ -23,9 +19,9 @@ class WsSlowConsumerGuard {
|
|
|
23
19
|
timer = null;
|
|
24
20
|
constructor(options) {
|
|
25
21
|
this.logger = options.logger;
|
|
26
|
-
this.maxBufferedBytes = options.maxBufferedBytes ??
|
|
27
|
-
this.graceMs = options.graceMs ??
|
|
28
|
-
this.pollMs = options.pollMs ??
|
|
22
|
+
this.maxBufferedBytes = options.maxBufferedBytes ?? WS_SLOW_CONSUMER_MAX_BUFFERED_BYTES;
|
|
23
|
+
this.graceMs = options.graceMs ?? WS_SLOW_CONSUMER_GRACE_MS;
|
|
24
|
+
this.pollMs = options.pollMs ?? WS_SLOW_CONSUMER_POLL_MS;
|
|
29
25
|
this.now = options.now ?? (() => Date.now());
|
|
30
26
|
}
|
|
31
27
|
/** Watch one connection until it closes. */
|
|
@@ -142,7 +138,6 @@ class WsSlowConsumerGuard {
|
|
|
142
138
|
};
|
|
143
139
|
}
|
|
144
140
|
}
|
|
145
|
-
exports.WsSlowConsumerGuard = WsSlowConsumerGuard;
|
|
146
141
|
function safeCount(read) {
|
|
147
142
|
try {
|
|
148
143
|
return read();
|
|
@@ -156,10 +151,10 @@ function errorMessage(err) {
|
|
|
156
151
|
return err instanceof Error ? err.message : String(err);
|
|
157
152
|
}
|
|
158
153
|
/** The facts a real `ws` upgrade request provides. */
|
|
159
|
-
function wsConnectionFacts(req) {
|
|
154
|
+
export function wsConnectionFacts(req) {
|
|
160
155
|
return {
|
|
161
|
-
ip:
|
|
162
|
-
userAgent:
|
|
156
|
+
ip: extractClientIp(req),
|
|
157
|
+
userAgent: extractUserAgent(req),
|
|
163
158
|
bytesRead: () => req.socket.bytesRead,
|
|
164
159
|
bytesWritten: () => req.socket.bytesWritten,
|
|
165
160
|
};
|
package/dist/api/upload-auth.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.UPLOAD_TRPC_PATH = void 0;
|
|
4
|
-
exports.authorizeUploadRequest = authorizeUploadRequest;
|
|
5
|
-
const scope_access_js_1 = require("./trpc/scope-access.js");
|
|
6
|
-
exports.UPLOAD_TRPC_PATH = 'addons.installPackage';
|
|
1
|
+
import { checkScopeAccess } from './trpc/scope-access.js';
|
|
2
|
+
export const UPLOAD_TRPC_PATH = 'addons.installPackage';
|
|
7
3
|
/**
|
|
8
4
|
* Validate a `cst_*` scoped token via the `user-management` cap singleton.
|
|
9
5
|
* Returns null when the singleton isn't mounted yet (boot race) or the
|
|
@@ -20,7 +16,7 @@ async function validateScopedTokenViaCap(addonRegistry, token) {
|
|
|
20
16
|
return userMgmt.validateScopedToken({ token });
|
|
21
17
|
}
|
|
22
18
|
function isUploadAllowed(scoped) {
|
|
23
|
-
return
|
|
19
|
+
return checkScopeAccess(scoped.scopes, UPLOAD_TRPC_PATH).ok;
|
|
24
20
|
}
|
|
25
21
|
/**
|
|
26
22
|
* Run the full upload auth chain. Returns `{ok: true}` on success, or the
|
|
@@ -28,7 +24,7 @@ function isUploadAllowed(scoped) {
|
|
|
28
24
|
* - 401 `Unauthorized` when no Authorization header is present,
|
|
29
25
|
* - 403 `Forbidden: <reason>` for any recognised-but-insufficient token.
|
|
30
26
|
*/
|
|
31
|
-
async function authorizeUploadRequest(args) {
|
|
27
|
+
export async function authorizeUploadRequest(args) {
|
|
32
28
|
const { authHeader, authService, addonRegistry } = args;
|
|
33
29
|
if (!authHeader) {
|
|
34
30
|
return { ok: false, status: 401, error: 'Unauthorized' };
|
|
@@ -55,7 +51,7 @@ async function authorizeUploadRequest(args) {
|
|
|
55
51
|
return { ok: true };
|
|
56
52
|
}
|
|
57
53
|
else {
|
|
58
|
-
authReason = `scoped token lacks create access on '${
|
|
54
|
+
authReason = `scoped token lacks create access on '${UPLOAD_TRPC_PATH}'`;
|
|
59
55
|
}
|
|
60
56
|
}
|
|
61
57
|
catch (err) {
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.gateAddonJwt = gateAddonJwt;
|
|
4
1
|
/**
|
|
5
2
|
* The JWT half of the `/addon/:addonId/*` gate — shared by the HTTP data-plane
|
|
6
3
|
* branch and the addon-route branch so there is exactly ONE answer to "may this
|
|
@@ -14,7 +11,7 @@ exports.gateAddonJwt = gateAddonJwt;
|
|
|
14
11
|
* the per-camera switches and notification snooze. A 60-second authorization
|
|
15
12
|
* CODE and a 30-day refresh token passed the same way. See D103.
|
|
16
13
|
*/
|
|
17
|
-
|
|
14
|
+
import { classifyBearerPrincipal, principalMayReachAddon, } from '@camstack/types';
|
|
18
15
|
function readScopes(raw) {
|
|
19
16
|
return Array.isArray(raw) ? raw : [];
|
|
20
17
|
}
|
|
@@ -27,8 +24,8 @@ function readScopes(raw) {
|
|
|
27
24
|
* an admin-only route keeps the response it already had; nothing that is not a
|
|
28
25
|
* session or an `oauth-access` token gets that far at all.
|
|
29
26
|
*/
|
|
30
|
-
function gateAddonJwt(input, logger) {
|
|
31
|
-
const principal =
|
|
27
|
+
export function gateAddonJwt(input, logger) {
|
|
28
|
+
const principal = classifyBearerPrincipal(input.payload);
|
|
32
29
|
const isAdmin = input.payload.isAdmin === true;
|
|
33
30
|
if (principal.kind === 'not-a-credential') {
|
|
34
31
|
logger.warn('addon route: refused a bearer that is not an API credential', {
|
|
@@ -42,7 +39,7 @@ function gateAddonJwt(input, logger) {
|
|
|
42
39
|
});
|
|
43
40
|
return { ok: false, status: 403, error: 'Admin required' };
|
|
44
41
|
}
|
|
45
|
-
const verdict =
|
|
42
|
+
const verdict = principalMayReachAddon({
|
|
46
43
|
principal,
|
|
47
44
|
scopes: readScopes(input.payload.scopes),
|
|
48
45
|
isAdmin,
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
exports.gateAddonShareToken = gateAddonShareToken;
|
|
5
|
-
exports.stripShareHeaders = stripShareHeaders;
|
|
6
|
-
exports.SHARE_KIND_HEADER = 'x-camstack-share-kind';
|
|
7
|
-
exports.SHARE_DEVICES_HEADER = 'x-camstack-share-devices';
|
|
8
|
-
function gateAddonShareToken(input) {
|
|
1
|
+
export const SHARE_KIND_HEADER = 'x-camstack-share-kind';
|
|
2
|
+
export const SHARE_DEVICES_HEADER = 'x-camstack-share-devices';
|
|
3
|
+
export function gateAddonShareToken(input) {
|
|
9
4
|
if (input.record === null)
|
|
10
5
|
return { ok: false, status: 401, error: 'Invalid token' };
|
|
11
6
|
const kind = input.record.scope.kind;
|
|
@@ -15,17 +10,17 @@ function gateAddonShareToken(input) {
|
|
|
15
10
|
return {
|
|
16
11
|
ok: true,
|
|
17
12
|
headers: {
|
|
18
|
-
[
|
|
19
|
-
[
|
|
13
|
+
[SHARE_KIND_HEADER]: kind,
|
|
14
|
+
[SHARE_DEVICES_HEADER]: input.record.scope.deviceIds.join(','),
|
|
20
15
|
},
|
|
21
16
|
};
|
|
22
17
|
}
|
|
23
18
|
/** The forwarded header set without any share header the client supplied. */
|
|
24
|
-
function stripShareHeaders(headers) {
|
|
19
|
+
export function stripShareHeaders(headers) {
|
|
25
20
|
const out = {};
|
|
26
21
|
for (const [key, value] of Object.entries(headers)) {
|
|
27
22
|
const lower = key.toLowerCase();
|
|
28
|
-
if (lower ===
|
|
23
|
+
if (lower === SHARE_KIND_HEADER || lower === SHARE_DEVICES_HEADER)
|
|
29
24
|
continue;
|
|
30
25
|
out[key] = value;
|
|
31
26
|
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.APP_CONFIRM_REFUSAL_CSP = exports.APP_CONFIRM_CSP = void 0;
|
|
4
|
-
exports.appConfirmPage = appConfirmPage;
|
|
5
|
-
exports.appConfirmRefusalPage = appConfirmRefusalPage;
|
|
6
1
|
/**
|
|
7
2
|
* The hub-rendered confirmation page of a native app sign-in (D858):
|
|
8
3
|
* "Sign in the CamStack app as <username>?".
|
|
@@ -16,8 +11,8 @@ exports.appConfirmRefusalPage = appConfirmRefusalPage;
|
|
|
16
11
|
* `location` the hub answers; Cancel is a plain link to the app with
|
|
17
12
|
* `error=cancelled`.
|
|
18
13
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
import { appButton, appCancelLink, appErrorLine, appHint, appPageCsp, appParagraph, escapeHtml, renderAppPage, } from './app-page-shell.js';
|
|
15
|
+
import { appStrings } from './app-page-strings.js';
|
|
21
16
|
const PAGE_SCRIPT = `(function () {
|
|
22
17
|
var btn = document.getElementById('go');
|
|
23
18
|
var msg = document.getElementById('msg');
|
|
@@ -43,13 +38,13 @@ const PAGE_SCRIPT = `(function () {
|
|
|
43
38
|
});
|
|
44
39
|
})();`;
|
|
45
40
|
/** The CSP: the shell's style and this page's one script by hash, same-origin requests, no framing, no form posts. */
|
|
46
|
-
|
|
41
|
+
export const APP_CONFIRM_CSP = appPageCsp({ script: PAGE_SCRIPT });
|
|
47
42
|
/** CSP of the refusal page: the shell's style only, nothing runs. */
|
|
48
|
-
|
|
43
|
+
export const APP_CONFIRM_REFUSAL_CSP = appPageCsp();
|
|
49
44
|
/** The confirmation page for `username`; `cancelHref` is the app callback carrying `error=cancelled`. */
|
|
50
|
-
function appConfirmPage(input) {
|
|
51
|
-
const t =
|
|
52
|
-
return
|
|
45
|
+
export function appConfirmPage(input) {
|
|
46
|
+
const t = appStrings(input.lang);
|
|
47
|
+
return renderAppPage({
|
|
53
48
|
lang: input.lang,
|
|
54
49
|
title: t.confirm.title,
|
|
55
50
|
heading: t.confirm.heading,
|
|
@@ -57,22 +52,22 @@ function appConfirmPage(input) {
|
|
|
57
52
|
script: PAGE_SCRIPT,
|
|
58
53
|
scriptStrings: { 'confirm-expired': t.script.confirmExpired },
|
|
59
54
|
body: [
|
|
60
|
-
`<p>${
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
`<noscript><p class="error">${
|
|
55
|
+
`<p>${escapeHtml(t.confirm.signedInAsBefore)}<strong>${escapeHtml(input.username)}</strong>${escapeHtml(t.confirm.signedInAsAfter)}</p>`,
|
|
56
|
+
appHint(t.confirm.hint),
|
|
57
|
+
appButton(t.confirm.continueLabel, `id="go" data-confirm="${escapeHtml(input.token)}"`),
|
|
58
|
+
appErrorLine('msg'),
|
|
59
|
+
appCancelLink(input.cancelHref, t.confirm.cancel),
|
|
60
|
+
`<noscript><p class="error">${escapeHtml(t.confirm.noscript)}</p></noscript>`,
|
|
66
61
|
].join(''),
|
|
67
62
|
});
|
|
68
63
|
}
|
|
69
64
|
/** A confirmation that is gone (expired, used, or opened in another browser): a sentence, nothing runs. */
|
|
70
|
-
function appConfirmRefusalPage(lang) {
|
|
71
|
-
const t =
|
|
72
|
-
return
|
|
65
|
+
export function appConfirmRefusalPage(lang) {
|
|
66
|
+
const t = appStrings(lang).confirmExpired;
|
|
67
|
+
return renderAppPage({
|
|
73
68
|
lang,
|
|
74
69
|
title: t.title,
|
|
75
70
|
heading: t.title,
|
|
76
|
-
body:
|
|
71
|
+
body: appParagraph(t.body) + appHint(t.hint),
|
|
77
72
|
});
|
|
78
73
|
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.APP_CONFIRM_PARAM = exports.APP_CONFIRM_PATH = void 0;
|
|
4
|
-
exports.appConfirmCookie = appConfirmCookie;
|
|
5
|
-
exports.registerAppConfirmRoutes = registerAppConfirmRoutes;
|
|
6
1
|
/**
|
|
7
2
|
* `/auth/app/confirm` - the last step of a native app sign-in (D858).
|
|
8
3
|
*
|
|
@@ -13,20 +8,20 @@ exports.registerAppConfirmRoutes = registerAppConfirmRoutes;
|
|
|
13
8
|
* the handoff code and answers `{ location }` at the app's scheme. Nothing is
|
|
14
9
|
* minted without the POST, and the token is useless in any other browser.
|
|
15
10
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
11
|
+
import { appFlowLocation } from '@camstack/types';
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { APP_CONFIRM_COOKIE, } from './app-confirmations.js';
|
|
14
|
+
import { APP_CONFIRM_CSP, APP_CONFIRM_REFUSAL_CSP, appConfirmPage, appConfirmRefusalPage, } from './app-confirm-page.js';
|
|
15
|
+
import { resolveAppPageLang } from './app-page-strings.js';
|
|
16
|
+
import { clientKeyFromRequest } from './auth-rate-limit.js';
|
|
17
|
+
import { readCookie } from './sso-finish-deps.js';
|
|
18
|
+
export const APP_CONFIRM_PATH = '/auth/app/confirm';
|
|
24
19
|
/** The query parameter carrying the token (masked in logged URLs). */
|
|
25
|
-
|
|
20
|
+
export const APP_CONFIRM_PARAM = 'confirm';
|
|
26
21
|
/** `Set-Cookie` for the browser binding: HttpOnly, Lax (the entry may come from a cross-site redirect chain). */
|
|
27
|
-
function appConfirmCookie(value, secure) {
|
|
22
|
+
export function appConfirmCookie(value, secure) {
|
|
28
23
|
return [
|
|
29
|
-
`${
|
|
24
|
+
`${APP_CONFIRM_COOKIE}=${value}`,
|
|
30
25
|
'Path=/',
|
|
31
26
|
'HttpOnly',
|
|
32
27
|
'SameSite=Lax',
|
|
@@ -34,7 +29,7 @@ function appConfirmCookie(value, secure) {
|
|
|
34
29
|
...(secure ? ['Secure'] : []),
|
|
35
30
|
].join('; ');
|
|
36
31
|
}
|
|
37
|
-
const ConfirmBodySchema =
|
|
32
|
+
const ConfirmBodySchema = z.object({ confirm: z.string().min(1).max(256) });
|
|
38
33
|
function noStore(reply) {
|
|
39
34
|
reply.header('Cache-Control', 'no-store');
|
|
40
35
|
reply.header('Referrer-Policy', 'no-referrer');
|
|
@@ -48,11 +43,11 @@ function mint(deps, confirmation) {
|
|
|
48
43
|
state: app.state,
|
|
49
44
|
authMethod: confirmation.authMethod,
|
|
50
45
|
});
|
|
51
|
-
return
|
|
46
|
+
return appFlowLocation(app.redirect, { code: minted.code }, app.state);
|
|
52
47
|
}
|
|
53
|
-
function registerAppConfirmRoutes(fastify, deps) {
|
|
48
|
+
export function registerAppConfirmRoutes(fastify, deps) {
|
|
54
49
|
const limited = (request, reply) => {
|
|
55
|
-
const client =
|
|
50
|
+
const client = clientKeyFromRequest(request, deps.network);
|
|
56
51
|
if (client === null)
|
|
57
52
|
return false;
|
|
58
53
|
const verdict = deps.limiter.check(`app-confirm:${client}`);
|
|
@@ -62,43 +57,43 @@ function registerAppConfirmRoutes(fastify, deps) {
|
|
|
62
57
|
void reply.status(429).send({ error: 'Too many attempts' });
|
|
63
58
|
return true;
|
|
64
59
|
};
|
|
65
|
-
fastify.get(
|
|
60
|
+
fastify.get(APP_CONFIRM_PATH, async (request, reply) => {
|
|
66
61
|
noStore(reply);
|
|
67
62
|
reply.type('text/html; charset=utf-8');
|
|
68
63
|
if (limited(request, reply))
|
|
69
64
|
return reply;
|
|
70
|
-
const token = request.query[
|
|
65
|
+
const token = request.query[APP_CONFIRM_PARAM];
|
|
71
66
|
const confirmation = token === undefined
|
|
72
67
|
? null
|
|
73
|
-
: deps.confirmations.peek(token,
|
|
68
|
+
: deps.confirmations.peek(token, readCookie(request.headers.cookie, APP_CONFIRM_COOKIE));
|
|
74
69
|
if (token === undefined || confirmation === null) {
|
|
75
70
|
deps.logger.warn('App sign-in confirmation page refused', {
|
|
76
71
|
meta: { reason: 'unknown-expired-or-other-browser' },
|
|
77
72
|
});
|
|
78
|
-
reply.header('Content-Security-Policy',
|
|
73
|
+
reply.header('Content-Security-Policy', APP_CONFIRM_REFUSAL_CSP);
|
|
79
74
|
return reply
|
|
80
75
|
.status(400)
|
|
81
|
-
.send(
|
|
76
|
+
.send(appConfirmRefusalPage(resolveAppPageLang({ acceptLanguage: request.headers['accept-language'] })));
|
|
82
77
|
}
|
|
83
|
-
reply.header('Content-Security-Policy',
|
|
84
|
-
return reply.send(
|
|
85
|
-
lang:
|
|
78
|
+
reply.header('Content-Security-Policy', APP_CONFIRM_CSP);
|
|
79
|
+
return reply.send(appConfirmPage({
|
|
80
|
+
lang: resolveAppPageLang({
|
|
86
81
|
lang: confirmation.app.lang,
|
|
87
82
|
acceptLanguage: request.headers['accept-language'],
|
|
88
83
|
}),
|
|
89
84
|
username: confirmation.username,
|
|
90
85
|
token,
|
|
91
|
-
cancelHref:
|
|
86
|
+
cancelHref: appFlowLocation(confirmation.app.redirect, { error: 'cancelled' }, confirmation.app.state),
|
|
92
87
|
}));
|
|
93
88
|
});
|
|
94
|
-
fastify.post(
|
|
89
|
+
fastify.post(APP_CONFIRM_PATH, async (request, reply) => {
|
|
95
90
|
noStore(reply);
|
|
96
91
|
if (limited(request, reply))
|
|
97
92
|
return reply;
|
|
98
93
|
const body = ConfirmBodySchema.safeParse(request.body);
|
|
99
94
|
if (!body.success)
|
|
100
95
|
return reply.status(400).send({ error: 'Malformed confirmation.' });
|
|
101
|
-
const confirmation = deps.confirmations.consume(body.data.confirm,
|
|
96
|
+
const confirmation = deps.confirmations.consume(body.data.confirm, readCookie(request.headers.cookie, APP_CONFIRM_COOKIE));
|
|
102
97
|
if (confirmation === null) {
|
|
103
98
|
deps.logger.warn('App sign-in confirmation refused', {
|
|
104
99
|
meta: { reason: 'unknown-used-expired-or-other-browser' },
|
|
@@ -1,40 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.AppConfirmations = exports.APP_CONFIRM_COOKIE = exports.APP_CONFIRM_TTL_MS = void 0;
|
|
37
|
-
const types_1 = require("@camstack/types");
|
|
1
|
+
import { userError } from '@camstack/types';
|
|
38
2
|
/**
|
|
39
3
|
* Pending "Sign in the CamStack app as <user>?" confirmations (D858).
|
|
40
4
|
*
|
|
@@ -52,18 +16,18 @@ const types_1 = require("@camstack/types");
|
|
|
52
16
|
*
|
|
53
17
|
* In-memory only: a hub restart simply means the person signs in again.
|
|
54
18
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
19
|
+
import * as crypto from 'node:crypto';
|
|
20
|
+
export const APP_CONFIRM_TTL_MS = 120_000;
|
|
57
21
|
const MAX_PENDING = 500;
|
|
58
22
|
/** The cookie that binds a confirmation to the browser that finished the ceremony. */
|
|
59
|
-
|
|
23
|
+
export const APP_CONFIRM_COOKIE = 'camstack_app_confirm';
|
|
60
24
|
const sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex');
|
|
61
25
|
function sameDigest(a, b) {
|
|
62
26
|
const left = Buffer.from(a, 'utf8');
|
|
63
27
|
const right = Buffer.from(b, 'utf8');
|
|
64
28
|
return left.length === right.length && crypto.timingSafeEqual(left, right);
|
|
65
29
|
}
|
|
66
|
-
class AppConfirmations {
|
|
30
|
+
export class AppConfirmations {
|
|
67
31
|
now;
|
|
68
32
|
/** Keyed by SHA-256(token). */
|
|
69
33
|
pending = new Map();
|
|
@@ -74,13 +38,13 @@ class AppConfirmations {
|
|
|
74
38
|
create(confirmation) {
|
|
75
39
|
this.prune();
|
|
76
40
|
if (this.pending.size >= MAX_PENDING)
|
|
77
|
-
throw
|
|
41
|
+
throw userError('server.app-confirmations-busy', undefined, 'Too many pending app confirmations');
|
|
78
42
|
const token = crypto.randomBytes(32).toString('base64url');
|
|
79
43
|
const cookie = crypto.randomBytes(32).toString('base64url');
|
|
80
44
|
this.pending.set(sha256(token), {
|
|
81
45
|
...confirmation,
|
|
82
46
|
cookieHash: sha256(cookie),
|
|
83
|
-
expiresAt: this.now() +
|
|
47
|
+
expiresAt: this.now() + APP_CONFIRM_TTL_MS,
|
|
84
48
|
});
|
|
85
49
|
return { token, cookie };
|
|
86
50
|
}
|
|
@@ -126,4 +90,3 @@ class AppConfirmations {
|
|
|
126
90
|
this.pending.delete(key);
|
|
127
91
|
}
|
|
128
92
|
}
|
|
129
|
-
exports.AppConfirmations = AppConfirmations;
|