@camstack/server 1.2.481 → 1.2.482
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/main.js +15 -4
- package/dist/api/core/cap-providers.js +49 -5
- package/dist/api/core/integration-secrets.js +60 -0
- package/dist/api/trpc/generated-cap-mounts.js +3 -1
- package/dist/api/trpc/generated-cap-routers.js +1668 -1357
- package/dist/auth/sso-finish.js +44 -5
- package/dist/boot/platform-keychain-key.js +94 -0
- package/dist/core/addon/addon-registry.service.js +59 -0
- package/dist/core/addon/addon-settings-provider.js +7 -1
- package/dist/core/moleculer/moleculer.service.js +31 -1
- package/dist/main.js +17 -3
- package/package.json +10 -10
package/dist/auth/sso-finish.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.OIDC_IDENTITY_PREFIX = exports.OIDC_BRIDGE_PROVIDER_PREFIX = exports.MAGIC_LINK_BRIDGE_PROVIDER = void 0;
|
|
3
|
+
exports.SSO_POPUP_COMPLETE_PATH = exports.SSO_LOGIN_PATH = exports.SSO_PROFILE_PATH = exports.OIDC_IDENTITY_PREFIX = exports.OIDC_BRIDGE_PROVIDER_PREFIX = exports.MAGIC_LINK_BRIDGE_PROVIDER = void 0;
|
|
4
|
+
exports.popupCompletionLocation = popupCompletionLocation;
|
|
4
5
|
exports.isLoginBridge = isLoginBridge;
|
|
5
6
|
exports.finishSsoLogin = finishSsoLogin;
|
|
6
7
|
const auth_provider_gate_js_1 = require("./auth-provider-gate.js");
|
|
@@ -22,9 +23,41 @@ exports.MAGIC_LINK_BRIDGE_PROVIDER = 'auth-magic-link';
|
|
|
22
23
|
exports.OIDC_BRIDGE_PROVIDER_PREFIX = 'auth-oidc/';
|
|
23
24
|
/** `auth-oidc` names an identity `oidc:<instance>:<sub>`. */
|
|
24
25
|
exports.OIDC_IDENTITY_PREFIX = 'oidc:';
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Where a completed (or refused) link lands — the signed-in user's profile —
|
|
28
|
+
* and where a login returns. Admin-ui routes, relative to its mount: the
|
|
29
|
+
* route in `main.ts` prepends the hub's mount prefix. (These read `/admin/…`
|
|
30
|
+
* once, a path admin-ui has no route for, so both returns drew a blank page.)
|
|
31
|
+
*/
|
|
32
|
+
exports.SSO_PROFILE_PATH = '/system/my-access';
|
|
33
|
+
exports.SSO_LOGIN_PATH = '/login';
|
|
34
|
+
const PROFILE_PATH = exports.SSO_PROFILE_PATH;
|
|
35
|
+
const LOGIN_PATH = exports.SSO_LOGIN_PATH;
|
|
36
|
+
/**
|
|
37
|
+
* The admin-ui page a POPUP sign-in lands on (D791): it hands the outcome to
|
|
38
|
+
* the window that opened it and closes, or — opener gone — continues to the
|
|
39
|
+
* page the outcome names, like a full-page return.
|
|
40
|
+
*/
|
|
41
|
+
exports.SSO_POPUP_COMPLETE_PATH = '/sso-complete';
|
|
42
|
+
/**
|
|
43
|
+
* The popup form of a return `location`: the completion page, with what the
|
|
44
|
+
* page return carried — the login FRAGMENT (session / challenge / error) or
|
|
45
|
+
* the profile QUERY (link outcome) — moved into the completion page's
|
|
46
|
+
* fragment, after `return=login|profile`. Fragment only: a session still
|
|
47
|
+
* never reaches a server log, and the completion page never posts it to
|
|
48
|
+
* another origin.
|
|
49
|
+
*/
|
|
50
|
+
function popupCompletionLocation(location) {
|
|
51
|
+
const hashAt = location.indexOf('#');
|
|
52
|
+
const pathAndQuery = hashAt === -1 ? location : location.slice(0, hashAt);
|
|
53
|
+
const fragment = hashAt === -1 ? '' : location.slice(hashAt + 1);
|
|
54
|
+
const queryAt = pathAndQuery.indexOf('?');
|
|
55
|
+
const path = queryAt === -1 ? pathAndQuery : pathAndQuery.slice(0, queryAt);
|
|
56
|
+
const query = queryAt === -1 ? '' : pathAndQuery.slice(queryAt + 1);
|
|
57
|
+
const target = path === exports.SSO_PROFILE_PATH ? 'profile' : 'login';
|
|
58
|
+
const carried = [query, fragment].filter((part) => part.length > 0).join('&');
|
|
59
|
+
return `${exports.SSO_POPUP_COMPLETE_PATH}#return=${target}${carried.length > 0 ? `&${carried}` : ''}`;
|
|
60
|
+
}
|
|
28
61
|
/**
|
|
29
62
|
* `(provider, subject, issuer)` of an OIDC bridge, or `null` when it is not a
|
|
30
63
|
* well-formed one. A subject is unique only per issuer, so a bridge without
|
|
@@ -130,7 +163,13 @@ async function completeLogin(claims, deps) {
|
|
|
130
163
|
}
|
|
131
164
|
return signIn(await deps.minter.findUser(link.userId), claims.provider, deps);
|
|
132
165
|
}
|
|
133
|
-
async function finishSsoLogin(bridge, deps) {
|
|
166
|
+
async function finishSsoLogin(bridge, deps, options = {}) {
|
|
167
|
+
const outcome = await finishSsoReturn(bridge, deps);
|
|
168
|
+
if (options.popup !== true || outcome.kind !== 'redirect')
|
|
169
|
+
return outcome;
|
|
170
|
+
return redirect(popupCompletionLocation(outcome.location));
|
|
171
|
+
}
|
|
172
|
+
async function finishSsoReturn(bridge, deps) {
|
|
134
173
|
if (!bridge)
|
|
135
174
|
return { kind: 'error', status: 400, body: { error: 'Missing bridge token' } };
|
|
136
175
|
const claims = deps.verifyBridge(bridge);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
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.keychainKeyFdFromArgv = keychainKeyFdFromArgv;
|
|
37
|
+
exports.loadPlatformKeychainKey = loadPlatformKeychainKey;
|
|
38
|
+
/**
|
|
39
|
+
* Read the keychain master key a DESKTOP app handed this hub over an inherited
|
|
40
|
+
* pipe (D788): `--keychain-key-fd=<n>` names the descriptor, the parent wrote
|
|
41
|
+
* the key (base64) and closed it. Read once, at boot, before anything else
|
|
42
|
+
* can — then the descriptor is closed and the key lives only in memory, in
|
|
43
|
+
* the platform-key slot the keychain builtin reads.
|
|
44
|
+
*
|
|
45
|
+
* An env var was rejected: a child's environment is readable by every process
|
|
46
|
+
* of the same user and is inherited by every addon runner the hub forks.
|
|
47
|
+
*/
|
|
48
|
+
const fs = __importStar(require("node:fs"));
|
|
49
|
+
const system_1 = require("@camstack/system");
|
|
50
|
+
const FD_ARG = '--keychain-key-fd=';
|
|
51
|
+
const KEY_BYTES = 32;
|
|
52
|
+
/** The OS keychain's name, for the Secrets page. */
|
|
53
|
+
function platformLabel(platform) {
|
|
54
|
+
switch (platform) {
|
|
55
|
+
case 'darwin':
|
|
56
|
+
return 'macOS Keychain';
|
|
57
|
+
case 'win32':
|
|
58
|
+
return 'Windows DPAPI';
|
|
59
|
+
default:
|
|
60
|
+
return 'OS secret service';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Parse the descriptor number out of argv; `null` when not passed. */
|
|
64
|
+
function keychainKeyFdFromArgv(argv) {
|
|
65
|
+
const arg = argv.find((a) => a.startsWith(FD_ARG));
|
|
66
|
+
if (arg === undefined)
|
|
67
|
+
return null;
|
|
68
|
+
const n = Number(arg.slice(FD_ARG.length));
|
|
69
|
+
return Number.isInteger(n) && n > 2 ? n : null;
|
|
70
|
+
}
|
|
71
|
+
function loadPlatformKeychainKey(argv = process.argv, readFd = (fd) => fs.readFileSync(fd, 'utf8'), closeFd = (fd) => fs.closeSync(fd)) {
|
|
72
|
+
const fd = keychainKeyFdFromArgv(argv);
|
|
73
|
+
if (fd === null)
|
|
74
|
+
return { kind: 'none' };
|
|
75
|
+
try {
|
|
76
|
+
const key = Buffer.from(readFd(fd).trim(), 'base64');
|
|
77
|
+
if (key.length !== KEY_BYTES)
|
|
78
|
+
return { kind: 'failed', error: 'not 32 bytes of key material' };
|
|
79
|
+
const label = platformLabel(process.platform);
|
|
80
|
+
(0, system_1.setPlatformKeychainKey)({ label, key });
|
|
81
|
+
return { kind: 'loaded', label };
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
return { kind: 'failed', error: err instanceof Error ? err.message : String(err) };
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
try {
|
|
88
|
+
closeFd(fd);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
/* already closed */
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -1151,6 +1151,22 @@ class AddonRegistryService {
|
|
|
1151
1151
|
* addon (`execution.group`) is never misclassified as `in-process`.
|
|
1152
1152
|
* Falls back to the addon id when the declaration is not yet known.
|
|
1153
1153
|
*/
|
|
1154
|
+
/** The runner id hosting an addon — its `execution.group`, or itself. */
|
|
1155
|
+
runnerIdFor(addonId) {
|
|
1156
|
+
return this.childIdForAddon(addonId);
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* A hub in-process keychain handle acting for `owner` — the door hub-main
|
|
1160
|
+
* code (the integration settings provider) uses to keep another addon's
|
|
1161
|
+
* values (D787). Never handed to an addon.
|
|
1162
|
+
*/
|
|
1163
|
+
secretsForOwner(owner) {
|
|
1164
|
+
return (0, types_1.createSecretsAccess)({
|
|
1165
|
+
owner,
|
|
1166
|
+
transport: this.inProcessKeychainTransport(owner),
|
|
1167
|
+
eventBus: this.eventBusService,
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1154
1170
|
childIdForAddon(addonId) {
|
|
1155
1171
|
const decl = this.addonEntries.get(addonId)?.declaration;
|
|
1156
1172
|
return decl ? (0, types_1.resolveRunnerId)(decl, addonId) : addonId;
|
|
@@ -2934,6 +2950,9 @@ class AddonRegistryService {
|
|
|
2934
2950
|
deps: new system_1.AddonDepsManager(dataDir, logger),
|
|
2935
2951
|
kernel: {
|
|
2936
2952
|
localNodeId: this.broker.nodeID,
|
|
2953
|
+
// The keychain accepts a grouped runner's stamp (its group id) for the
|
|
2954
|
+
// refs of the addons that runner hosts.
|
|
2955
|
+
runnerIdForAddon: (id) => this.childIdForAddon(id),
|
|
2937
2956
|
storage: storageProvider ?? undefined,
|
|
2938
2957
|
deviceRegistry: this.deviceRegistry ?? undefined,
|
|
2939
2958
|
devices: deviceManagerApi,
|
|
@@ -2978,6 +2997,16 @@ class AddonRegistryService {
|
|
|
2978
2997
|
return (0, types_1.createDeviceProxy)(api, binding);
|
|
2979
2998
|
},
|
|
2980
2999
|
settings: settingsView,
|
|
3000
|
+
// This builtin's keychain values (D787). In-process, so the caller is
|
|
3001
|
+
// STATED (no wire, no stamp) — the one place the keychain trusts a
|
|
3002
|
+
// claim. `forOwner` is the door a system builtin (device-manager,
|
|
3003
|
+
// storage-orchestrator) uses to keep another addon's values.
|
|
3004
|
+
secrets: (0, types_1.createSecretsAccess)({
|
|
3005
|
+
owner: addonId,
|
|
3006
|
+
transport: this.inProcessKeychainTransport(addonId),
|
|
3007
|
+
transportForOwner: (owner) => this.inProcessKeychainTransport(owner),
|
|
3008
|
+
eventBus: this.eventBusService,
|
|
3009
|
+
}),
|
|
2981
3010
|
useCapability(capName, scope = { type: 'global' }) {
|
|
2982
3011
|
return getOrCreateHandle(capName, scope, 15_000);
|
|
2983
3012
|
},
|
|
@@ -2997,6 +3026,36 @@ class AddonRegistryService {
|
|
|
2997
3026
|
};
|
|
2998
3027
|
return ctx;
|
|
2999
3028
|
}
|
|
3029
|
+
/**
|
|
3030
|
+
* The keychain transport of a hub in-process caller: the provider lives in
|
|
3031
|
+
* this process, so the call goes straight to it with the caller STATED.
|
|
3032
|
+
* Built per call so a keychain registered after this context still answers;
|
|
3033
|
+
* a missing provider throws, which `ctx.secrets` reads as `unavailable`.
|
|
3034
|
+
*/
|
|
3035
|
+
inProcessKeychainTransport(owner) {
|
|
3036
|
+
const provider = () => {
|
|
3037
|
+
const p = this.capabilityRegistry.getSingleton('keychain');
|
|
3038
|
+
if (p === null || p === undefined)
|
|
3039
|
+
throw new Error('keychain provider is not registered');
|
|
3040
|
+
return p;
|
|
3041
|
+
};
|
|
3042
|
+
const callerAddonId = owner;
|
|
3043
|
+
return {
|
|
3044
|
+
put: (input) => {
|
|
3045
|
+
const stated = { ref: input.ref, value: input.value, callerAddonId };
|
|
3046
|
+
return provider().put(stated);
|
|
3047
|
+
},
|
|
3048
|
+
resolve: (input) => {
|
|
3049
|
+
const stated = { refs: [...input.refs], callerAddonId };
|
|
3050
|
+
return provider().resolve(stated);
|
|
3051
|
+
},
|
|
3052
|
+
delete: (input) => {
|
|
3053
|
+
const stated = { refs: [...input.refs], callerAddonId };
|
|
3054
|
+
return provider().delete(stated);
|
|
3055
|
+
},
|
|
3056
|
+
getState: () => provider().getState({}),
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3000
3059
|
/**
|
|
3001
3060
|
* Per-addon disposer chain. Created lazily the first time an addon
|
|
3002
3061
|
* calls `ctx.addDisposer(...)`. Drained by `restartAddon()` /
|
|
@@ -76,7 +76,13 @@ function createAddonSettingsProvider(deps) {
|
|
|
76
76
|
if (!addon || typeof addon.updateGlobalSettings !== 'function') {
|
|
77
77
|
throw new Error(`Addon "${input.addonId}" does not implement updateGlobalSettings`);
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
// The admin API's write is the OPERATOR's (D787).
|
|
80
|
+
if (typeof addon.updateGlobalSettingsAsOperator === 'function') {
|
|
81
|
+
await addon.updateGlobalSettingsAsOperator(input.patch, input.nodeId);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
await addon.updateGlobalSettings(input.patch, input.nodeId);
|
|
85
|
+
}
|
|
80
86
|
return { success: true };
|
|
81
87
|
}
|
|
82
88
|
return forkedUpdate(input.addonId, 'updateGlobalSettings', { patch: input.patch, ...(input.nodeId ? { nodeId: input.nodeId } : {}) }, input.nodeId);
|
|
@@ -132,6 +132,27 @@ class MoleculerService {
|
|
|
132
132
|
setRunnerIdResolver(resolver) {
|
|
133
133
|
this.runnerIdResolver = resolver;
|
|
134
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Does `nodeId` (an agent) host the runner `runnerId`? Read from what the
|
|
137
|
+
* node itself registered: its manifest names the addons it runs, and an
|
|
138
|
+
* addon's runner is its `execution.group` or itself. A node that never
|
|
139
|
+
* registered hosts nothing.
|
|
140
|
+
*/
|
|
141
|
+
isRunnerOnNode(nodeId, runnerId) {
|
|
142
|
+
const nodes = this.nodeRegistry
|
|
143
|
+
.listNodeIds()
|
|
144
|
+
.filter((id) => id === nodeId || id.startsWith(`${nodeId}/`));
|
|
145
|
+
if (nodes.includes(`${nodeId}/${runnerId}`))
|
|
146
|
+
return true;
|
|
147
|
+
for (const id of nodes) {
|
|
148
|
+
for (const entry of this.nodeRegistry.getNodeManifest(id) ?? []) {
|
|
149
|
+
const runner = this.runnerIdResolver?.(entry.addonId) ?? entry.addonId;
|
|
150
|
+
if (entry.addonId === runnerId || runner === runnerId)
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
135
156
|
get childRegistry() {
|
|
136
157
|
return this.localChildRegistry;
|
|
137
158
|
}
|
|
@@ -400,7 +421,16 @@ class MoleculerService {
|
|
|
400
421
|
// `broker.call('$hub-cap-fwd.forward', …)` discovers it here. Without it a
|
|
401
422
|
// remote agent's raw `${cap}.*` discovery 30s-deadlines (hub-local addon
|
|
402
423
|
// runners expose no Moleculer service). See hub-cap-forward.ts.
|
|
403
|
-
this.brokerSafe.createService((0, system_1.createHubCapForwardService)(onUnownedCall
|
|
424
|
+
this.brokerSafe.createService((0, system_1.createHubCapForwardService)(onUnownedCall, {
|
|
425
|
+
// The agent stamps WHO is calling (its child's runner id) into the
|
|
426
|
+
// envelope (D787). Believe it only when that runner is on the agent
|
|
427
|
+
// the request came from — a node's registered manifest lists the
|
|
428
|
+
// addons it hosts, and a grouped runner answers to its group id.
|
|
429
|
+
isCallerOnNode: (senderNodeId, callerRunnerId) => this.isRunnerOnNode(senderNodeId, callerRunnerId),
|
|
430
|
+
onRejectedCaller: (senderNodeId, callerRunnerId) => logger.warn('dropped an agent caller stamp the sender does not host', {
|
|
431
|
+
meta: { senderNodeId, callerRunnerId },
|
|
432
|
+
}),
|
|
433
|
+
}));
|
|
404
434
|
const registry = new system_1.LocalChildRegistry({
|
|
405
435
|
server: (0, system_1.createLocalTransport)().createServer(nodeId),
|
|
406
436
|
// This node's own id — lets the cap-call-out router serve a pin-to-self
|
package/dist/main.js
CHANGED
|
@@ -87,12 +87,13 @@ const ws_slow_consumer_guard_js_1 = require("./api/trpc/ws-slow-consumer-guard.j
|
|
|
87
87
|
const addon_route_jwt_gate_js_1 = require("./auth/addon-route-jwt-gate.js");
|
|
88
88
|
const addon_route_share_gate_js_1 = require("./auth/addon-route-share-gate.js");
|
|
89
89
|
const auth_rate_limit_1 = require("./auth/auth-rate-limit");
|
|
90
|
+
const external_link_intents_js_1 = require("./auth/external-link-intents.js");
|
|
90
91
|
const session_cookie_js_1 = require("./auth/session-cookie.js");
|
|
91
92
|
const session_mint_js_1 = require("./auth/session-mint.js");
|
|
92
|
-
const external_link_intents_js_1 = require("./auth/external-link-intents.js");
|
|
93
93
|
const sso_finish_js_1 = require("./auth/sso-finish.js");
|
|
94
94
|
const sso_finish_deps_js_1 = require("./auth/sso-finish-deps.js");
|
|
95
95
|
const boot_config_1 = require("./boot/boot-config");
|
|
96
|
+
const platform_keychain_key_1 = require("./boot/platform-keychain-key");
|
|
96
97
|
const integration_id_backfill_1 = require("./boot/integration-id-backfill");
|
|
97
98
|
const post_boot_service_1 = require("./boot/post-boot.service");
|
|
98
99
|
const retired_public_url_env_1 = require("./boot/retired-public-url-env");
|
|
@@ -250,6 +251,15 @@ async function bootstrap() {
|
|
|
250
251
|
// forked, so no child can read it — and warned about once a logger exists
|
|
251
252
|
// (below). See `boot/retired-public-url-env.ts`.
|
|
252
253
|
const publicUrlWarning = (0, retired_public_url_env_1.dropRetiredHubAddressEnv)(process.env);
|
|
254
|
+
// A desktop hub's keychain master key arrives on an inherited pipe (D788).
|
|
255
|
+
// Read and closed before anything is forked, so no runner inherits it.
|
|
256
|
+
const platformKey = (0, platform_keychain_key_1.loadPlatformKeychainKey)();
|
|
257
|
+
if (platformKey.kind === 'loaded') {
|
|
258
|
+
console.log(`[keychain] master key provided by the ${platformKey.label}`);
|
|
259
|
+
}
|
|
260
|
+
else if (platformKey.kind === 'failed') {
|
|
261
|
+
console.warn(`[keychain] the desktop app's key pipe could not be read: ${platformKey.error}`);
|
|
262
|
+
}
|
|
253
263
|
// hub-main's own memory, on the record. Deliberately started HERE and not in
|
|
254
264
|
// the starter: the starter runs from the image's /opt closure, which only
|
|
255
265
|
// changes with a new image, while this entry is what `applyServerUpdate`
|
|
@@ -831,11 +841,15 @@ async function bootstrap() {
|
|
|
831
841
|
logFailure: (error) => ssoLogger.error('SSO finish failed', {
|
|
832
842
|
meta: { error: error instanceof Error ? error.message : String(error) },
|
|
833
843
|
}),
|
|
834
|
-
}, request.headers.cookie)
|
|
844
|
+
}, request.headers.cookie),
|
|
845
|
+
// A popup sign-in (D791) returns through the admin-ui completion page.
|
|
846
|
+
{ popup: request.query.popup === '1' });
|
|
835
847
|
if (outcome.kind === 'error')
|
|
836
848
|
return reply.status(outcome.status).send(outcome.body);
|
|
837
849
|
reply.code(302);
|
|
838
|
-
|
|
850
|
+
// Admin-ui paths are relative to the hub's mount (HA ingress etc.).
|
|
851
|
+
const ssoMount = (0, spa_mount_1.parseForwardedPrefix)(request.headers['x-forwarded-prefix']) ?? '';
|
|
852
|
+
reply.header('Location', `${ssoMount}${outcome.location}`);
|
|
839
853
|
return reply.send('');
|
|
840
854
|
});
|
|
841
855
|
// ── Backup archive download ─────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.482",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"files": [
|
|
@@ -37,18 +37,18 @@
|
|
|
37
37
|
]
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
41
|
-
"@camstack/addon-agent-ui": "1.2.
|
|
42
|
-
"@camstack/addon-auth": "1.2.
|
|
40
|
+
"@camstack/addon-admin-ui": "1.2.355",
|
|
41
|
+
"@camstack/addon-agent-ui": "1.2.192",
|
|
42
|
+
"@camstack/addon-auth": "1.2.132",
|
|
43
43
|
"@camstack/addon-decoder-nodeav": "1.2.121",
|
|
44
44
|
"@camstack/addon-notifiers": "1.2.128",
|
|
45
|
-
"@camstack/addon-pipeline": "1.2.
|
|
46
|
-
"@camstack/addon-pipeline-orchestrator": "1.2.
|
|
47
|
-
"@camstack/addon-post-analysis": "1.2.
|
|
45
|
+
"@camstack/addon-pipeline": "1.2.346",
|
|
46
|
+
"@camstack/addon-pipeline-orchestrator": "1.2.298",
|
|
47
|
+
"@camstack/addon-post-analysis": "1.2.344",
|
|
48
48
|
"@camstack/sdk": "1.2.126",
|
|
49
|
-
"@camstack/system": "1.2.
|
|
50
|
-
"@camstack/types": "1.2.
|
|
51
|
-
"@camstack/ui-library": "1.2.
|
|
49
|
+
"@camstack/system": "1.2.377",
|
|
50
|
+
"@camstack/types": "1.2.328",
|
|
51
|
+
"@camstack/ui-library": "1.2.250",
|
|
52
52
|
"@fastify/compress": "^9.0.0",
|
|
53
53
|
"@fastify/cookie": "^11.0.2",
|
|
54
54
|
"@fastify/cors": "^11.2.0",
|