@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,6 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.finishSsoApp = finishSsoApp;
|
|
4
1
|
/**
|
|
5
2
|
* `/api/auth/sso/finish-app` - the native-app leg of an OIDC sign-in (D858).
|
|
6
3
|
*
|
|
@@ -30,41 +27,41 @@ exports.finishSsoApp = finishSsoApp;
|
|
|
30
27
|
*
|
|
31
28
|
* Pure: the Fastify route in `sso-routes.ts` wires the deps and maps the outcome.
|
|
32
29
|
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
import { APP_FLOW_DEFAULT_REDIRECT, appFlowLocation, parseAppAuthCallback, } from '@camstack/types';
|
|
31
|
+
import { bridgeProviderAddonId } from './auth-provider-gate.js';
|
|
32
|
+
import { SessionUserGoneError } from './session-mint.js';
|
|
33
|
+
import { bridgeIsFirstUseHere, isLoginBridge, oidcIdentityOf, } from './sso-finish.js';
|
|
37
34
|
function refused(error, deps, redirect, state) {
|
|
38
35
|
deps.logRefusal?.(error);
|
|
39
|
-
return { kind: 'app', location:
|
|
36
|
+
return { kind: 'app', location: appFlowLocation(redirect, { error }, state) };
|
|
40
37
|
}
|
|
41
38
|
/** Exchange a verified app login bridge for a handoff code at the app's callback. */
|
|
42
|
-
async function finishSsoApp(bridge, deps) {
|
|
43
|
-
const fallback =
|
|
39
|
+
export async function finishSsoApp(bridge, deps) {
|
|
40
|
+
const fallback = APP_FLOW_DEFAULT_REDIRECT;
|
|
44
41
|
if (!bridge)
|
|
45
42
|
return refused('invalid-bridge', deps, fallback, undefined);
|
|
46
43
|
const claims = deps.verifyBridge(bridge);
|
|
47
44
|
if (claims === null)
|
|
48
45
|
return refused('invalid-bridge', deps, fallback, undefined);
|
|
49
46
|
const app = claims.app;
|
|
50
|
-
if (app === undefined || !
|
|
47
|
+
if (app === undefined || !isLoginBridge(claims)) {
|
|
51
48
|
return refused('not-app-flow', deps, fallback, undefined);
|
|
52
49
|
}
|
|
53
50
|
// The signed redirect is checked against the allow-list again: the finish
|
|
54
51
|
// never trusts a stored URL to be one it would accept.
|
|
55
|
-
const redirect =
|
|
52
|
+
const redirect = parseAppAuthCallback(app.redirect);
|
|
56
53
|
if (redirect === null)
|
|
57
54
|
return refused('not-app-flow', deps, fallback, undefined);
|
|
58
55
|
const deny = (error) => refused(error, deps, redirect, app.state);
|
|
59
56
|
if (claims.linkIntent !== undefined)
|
|
60
57
|
return deny('link-not-supported');
|
|
61
|
-
if (deps.isProviderDisabled(
|
|
58
|
+
if (deps.isProviderDisabled(bridgeProviderAddonId(claims.provider))) {
|
|
62
59
|
return deny('provider-disabled');
|
|
63
60
|
}
|
|
64
|
-
if (!
|
|
61
|
+
if (!bridgeIsFirstUseHere(claims, deps))
|
|
65
62
|
return deny('sso-state');
|
|
66
63
|
try {
|
|
67
|
-
const identity =
|
|
64
|
+
const identity = oidcIdentityOf(claims);
|
|
68
65
|
if (identity === null)
|
|
69
66
|
return deny('invalid-bridge');
|
|
70
67
|
const link = await deps.identities.findUser(identity.provider, identity.subject);
|
|
@@ -94,7 +91,7 @@ async function finishSsoApp(bridge, deps) {
|
|
|
94
91
|
return { kind: 'confirm', ...parked };
|
|
95
92
|
}
|
|
96
93
|
catch (error) {
|
|
97
|
-
if (error instanceof
|
|
94
|
+
if (error instanceof SessionUserGoneError)
|
|
98
95
|
return deny('user-gone');
|
|
99
96
|
deps.logFailure?.(error);
|
|
100
97
|
return deny('failed');
|
|
@@ -1,29 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sessionUserIdFromCookie = sessionUserIdFromCookie;
|
|
4
|
-
exports.readCookie = readCookie;
|
|
5
|
-
exports.ssoBrowserBinding = ssoBrowserBinding;
|
|
6
|
-
exports.createSsoFinishDeps = createSsoFinishDeps;
|
|
7
|
-
const types_1 = require("@camstack/types");
|
|
1
|
+
import { userError } from '@camstack/types';
|
|
8
2
|
/**
|
|
9
3
|
* The live wiring of `finishSsoLogin`'s deps (D777, D782) — kept out of
|
|
10
4
|
* `main.ts` so the route there stays a three-line mapping.
|
|
11
5
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { getUserManagementInternal } from '@camstack/system';
|
|
8
|
+
import { SSO_STATE_COOKIE } from '@camstack/types';
|
|
9
|
+
import { isAuthAddonDisabled } from './auth-provider-gate.js';
|
|
10
|
+
import { resolvePasskeyProvider } from './passkey-provider.js';
|
|
11
|
+
import { collectSecondFactors } from './second-factors.js';
|
|
12
|
+
import { readSessionCookieFromHeader } from './session-cookie.js';
|
|
19
13
|
/**
|
|
20
14
|
* The user a REAL session cookie names, or `null`. Same rule as
|
|
21
15
|
* `auth.exchangeSession`: a bridge or challenge token verifies under the same
|
|
22
16
|
* secret but carries a `kind`, and a session always has a boolean `isAdmin`
|
|
23
17
|
* and a string `userId`.
|
|
24
18
|
*/
|
|
25
|
-
function sessionUserIdFromCookie(auth, cookieHeader) {
|
|
26
|
-
const token =
|
|
19
|
+
export function sessionUserIdFromCookie(auth, cookieHeader) {
|
|
20
|
+
const token = readSessionCookieFromHeader(cookieHeader);
|
|
27
21
|
if (token === null)
|
|
28
22
|
return null;
|
|
29
23
|
try {
|
|
@@ -39,7 +33,7 @@ function sessionUserIdFromCookie(auth, cookieHeader) {
|
|
|
39
33
|
}
|
|
40
34
|
}
|
|
41
35
|
/** The value of one cookie in a raw `Cookie` header, or `null`. */
|
|
42
|
-
function readCookie(header, name) {
|
|
36
|
+
export function readCookie(header, name) {
|
|
43
37
|
if (header === undefined)
|
|
44
38
|
return null;
|
|
45
39
|
for (const pair of header.split(';')) {
|
|
@@ -54,22 +48,22 @@ function readCookie(header, name) {
|
|
|
54
48
|
return null;
|
|
55
49
|
}
|
|
56
50
|
/** SHA-256 (hex) of the request's SSO state cookie, or `null` without one. */
|
|
57
|
-
function ssoBrowserBinding(cookieHeader) {
|
|
58
|
-
const value = readCookie(cookieHeader,
|
|
59
|
-
return value === null ? null :
|
|
51
|
+
export function ssoBrowserBinding(cookieHeader) {
|
|
52
|
+
const value = readCookie(cookieHeader, SSO_STATE_COOKIE);
|
|
53
|
+
return value === null ? null : createHash('sha256').update(value).digest('hex');
|
|
60
54
|
}
|
|
61
|
-
function createSsoFinishDeps(wiring, cookieHeader) {
|
|
55
|
+
export function createSsoFinishDeps(wiring, cookieHeader) {
|
|
62
56
|
const { auth, registry, moleculer } = wiring;
|
|
63
57
|
const userManagement = () => {
|
|
64
58
|
const um = registry.getSingleton('user-management');
|
|
65
59
|
if (!um)
|
|
66
|
-
throw
|
|
60
|
+
throw userError('server.login-unavailable', undefined, 'Login unavailable — `user-management` capability not registered');
|
|
67
61
|
return um;
|
|
68
62
|
};
|
|
69
63
|
return {
|
|
70
64
|
verifyBridge: (token) => auth.verifySsoBridgeToken(token),
|
|
71
65
|
minter: wiring.minter,
|
|
72
|
-
isProviderDisabled: (addonId) =>
|
|
66
|
+
isProviderDisabled: (addonId) => isAuthAddonDisabled(registry, addonId),
|
|
73
67
|
identities: {
|
|
74
68
|
findUser: async (provider, subject) => userManagement().findUserByExternalIdentity({ provider, subject }),
|
|
75
69
|
link: async (input) => {
|
|
@@ -82,7 +76,7 @@ function createSsoFinishDeps(wiring, cookieHeader) {
|
|
|
82
76
|
return 'conflict';
|
|
83
77
|
// Hub-internal: creating a link is on no wire (not tRPC, not the
|
|
84
78
|
// mesh, not ctx.api) — only this in-process caller reaches it.
|
|
85
|
-
const internal =
|
|
79
|
+
const internal = getUserManagementInternal(um);
|
|
86
80
|
if (internal === null)
|
|
87
81
|
throw new Error('External sign-in linking not available');
|
|
88
82
|
await internal.linkExternalIdentity(input);
|
|
@@ -98,7 +92,7 @@ function createSsoFinishDeps(wiring, cookieHeader) {
|
|
|
98
92
|
? {
|
|
99
93
|
trustedNetworkLogin: wiring.trustedNetworkLogin,
|
|
100
94
|
auditSkip: async (userId, factors) => {
|
|
101
|
-
const internal =
|
|
95
|
+
const internal = getUserManagementInternal(registry.getSingleton('user-management'));
|
|
102
96
|
if (internal === null)
|
|
103
97
|
throw new Error('auth audit unavailable');
|
|
104
98
|
const ip = wiring.sessionIssue?.().ip;
|
|
@@ -114,11 +108,11 @@ function createSsoFinishDeps(wiring, cookieHeader) {
|
|
|
114
108
|
sessionUserId: () => sessionUserIdFromCookie(auth, cookieHeader),
|
|
115
109
|
secondFactors: (userId) => {
|
|
116
110
|
const um = registry.getSingleton('user-management');
|
|
117
|
-
return
|
|
111
|
+
return collectSecondFactors({
|
|
118
112
|
...(um && typeof um.getTotpStatus === 'function'
|
|
119
113
|
? { getTotpStatus: (input) => um.getTotpStatus(input) }
|
|
120
114
|
: {}),
|
|
121
|
-
passkeyProvider: () =>
|
|
115
|
+
passkeyProvider: () => resolvePasskeyProvider(registry, moleculer),
|
|
122
116
|
}, userId, 'sso');
|
|
123
117
|
},
|
|
124
118
|
signChallenge: (user) => auth.signTotpChallengeToken(user),
|
package/dist/auth/sso-finish.js
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SSO_POPUP_COMPLETE_PATH = exports.SSO_LOGIN_PATH = exports.SSO_PROFILE_PATH = exports.OIDC_IDENTITY_PREFIX = exports.OIDC_BRIDGE_PROVIDER_PREFIX = void 0;
|
|
4
|
-
exports.popupCompletionLocation = popupCompletionLocation;
|
|
5
|
-
exports.oidcIdentityOf = oidcIdentityOf;
|
|
6
|
-
exports.isLoginBridge = isLoginBridge;
|
|
7
|
-
exports.appCallbackLocation = appCallbackLocation;
|
|
8
|
-
exports.sessionFragmentParams = sessionFragmentParams;
|
|
9
|
-
exports.finishSsoLogin = finishSsoLogin;
|
|
10
|
-
exports.bridgeIsFirstUseHere = bridgeIsFirstUseHere;
|
|
11
1
|
/**
|
|
12
2
|
* `/api/auth/sso/finish` — swaps a verified login bridge for a session (D712),
|
|
13
3
|
* or completes an explicit OIDC link (D782).
|
|
@@ -45,10 +35,10 @@ exports.bridgeIsFirstUseHere = bridgeIsFirstUseHere;
|
|
|
45
35
|
*
|
|
46
36
|
* Pure: the Fastify route in `main.ts` wires the deps and maps the outcome.
|
|
47
37
|
*/
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
39
|
+
import { hasUrlScheme } from '@camstack/types';
|
|
40
|
+
import { bridgeProviderAddonId } from './auth-provider-gate.js';
|
|
41
|
+
import { SessionUserGoneError, } from './session-mint.js';
|
|
52
42
|
/**
|
|
53
43
|
* `auth-oidc` signs `auth-oidc/<instance>`.
|
|
54
44
|
*
|
|
@@ -60,25 +50,25 @@ const session_mint_js_1 = require("./session-mint.js");
|
|
|
60
50
|
* non-OIDC provider fails closed until it has its own bridge format. (The
|
|
61
51
|
* magic-link bridge that named a user record directly is gone with its addon.)
|
|
62
52
|
*/
|
|
63
|
-
|
|
53
|
+
export const OIDC_BRIDGE_PROVIDER_PREFIX = 'auth-oidc/';
|
|
64
54
|
/** `auth-oidc` names an identity `oidc:<instance>:<sub>`. */
|
|
65
|
-
|
|
55
|
+
export const OIDC_IDENTITY_PREFIX = 'oidc:';
|
|
66
56
|
/**
|
|
67
57
|
* Where a completed (or refused) link lands — the signed-in user's profile —
|
|
68
58
|
* and where a login returns. Admin-ui routes, relative to its mount: the
|
|
69
59
|
* route in `main.ts` prepends the hub's mount prefix. (These read `/admin/…`
|
|
70
60
|
* once, a path admin-ui has no route for, so both returns drew a blank page.)
|
|
71
61
|
*/
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const PROFILE_PATH =
|
|
75
|
-
const LOGIN_PATH =
|
|
62
|
+
export const SSO_PROFILE_PATH = '/system/my-access';
|
|
63
|
+
export const SSO_LOGIN_PATH = '/login';
|
|
64
|
+
const PROFILE_PATH = SSO_PROFILE_PATH;
|
|
65
|
+
const LOGIN_PATH = SSO_LOGIN_PATH;
|
|
76
66
|
/**
|
|
77
67
|
* The admin-ui page a POPUP sign-in lands on (D791): it hands the outcome to
|
|
78
68
|
* the window that opened it and closes, or — opener gone — continues to the
|
|
79
69
|
* page the outcome names, like a full-page return.
|
|
80
70
|
*/
|
|
81
|
-
|
|
71
|
+
export const SSO_POPUP_COMPLETE_PATH = '/sso-complete';
|
|
82
72
|
/**
|
|
83
73
|
* The popup form of a return `location`: the completion page, with what the
|
|
84
74
|
* page return carried — the login FRAGMENT (session / challenge / error) or
|
|
@@ -87,39 +77,39 @@ exports.SSO_POPUP_COMPLETE_PATH = '/sso-complete';
|
|
|
87
77
|
* never reaches a server log, and the completion page never posts it to
|
|
88
78
|
* another origin.
|
|
89
79
|
*/
|
|
90
|
-
function popupCompletionLocation(location) {
|
|
80
|
+
export function popupCompletionLocation(location) {
|
|
91
81
|
const hashAt = location.indexOf('#');
|
|
92
82
|
const pathAndQuery = hashAt === -1 ? location : location.slice(0, hashAt);
|
|
93
83
|
const fragment = hashAt === -1 ? '' : location.slice(hashAt + 1);
|
|
94
84
|
const queryAt = pathAndQuery.indexOf('?');
|
|
95
85
|
const path = queryAt === -1 ? pathAndQuery : pathAndQuery.slice(0, queryAt);
|
|
96
86
|
const query = queryAt === -1 ? '' : pathAndQuery.slice(queryAt + 1);
|
|
97
|
-
const target = path ===
|
|
87
|
+
const target = path === SSO_PROFILE_PATH ? 'profile' : 'login';
|
|
98
88
|
const carried = [query, fragment].filter((part) => part.length > 0).join('&');
|
|
99
|
-
return `${
|
|
89
|
+
return `${SSO_POPUP_COMPLETE_PATH}#return=${target}${carried.length > 0 ? `&${carried}` : ''}`;
|
|
100
90
|
}
|
|
101
91
|
/**
|
|
102
92
|
* `(provider, subject, issuer)` of an OIDC bridge, or `null` when it is not a
|
|
103
93
|
* well-formed one. A subject is unique only per issuer, so a bridge without
|
|
104
94
|
* the verified `iss` names no identity.
|
|
105
95
|
*/
|
|
106
|
-
function oidcIdentityOf(claims) {
|
|
107
|
-
if (!claims.provider.startsWith(
|
|
96
|
+
export function oidcIdentityOf(claims) {
|
|
97
|
+
if (!claims.provider.startsWith(OIDC_BRIDGE_PROVIDER_PREFIX))
|
|
108
98
|
return null;
|
|
109
99
|
if (claims.issuer === undefined || claims.issuer.length === 0)
|
|
110
100
|
return null;
|
|
111
|
-
const instance = claims.provider.slice(
|
|
101
|
+
const instance = claims.provider.slice(OIDC_BRIDGE_PROVIDER_PREFIX.length);
|
|
112
102
|
if (instance.length === 0)
|
|
113
103
|
return null;
|
|
114
|
-
const prefix = `${
|
|
104
|
+
const prefix = `${OIDC_IDENTITY_PREFIX}${instance}:`;
|
|
115
105
|
if (!claims.userId.startsWith(prefix))
|
|
116
106
|
return null;
|
|
117
107
|
const subject = claims.userId.slice(prefix.length);
|
|
118
108
|
return subject.length === 0 ? null : { provider: claims.provider, subject, issuer: claims.issuer };
|
|
119
109
|
}
|
|
120
110
|
/** Whether `claims` is a LOGIN bridge — the only kind this endpoint swaps. */
|
|
121
|
-
function isLoginBridge(claims) {
|
|
122
|
-
return claims.provider.startsWith(
|
|
111
|
+
export function isLoginBridge(claims) {
|
|
112
|
+
return claims.provider.startsWith(OIDC_BRIDGE_PROVIDER_PREFIX);
|
|
123
113
|
}
|
|
124
114
|
function unauthorized(error) {
|
|
125
115
|
return { kind: 'error', status: 401, body: { error } };
|
|
@@ -131,11 +121,11 @@ function linkRefused(reason) {
|
|
|
131
121
|
return redirect(`${PROFILE_PATH}?linkError=${reason}`);
|
|
132
122
|
}
|
|
133
123
|
/** The app's callback with one query parameter — `link=<code>` or `linkError=<reason>`. */
|
|
134
|
-
function appCallbackLocation(returnTo, key, value) {
|
|
124
|
+
export function appCallbackLocation(returnTo, key, value) {
|
|
135
125
|
return `${returnTo}?${key}=${encodeURIComponent(value)}`;
|
|
136
126
|
}
|
|
137
127
|
function failed(error, deps) {
|
|
138
|
-
if (error instanceof
|
|
128
|
+
if (error instanceof SessionUserGoneError)
|
|
139
129
|
return unauthorized('User no longer exists');
|
|
140
130
|
// The detail stays in the server log: a store error can name user ids.
|
|
141
131
|
deps.logFailure?.(error);
|
|
@@ -146,7 +136,7 @@ function failed(error, deps) {
|
|
|
146
136
|
* admin-ui is a refreshing client — the refresh token and both expiries. A
|
|
147
137
|
* fragment never reaches a server log or a Referer.
|
|
148
138
|
*/
|
|
149
|
-
function sessionFragmentParams(session) {
|
|
139
|
+
export function sessionFragmentParams(session) {
|
|
150
140
|
return {
|
|
151
141
|
token: session.token,
|
|
152
142
|
...(session.refreshToken !== undefined ? { refreshToken: session.refreshToken } : {}),
|
|
@@ -252,12 +242,12 @@ async function completeLogin(claims, deps) {
|
|
|
252
242
|
}
|
|
253
243
|
return signIn(await deps.minter.findUser(link.userId), claims.provider, deps);
|
|
254
244
|
}
|
|
255
|
-
async function finishSsoLogin(bridge, deps, options = {}) {
|
|
245
|
+
export async function finishSsoLogin(bridge, deps, options = {}) {
|
|
256
246
|
const outcome = await finishSsoReturn(bridge, deps);
|
|
257
247
|
if (options.popup !== true || outcome.kind !== 'redirect')
|
|
258
248
|
return outcome;
|
|
259
249
|
// A native return goes to the app's scheme, never to a popup page.
|
|
260
|
-
if (
|
|
250
|
+
if (hasUrlScheme(outcome.location))
|
|
261
251
|
return outcome;
|
|
262
252
|
return redirect(popupCompletionLocation(outcome.location));
|
|
263
253
|
}
|
|
@@ -265,7 +255,7 @@ async function finishSsoLogin(bridge, deps, options = {}) {
|
|
|
265
255
|
function sameDigest(a, b) {
|
|
266
256
|
const left = Buffer.from(a, 'utf8');
|
|
267
257
|
const right = Buffer.from(b, 'utf8');
|
|
268
|
-
return left.length === right.length &&
|
|
258
|
+
return left.length === right.length && timingSafeEqual(left, right);
|
|
269
259
|
}
|
|
270
260
|
/**
|
|
271
261
|
* Is this the FIRST presentation of the bridge, in the browser that started
|
|
@@ -273,7 +263,7 @@ function sameDigest(a, b) {
|
|
|
273
263
|
* presented from a browser without the cookie is refused without being burned
|
|
274
264
|
* — the real browser can still finish.
|
|
275
265
|
*/
|
|
276
|
-
function bridgeIsFirstUseHere(claims, deps) {
|
|
266
|
+
export function bridgeIsFirstUseHere(claims, deps) {
|
|
277
267
|
if (claims.jti === undefined || claims.browserBinding === undefined)
|
|
278
268
|
return false;
|
|
279
269
|
const presented = deps.browserBinding();
|
|
@@ -293,7 +283,7 @@ async function finishSsoReturn(bridge, deps) {
|
|
|
293
283
|
// which hands the app a code; it must never become a web session here.
|
|
294
284
|
if (claims.app !== undefined)
|
|
295
285
|
return unauthorized('Not a web login bridge token');
|
|
296
|
-
if (deps.isProviderDisabled(
|
|
286
|
+
if (deps.isProviderDisabled(bridgeProviderAddonId(claims.provider))) {
|
|
297
287
|
return unauthorized('Login provider is disabled');
|
|
298
288
|
}
|
|
299
289
|
if (!bridgeIsFirstUseHere(claims, deps)) {
|
package/dist/auth/sso-routes.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.registerSsoRoutes = registerSsoRoutes;
|
|
4
1
|
/**
|
|
5
2
|
* The two browser-facing SSO routes, kept out of `main.ts`:
|
|
6
3
|
*
|
|
@@ -14,14 +11,14 @@ exports.registerSsoRoutes = registerSsoRoutes;
|
|
|
14
11
|
* - `GET /api/auth/browser-handoff?code=…` — a one-time code from
|
|
15
12
|
* `auth.createBrowserHandoff` opens the account page signed in (D801).
|
|
16
13
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
import { SSO_STATE_COOKIE } from '@camstack/types';
|
|
15
|
+
import { parseForwardedPrefix } from '../api/static/spa-mount.js';
|
|
16
|
+
import { clientKeyFromRequest } from './auth-rate-limit.js';
|
|
17
|
+
import { browserHandoffLocation, ssoReturnLocation } from './native-return.js';
|
|
18
|
+
import { finishSsoLogin } from './sso-finish.js';
|
|
19
|
+
import { APP_CONFIRM_PARAM, APP_CONFIRM_PATH, appConfirmCookie } from './app-confirm-routes.js';
|
|
20
|
+
import { finishSsoApp } from './sso-finish-app.js';
|
|
21
|
+
import { createSsoFinishDeps } from './sso-finish-deps.js';
|
|
25
22
|
/** A User-Agent as a session's client label. */
|
|
26
23
|
function clientOf(ua) {
|
|
27
24
|
const value = Array.isArray(ua) ? ua[0] : ua;
|
|
@@ -29,11 +26,11 @@ function clientOf(ua) {
|
|
|
29
26
|
}
|
|
30
27
|
/** Expire the state cookie (both Secure variants are cleared by name + path). */
|
|
31
28
|
function clearSsoStateCookie() {
|
|
32
|
-
return `${
|
|
29
|
+
return `${SSO_STATE_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`;
|
|
33
30
|
}
|
|
34
|
-
function registerSsoRoutes(fastify, deps) {
|
|
31
|
+
export function registerSsoRoutes(fastify, deps) {
|
|
35
32
|
fastify.get('/api/auth/sso/finish', async (request, reply) => {
|
|
36
|
-
const client =
|
|
33
|
+
const client = clientKeyFromRequest(request, deps.network);
|
|
37
34
|
if (client !== null) {
|
|
38
35
|
const verdict = deps.limiter.check(`sso-finish:${client}`);
|
|
39
36
|
if (!verdict.allowed) {
|
|
@@ -41,7 +38,7 @@ function registerSsoRoutes(fastify, deps) {
|
|
|
41
38
|
return reply.status(429).send({ error: 'Too many attempts' });
|
|
42
39
|
}
|
|
43
40
|
}
|
|
44
|
-
const outcome = await
|
|
41
|
+
const outcome = await finishSsoLogin(request.query.bridge, createSsoFinishDeps({
|
|
45
42
|
...deps.wiring,
|
|
46
43
|
sessionIssue: () => {
|
|
47
44
|
const ip = deps.network.clientAddress(request).ip;
|
|
@@ -62,8 +59,8 @@ function registerSsoRoutes(fastify, deps) {
|
|
|
62
59
|
reply.code(302);
|
|
63
60
|
// Admin-ui paths are relative to the hub's mount (HA ingress etc.); a
|
|
64
61
|
// native app return (`camstack://…`, D801) is not a hub path at all.
|
|
65
|
-
const mount =
|
|
66
|
-
reply.header('Location',
|
|
62
|
+
const mount = parseForwardedPrefix(request.headers['x-forwarded-prefix']) ?? '';
|
|
63
|
+
reply.header('Location', ssoReturnLocation(mount, outcome.location));
|
|
67
64
|
return reply.send('');
|
|
68
65
|
});
|
|
69
66
|
// The native-app leg (D858): same bridge rules, a handoff code instead of a
|
|
@@ -73,7 +70,7 @@ function registerSsoRoutes(fastify, deps) {
|
|
|
73
70
|
reply.header('Cache-Control', 'no-store');
|
|
74
71
|
const clearState = clearSsoStateCookie();
|
|
75
72
|
reply.header('Set-Cookie', clearState);
|
|
76
|
-
const client =
|
|
73
|
+
const client = clientKeyFromRequest(request, deps.network);
|
|
77
74
|
if (client !== null) {
|
|
78
75
|
const verdict = deps.limiter.check(`sso-finish:${client}`);
|
|
79
76
|
if (!verdict.allowed) {
|
|
@@ -81,8 +78,8 @@ function registerSsoRoutes(fastify, deps) {
|
|
|
81
78
|
return reply.status(429).send({ error: 'Too many attempts' });
|
|
82
79
|
}
|
|
83
80
|
}
|
|
84
|
-
const outcome = await
|
|
85
|
-
...
|
|
81
|
+
const outcome = await finishSsoApp(request.query.bridge, {
|
|
82
|
+
...createSsoFinishDeps({
|
|
86
83
|
...deps.wiring,
|
|
87
84
|
logFailure: (error) => deps.logger.error('SSO app finish failed', {
|
|
88
85
|
meta: { error: error instanceof Error ? error.message : String(error) },
|
|
@@ -99,19 +96,19 @@ function registerSsoRoutes(fastify, deps) {
|
|
|
99
96
|
}
|
|
100
97
|
// The confirmation page, on this hub (mount-aware); its cookie binds the
|
|
101
98
|
// token to this browser. The SSO state cookie is spent either way.
|
|
102
|
-
const mount =
|
|
99
|
+
const mount = parseForwardedPrefix(request.headers['x-forwarded-prefix']) ?? '';
|
|
103
100
|
reply.header('Set-Cookie', [
|
|
104
101
|
clearState,
|
|
105
|
-
|
|
102
|
+
appConfirmCookie(outcome.cookie, request.protocol === 'https'),
|
|
106
103
|
]);
|
|
107
|
-
reply.header('Location', `${mount}${
|
|
104
|
+
reply.header('Location', `${mount}${APP_CONFIRM_PATH}?${APP_CONFIRM_PARAM}=${encodeURIComponent(outcome.token)}`);
|
|
108
105
|
return reply.send('');
|
|
109
106
|
});
|
|
110
107
|
// A one-time code minted by `auth.createBrowserHandoff` opens the account
|
|
111
108
|
// page signed in (D801): the session rides the URL FRAGMENT (never a server
|
|
112
109
|
// log), and the only destination is the account page.
|
|
113
110
|
fastify.get('/api/auth/browser-handoff', async (request, reply) => {
|
|
114
|
-
const mount =
|
|
111
|
+
const mount = parseForwardedPrefix(request.headers['x-forwarded-prefix']) ?? '';
|
|
115
112
|
const grant = typeof request.query.code === 'string'
|
|
116
113
|
? deps.browserHandoffCodes.redeem(request.query.code)
|
|
117
114
|
: null;
|
|
@@ -136,7 +133,7 @@ function registerSsoRoutes(fastify, deps) {
|
|
|
136
133
|
}
|
|
137
134
|
reply.code(302);
|
|
138
135
|
reply.header('Referrer-Policy', 'no-referrer');
|
|
139
|
-
reply.header('Location',
|
|
136
|
+
reply.header('Location', browserHandoffLocation(mount, session));
|
|
140
137
|
return reply.send('');
|
|
141
138
|
});
|
|
142
139
|
}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UsedBridgeIds = void 0;
|
|
4
1
|
/**
|
|
5
2
|
* The `jti`s of login bridges already swapped for a session or a link.
|
|
6
3
|
*
|
|
@@ -15,7 +12,7 @@ exports.UsedBridgeIds = void 0;
|
|
|
15
12
|
*/
|
|
16
13
|
const RETENTION_MS = 10 * 60_000;
|
|
17
14
|
const DEFAULT_MAX_ENTRIES = 10_000;
|
|
18
|
-
class UsedBridgeIds {
|
|
15
|
+
export class UsedBridgeIds {
|
|
19
16
|
used = new Map();
|
|
20
17
|
now;
|
|
21
18
|
maxEntries;
|
|
@@ -49,4 +46,3 @@ class UsedBridgeIds {
|
|
|
49
46
|
}
|
|
50
47
|
}
|
|
51
48
|
}
|
|
52
|
-
exports.UsedBridgeIds = UsedBridgeIds;
|
package/dist/boot/boot-config.js
CHANGED
|
@@ -1,53 +1,14 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Phase 1: Bootstrap config loading and infrastructure setup.
|
|
4
3
|
*
|
|
5
4
|
* Extracted from main.ts — pure extraction, no behavior change.
|
|
6
5
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Object.defineProperty(o, k2, desc);
|
|
14
|
-
}) : (function(o, m, k, k2) {
|
|
15
|
-
if (k2 === undefined) k2 = k;
|
|
16
|
-
o[k2] = m[k];
|
|
17
|
-
}));
|
|
18
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
-
}) : function(o, v) {
|
|
21
|
-
o["default"] = v;
|
|
22
|
-
});
|
|
23
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
-
var ownKeys = function(o) {
|
|
25
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
-
var ar = [];
|
|
27
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
-
return ar;
|
|
29
|
-
};
|
|
30
|
-
return ownKeys(o);
|
|
31
|
-
};
|
|
32
|
-
return function (mod) {
|
|
33
|
-
if (mod && mod.__esModule) return mod;
|
|
34
|
-
var result = {};
|
|
35
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
-
__setModuleDefault(result, mod);
|
|
37
|
-
return result;
|
|
38
|
-
};
|
|
39
|
-
})();
|
|
40
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
-
exports.ignoredTlsCertSources = ignoredTlsCertSources;
|
|
42
|
-
exports.retiredSecretSources = retiredSecretSources;
|
|
43
|
-
exports.loadBootstrapConfig = loadBootstrapConfig;
|
|
44
|
-
exports.setupInfra = setupInfra;
|
|
45
|
-
const fs = __importStar(require("node:fs"));
|
|
46
|
-
const path = __importStar(require("node:path"));
|
|
47
|
-
const types_1 = require("@camstack/types");
|
|
48
|
-
const yaml = __importStar(require("js-yaml"));
|
|
49
|
-
const config_schema_1 = require("../core/config/config.schema");
|
|
50
|
-
const storage_location_manager_1 = require("../core/storage/storage-location-manager");
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import { asJsonObject } from '@camstack/types';
|
|
9
|
+
import * as yaml from 'js-yaml';
|
|
10
|
+
import { bootstrapSchema } from '../core/config/config.schema';
|
|
11
|
+
import { StorageLocationManager } from '../core/storage/storage-location-manager';
|
|
51
12
|
// ---------------------------------------------------------------------------
|
|
52
13
|
// Constants
|
|
53
14
|
// ---------------------------------------------------------------------------
|
|
@@ -100,7 +61,7 @@ function setNested(obj, p, value) {
|
|
|
100
61
|
return obj;
|
|
101
62
|
if (rest.length === 0)
|
|
102
63
|
return { ...obj, [head]: value };
|
|
103
|
-
const child =
|
|
64
|
+
const child = asJsonObject(obj[head]) ?? {};
|
|
104
65
|
return { ...obj, [head]: setNested(child, rest.join('.'), value) };
|
|
105
66
|
}
|
|
106
67
|
/**
|
|
@@ -108,12 +69,12 @@ function setNested(obj, p, value) {
|
|
|
108
69
|
* certificate and is now ignored (D726). Blank env values count as unset,
|
|
109
70
|
* exactly as the override loop treats them.
|
|
110
71
|
*/
|
|
111
|
-
function ignoredTlsCertSources(env, raw) {
|
|
72
|
+
export function ignoredTlsCertSources(env, raw) {
|
|
112
73
|
const fromEnv = IGNORED_TLS_CERT_ENV.filter((name) => {
|
|
113
74
|
const value = env[name];
|
|
114
75
|
return value !== undefined && value !== '';
|
|
115
76
|
});
|
|
116
|
-
const tls =
|
|
77
|
+
const tls = asJsonObject(raw['tls']) ?? {};
|
|
117
78
|
const fromYaml = IGNORED_TLS_CERT_KEYS.filter((key) => tls[key] !== undefined).map((key) => `tls.${key}`);
|
|
118
79
|
return [...fromEnv, ...fromYaml];
|
|
119
80
|
}
|
|
@@ -121,12 +82,12 @@ function ignoredTlsCertSources(env, raw) {
|
|
|
121
82
|
* Every retired secret source still present: env variables set (non-blank) and
|
|
122
83
|
* `config.yaml` `auth.*` keys. Names only — a value is never read into a log.
|
|
123
84
|
*/
|
|
124
|
-
function retiredSecretSources(env, raw) {
|
|
85
|
+
export function retiredSecretSources(env, raw) {
|
|
125
86
|
const fromEnv = RETIRED_SECRET_ENV.filter((name) => {
|
|
126
87
|
const value = env[name];
|
|
127
88
|
return value !== undefined && value !== '';
|
|
128
89
|
});
|
|
129
|
-
const auth =
|
|
90
|
+
const auth = asJsonObject(raw['auth']) ?? {};
|
|
130
91
|
const fromYaml = RETIRED_AUTH_KEYS.filter((key) => auth[key] !== undefined && auth[key] !== null).map((key) => `auth.${key}`);
|
|
131
92
|
return [...fromEnv, ...fromYaml];
|
|
132
93
|
}
|
|
@@ -137,13 +98,13 @@ function retiredSecretSources(env, raw) {
|
|
|
137
98
|
* Load bootstrap config from a YAML file, apply env-var overrides, and
|
|
138
99
|
* validate via the bootstrap Zod schema.
|
|
139
100
|
*/
|
|
140
|
-
function loadBootstrapConfig(configPath) {
|
|
101
|
+
export function loadBootstrapConfig(configPath) {
|
|
141
102
|
// Only bootstrap sections live in config.yaml.
|
|
142
103
|
// All runtime settings are stored in the SQL system_settings table.
|
|
143
104
|
let raw;
|
|
144
105
|
if (fs.existsSync(configPath)) {
|
|
145
106
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
146
|
-
raw =
|
|
107
|
+
raw = asJsonObject(yaml.load(content)) ?? {};
|
|
147
108
|
// Merge in any missing bootstrap sections (server, auth only)
|
|
148
109
|
let updated = false;
|
|
149
110
|
for (const [key, defaults] of Object.entries(CONFIG_DEFAULTS)) {
|
|
@@ -205,7 +166,7 @@ function loadBootstrapConfig(configPath) {
|
|
|
205
166
|
console.warn(`[Phase1] WARN ${ignored.join(', ')} ignored — the certificate is set only in the admin UI. ` +
|
|
206
167
|
'Upload the certificate in Network → Local access (D726).');
|
|
207
168
|
}
|
|
208
|
-
return
|
|
169
|
+
return bootstrapSchema.parse(raw);
|
|
209
170
|
}
|
|
210
171
|
// ---------------------------------------------------------------------------
|
|
211
172
|
// setupInfra
|
|
@@ -214,7 +175,7 @@ function loadBootstrapConfig(configPath) {
|
|
|
214
175
|
* Phase 2: Create StorageLocationManager, ensure directories, configure TLS.
|
|
215
176
|
* Returns the full InfraContext needed by subsequent boot phases.
|
|
216
177
|
*/
|
|
217
|
-
async function setupInfra(_configPath, bootstrapConfig) {
|
|
178
|
+
export async function setupInfra(_configPath, bootstrapConfig) {
|
|
218
179
|
// No secret is generated here: the session signing key is the keychain's
|
|
219
180
|
// (local-auth loads or creates it once the keychain is up — D797).
|
|
220
181
|
const config = bootstrapConfig;
|
|
@@ -224,7 +185,7 @@ async function setupInfra(_configPath, bootstrapConfig) {
|
|
|
224
185
|
console.log(`[Phase1] Bootstrap: port=${port}, host=${host}, dataPath=${dataPath}`);
|
|
225
186
|
// --- Phase 2: Init StorageLocationManager → ensure all dirs exist ---
|
|
226
187
|
console.log('[Phase2] Initializing storage locations…');
|
|
227
|
-
const locationManager = new
|
|
188
|
+
const locationManager = new StorageLocationManager(dataPath);
|
|
228
189
|
await locationManager.initializeDefaults();
|
|
229
190
|
const locationStatus = locationManager.getStatus();
|
|
230
191
|
for (const { name, available, path: locPath } of locationStatus) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* The hub's `onModuleInit` order, as one named, testable sequence.
|
|
4
3
|
*
|
|
@@ -15,10 +14,8 @@
|
|
|
15
14
|
* 4. AddonRegistry — boots EVERY addon. Minutes of work on a real hub.
|
|
16
15
|
* 5. …post-boot reconciles.
|
|
17
16
|
*/
|
|
18
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.runBootInitSequence = runBootInitSequence;
|
|
20
17
|
/** Run the boot init steps in dependency order. */
|
|
21
|
-
async function runBootInitSequence(sequence) {
|
|
18
|
+
export async function runBootInitSequence(sequence) {
|
|
22
19
|
await sequence.moleculer();
|
|
23
20
|
await sequence.addonBridge();
|
|
24
21
|
await sequence.agentRegistry();
|